Skip to content

Commit

Permalink
Merge pull request #926 from woocommerce/feature/863-integrate-api
Browse files Browse the repository at this point in the history
Integrate with contact information API and complete form submissions within MC setup and settings
  • Loading branch information
eason9487 authored Aug 4, 2021
2 parents dfc4be1 + 0fc2b84 commit d03a8ec
Show file tree
Hide file tree
Showing 11 changed files with 240 additions and 111 deletions.
38 changes: 33 additions & 5 deletions js/src/components/contact-information/phone-number-card.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
/**
* External dependencies
*/
import { getCountryCallingCode } from 'libphonenumber-js';
import {
getCountryCallingCode,
parsePhoneNumberFromString as parsePhoneNumber,
} from 'libphonenumber-js';
import { __ } from '@wordpress/i18n';
import { useState } from '@wordpress/element';
import { Flex, FlexItem, FlexBlock, CardDivider } from '@wordpress/components';
Expand Down Expand Up @@ -34,10 +37,35 @@ function PhoneNumberContent( {
setCountry( nextCountry );
setNumber( nextNumber );

const countryCallingCode = nextCountry
? getCountryCallingCode( nextCountry )
: '';
onPhoneNumberChange( countryCallingCode, nextNumber, nextCountry );
const parsed = parsePhoneNumber( nextNumber, nextCountry );
const isValid = parsed ? parsed.isValid() : false;

if ( parsed ) {
const isDirty =
nextCountry !== initCountry ||
parsed.nationalNumber !== initNationalNumber;

onPhoneNumberChange( {
...parsed,
isValid,
isDirty,
} );
} else {
const isDirty =
nextCountry !== initCountry ||
nextNumber !== initNationalNumber;

const countryCallingCode = nextCountry
? getCountryCallingCode( nextCountry )
: '';

onPhoneNumberChange( {
isValid,
isDirty,
countryCallingCode,
nationalNumber: nextNumber,
} );
}
};

const handleCountryChange = ( nextCountry ) =>
Expand Down
1 change: 1 addition & 0 deletions js/src/data/action-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const TYPES = {
'RECEIVE_ACCOUNTS_GOOGLE_ADS_BILLING_STATUS',
RECEIVE_ACCOUNTS_GOOGLE_ADS_EXISTING:
'RECEIVE_ACCOUNTS_GOOGLE_ADS_EXISTING',
RECEIVE_MC_CONTACT_INFORMATION: 'RECEIVE_MC_CONTACT_INFORMATION',
RECEIVE_COUNTRIES: 'RECEIVE_COUNTRIES',
RECEIVE_TARGET_AUDIENCE: 'RECEIVE_TARGET_AUDIENCE',
SAVE_TARGET_AUDIENCE: 'SAVE_TARGET_AUDIENCE',
Expand Down
44 changes: 44 additions & 0 deletions js/src/data/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,50 @@ export function* fetchExistingGoogleAdsAccounts() {
}
}

export function receiveGoogleMCContactInformation( data ) {
return {
type: TYPES.RECEIVE_MC_CONTACT_INFORMATION,
data,
};
}

/**
* Update the contact information to user's account of Google Merchant Center.
* The phone number will be updated only if `countryCallingCode` and `nationalNumber` are not falsy.
*
* @param {string} [countryCallingCode] The country calling code. Example: '1'.
* @param {string} [nationalNumber] The national (significant) number. Example: '2133734253'.
*/
export function* updateGoogleMCContactInformation(
countryCallingCode,
nationalNumber
) {
const data = {};

if ( countryCallingCode && nationalNumber ) {
data.phone_number = `+${ countryCallingCode }${ nationalNumber }`;
}

try {
const response = yield apiFetch( {
path: `${ API_NAMESPACE }/mc/contact-information`,
method: 'POST',
data,
} );

yield receiveGoogleMCContactInformation( response );
} catch ( error ) {
yield handleFetchError(
error,
__(
'Unable to update your Google Merchant Center contact information. Please try again later.',
'google-listings-and-ads'
)
);
throw error;
}
}

export function* fetchCountries() {
try {
const response = yield apiFetch( {
Expand Down
12 changes: 12 additions & 0 deletions js/src/data/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const DEFAULT_STATE = {
ads_billing_status: null,
google_access: null,
},
contact: null,
},
ads_campaigns: null,
mc_setup: null,
Expand Down Expand Up @@ -223,6 +224,17 @@ const reducer = ( state = DEFAULT_STATE, action ) => {
return newState;
}

case TYPES.RECEIVE_MC_CONTACT_INFORMATION: {
const newState = {
...state,
mc: {
...state.mc,
contact: action.data,
},
};
return newState;
}

case TYPES.RECEIVE_COUNTRIES: {
const { countries } = action;
const newState = cloneDeep( state );
Expand Down
18 changes: 16 additions & 2 deletions js/src/data/resolvers.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
fetchGoogleAdsAccount,
fetchGoogleAdsAccountBillingStatus,
fetchExistingGoogleAdsAccounts,
receiveGoogleMCContactInformation,
fetchCountries,
fetchTargetAudience,
fetchAdsCampaigns,
Expand Down Expand Up @@ -106,8 +107,21 @@ export function* getExistingGoogleAdsAccounts() {
yield fetchExistingGoogleAdsAccounts();
}

export function* getGoogleMCPhoneNumber() {
// TODO: [lite-contact-info] integrate with API
export function* getGoogleMCContactInformation() {
try {
const data = yield apiFetch( {
path: `${ API_NAMESPACE }/mc/contact-information`,
} );
yield receiveGoogleMCContactInformation( data );
} catch ( error ) {
yield handleFetchError(
error,
__(
'There was an error loading Google Merchant Center contact information.',
'google-listings-and-ads'
)
);
}
}

export function* getCountries() {
Expand Down
25 changes: 18 additions & 7 deletions js/src/data/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,26 @@ export const getExistingGoogleAdsAccounts = ( state ) => {
return state.mc.accounts.existing_ads;
};

const mockPhoneNumber = Math.random() > 0.5 ? '+12133734253' : '';
export const getGoogleMCPhoneNumber = () => {
// TODO: [lite-contact-info] integrate with API
return {
id: '123456789',
phone_number: mockPhoneNumber,
};
export const getGoogleMCContactInformation = ( state ) => {
return state.mc.contact;
};

// Create another selector to separate the `hasFinishedResolution` state with `getGoogleMCContactInformation`.
export const getGoogleMCPhoneNumber = createRegistrySelector(
( select ) => ( state ) => {
const selector = select( STORE_KEY );

const loaded =
!! getGoogleMCContactInformation( state ) ||
selector.hasFinishedResolution( 'getGoogleMCContactInformation' );

return {
loaded,
data: selector.getGoogleMCContactInformation(),
};
}
);

export const getCountries = ( state ) => {
return state.mc.countries;
};
Expand Down
12 changes: 6 additions & 6 deletions js/src/hooks/useGoogleMCPhoneNumber.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { parsePhoneNumberFromString as parsePhoneNumber } from 'libphonenumber-j
*/
import { STORE_KEY } from '.~/data/constants';

const selectorName = 'getGoogleMCPhoneNumber';
const emptyData = {
country: '',
countryCallingCode: '',
Expand All @@ -20,12 +19,13 @@ const emptyData = {

export default function useGoogleMCPhoneNumber() {
return useSelect( ( select ) => {
const selector = select( STORE_KEY );
const phone = selector[ selectorName ]();
const { getGoogleMCPhoneNumber } = select( STORE_KEY );
const { data: contact, loaded } = getGoogleMCPhoneNumber();
let data = emptyData;

if ( phone ) {
const parsed = parsePhoneNumber( phone.phone_number );
if ( contact ) {
// Prevent to call parsePhoneNumber with null.
const parsed = parsePhoneNumber( contact.phone_number || '' );
if ( parsed ) {
data = {
...parsed,
Expand All @@ -37,7 +37,7 @@ export default function useGoogleMCPhoneNumber() {
}

return {
loaded: selector.hasFinishedResolution( selectorName ),
loaded,
data,
};
}, [] );
Expand Down
125 changes: 54 additions & 71 deletions js/src/hooks/useStoreAddress.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
/**
* External dependencies
* Internal dependencies
*/
import { useCallback } from '@wordpress/element';
import { useSelect, useDispatch } from '@wordpress/data';
import { SETTINGS_STORE_NAME, OPTIONS_STORE_NAME } from '@woocommerce/data';
import useAppSelectDispatch from './useAppSelectDispatch';
import useCountryKeyNameMap from './useCountryKeyNameMap';

const optionNames = [
'woocommerce_store_address',
'woocommerce_store_address_2',
'woocommerce_store_city',
'woocommerce_default_country',
'woocommerce_store_postcode',
];

const selectorName = 'getOption';
const emptyData = {
address: '',
address2: '',
city: '',
state: '',
country: '',
postcode: '',
isMCAddressDifferent: null,
isAddressFilled: null,
};

/**
* @typedef {Object} StoreAddress
Expand All @@ -23,6 +23,10 @@ const selectorName = 'getOption';
* @property {string} state Store country state if available.
* @property {string} country Store country.
* @property {string} postcode Store postcode.
* @property {boolean|null} isAddressFilled Whether the minimum address data is filled in.
* `null` if data have not loaded yet.
* @property {boolean|null} isMCAddressDifferent Whether the address data from WC store and GMC are the same.
* `null` if data have not loaded yet.
*/
/**
* @typedef {Object} StoreAddressResult
Expand All @@ -36,68 +40,47 @@ const selectorName = 'getOption';
* @return {StoreAddressResult} Store address result.
*/
export default function useStoreAddress() {
const { invalidateResolution } = useDispatch( OPTIONS_STORE_NAME );
const refetch = useCallback( () => {
optionNames.forEach( ( name ) =>
invalidateResolution( selectorName, [ name ] )
);
}, [ invalidateResolution ] );

return useSelect(
( select ) => {
const selector = select( OPTIONS_STORE_NAME );
const { hasFinishedResolution } = selector;

const [
address,
address2,
city,
countryAndState,
postcode,
] = optionNames.map( ( name ) => selector[ selectorName ]( name ) );
const {
data: contact,
hasFinishedResolution: loaded,
invalidateResolution: refetch,
} = useAppSelectDispatch( 'getGoogleMCContactInformation' );

const loaded = optionNames
.map( ( name ) =>
hasFinishedResolution( selectorName, [ name ] )
)
.every( Boolean );
const countryNameDict = useCountryKeyNameMap();

let country = '';
let state = '';
let data = emptyData;

if ( loaded ) {
const { getSetting } = select( SETTINGS_STORE_NAME );
const { countries } = getSetting( 'wc_admin', 'dataEndpoints' );
const [ countryCode, stateCode ] = countryAndState.split( ':' );
if ( loaded && contact ) {
const {
wc_address: {
country: countryCode,
locality: city,
postal_code: postcode,
region: state,
street_address: streetAddress,
},
is_mc_address_different: isMCAddressDifferent,
} = contact;

const countryMeta = countries.find(
( el ) => el.code === countryCode
);
if ( countryMeta ) {
country = countryMeta.name;
const [ address, address2 = '' ] = streetAddress.split( '\n' );
const country = countryNameDict[ countryCode ];
const isAddressFilled = !! ( address && city && country && postcode );

const stateMeta = countryMeta.states.find(
( el ) => el.code === stateCode
);
if ( stateMeta ) {
state = stateMeta.name;
}
}
}
data = {
address,
address2,
city,
state,
country,
postcode,
isAddressFilled,
isMCAddressDifferent,
};
}

return {
refetch,
loaded,
data: {
address,
address2,
city,
state,
country,
postcode,
},
};
},
[ refetch ]
);
return {
refetch,
loaded,
data,
};
}
Loading

0 comments on commit d03a8ec

Please sign in to comment.