Skip to content

Commit

Permalink
feat(common): CHECKOUT-4642 Expose Save Address checkbox
Browse files Browse the repository at this point in the history
  • Loading branch information
lpschz committed May 26, 2020
1 parent 40d1f6f commit d231cc1
Show file tree
Hide file tree
Showing 20 changed files with 95 additions and 45 deletions.
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
},
"homepage": "https://github.com/bigcommerce/checkout-js#readme",
"dependencies": {
"@bigcommerce/checkout-sdk": "^1.71.0",
"@bigcommerce/checkout-sdk": "^1.72.0",
"@bigcommerce/citadel": "^2.15.1",
"@bigcommerce/form-poster": "^1.2.2",
"@bigcommerce/memoize": "^1.0.0",
Expand Down
7 changes: 7 additions & 0 deletions src/app/address/AddressForm.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ describe('AddressForm Component', () => {
</LocaleContext.Provider>
);

expect(component.find('[name="address.shouldSaveAddress"]').exists())
.toEqual(false);

expect(component.find(AddressFormField).length).toEqual(formFields.length);
});

Expand All @@ -55,6 +58,7 @@ describe('AddressForm Component', () => {
<AddressForm
fieldName="address"
formFields={ formFields }
shouldShowSaveAddress={ true }
/>
</Formik>
</LocaleContext.Provider>
Expand All @@ -67,6 +71,9 @@ describe('AddressForm Component', () => {
})
);

expect(component.find('[name="address.shouldSaveAddress"]').exists())
.toEqual(true);

expect(component.find(AddressFormField).at(0).prop('field')).toEqual(
expect.objectContaining({
id: 'field_14',
Expand Down
78 changes: 44 additions & 34 deletions src/app/address/AddressForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { memoize } from '@bigcommerce/memoize';
import { forIn, noop } from 'lodash';
import React, { createRef, Component, ReactNode, RefObject } from 'react';

import { withLanguage, WithLanguageProps } from '../locale';
import { withLanguage, TranslatedString, WithLanguageProps } from '../locale';
import { AutocompleteItem } from '../ui/autocomplete';
import { CheckboxFormField, Fieldset } from '../ui/form';

import { mapToAddress, GoogleAutocompleteFormField } from './googleAutocomplete';
import './AddressForm.scss';
Expand All @@ -19,6 +20,7 @@ export interface AddressFormProps {
countries?: Country[];
formFields: FormField[];
googleMapsApiKey?: string;
shouldShowSaveAddress?: boolean;
onAutocompleteSelect?(address: Partial<Address>): void;
onAutocompleteToggle?(state: { inputValue: string; isOpen: boolean }): void;
onChange?(fieldName: string, value: string | string[]): void;
Expand Down Expand Up @@ -58,46 +60,54 @@ class AddressForm extends Component<AddressFormProps & WithLanguageProps> {
countryCode,
googleMapsApiKey,
onAutocompleteToggle,
shouldShowSaveAddress,
} = this.props;

return (
<div className="checkout-address" ref={ this.containerRef as RefObject<HTMLDivElement> }>
{ formFields.map(field => {
const addressFieldName = field.name;
const translatedPlaceholderId = PLACEHOLDER[addressFieldName];
return (<>
<Fieldset>
<div className="checkout-address" ref={ this.containerRef as RefObject<HTMLDivElement> }>
{ formFields.map(field => {
const addressFieldName = field.name;
const translatedPlaceholderId = PLACEHOLDER[addressFieldName];

if (addressFieldName === 'address1' && googleMapsApiKey && countriesWithAutocomplete) {
return (
<GoogleAutocompleteFormField
apiKey={ googleMapsApiKey }
countryCode={ countryCode }
field={ field }
key={ field.id }
nextElement={ this.nextElement || undefined }
onChange={ this.handleAutocompleteChange }
onSelect={ this.handleAutocompleteSelect }
onToggleOpen={ onAutocompleteToggle }
parentFieldName={ fieldName }
supportedCountries={ countriesWithAutocomplete }
/>
);
}

if (addressFieldName === 'address1' && googleMapsApiKey && countriesWithAutocomplete) {
return (
<GoogleAutocompleteFormField
apiKey={ googleMapsApiKey }
countryCode={ countryCode }
<AddressFormField
field={ field }
key={ field.id }
nextElement={ this.nextElement || undefined }
onChange={ this.handleAutocompleteChange }
onSelect={ this.handleAutocompleteSelect }
onToggleOpen={ onAutocompleteToggle }
parentFieldName={ fieldName }
supportedCountries={ countriesWithAutocomplete }
// stateOrProvince can sometimes be a dropdown or input, so relying on id is not sufficient
key={ `${field.id}-${field.name}` }
onChange={ this.handleDynamicFormFieldChange(addressFieldName) }
parentFieldName={ field.custom ?
(fieldName ? `${fieldName}.customFields` : 'customFields') :
fieldName }
placeholder={ translatedPlaceholderId && language.translate(translatedPlaceholderId) }
/>
);
}

return (
<AddressFormField
field={ field }
// stateOrProvince can sometimes be a dropdown or input, so relying on id is not sufficient
key={ `${field.id}-${field.name}` }
onChange={ this.handleDynamicFormFieldChange(addressFieldName) }
parentFieldName={ field.custom ?
(fieldName ? `${fieldName}.customFields` : 'customFields') :
fieldName }
placeholder={ translatedPlaceholderId && language.translate(translatedPlaceholderId) }
/>
);
}) }
</div>
);
}) }
</div>
</Fieldset>
{ shouldShowSaveAddress &&
<CheckboxFormField
labelContent={ <TranslatedString id="address.save_in_addressbook" /> }
name={ fieldName ? `${fieldName}.shouldSaveAddress` : 'shouldSaveAddress' }
/> }
</>);
}

private handleAutocompleteChange: (value: string, isOpen: boolean) => void = (value, isOpen) => {
Expand Down
1 change: 1 addition & 0 deletions src/app/address/isEqualAddress.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ describe('isEqualAddress', () => {
stateOrProvinceCode: 'w',
id: 'x',
email: 'y',
shouldSaveAddress: false,
type: 'z',
})).toBeTruthy();
});
Expand Down
2 changes: 1 addition & 1 deletion src/app/address/isEqualAddress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default function isEqualAddress(address1?: ComparableAddress, address2?:
}

function normalizeAddress(address: ComparableAddress) {
const ignoredFields: ComparableAddressFields[] = ['id', 'stateOrProvinceCode', 'type', 'email'];
const ignoredFields: ComparableAddressFields[] = ['id', 'shouldSaveAddress', 'stateOrProvinceCode', 'type', 'email'];

return omit(
{
Expand Down
3 changes: 2 additions & 1 deletion src/app/address/mapAddressFromFormValues.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ describe('mapAddressFromFormValues', () => {
customFields: {},
};

expect(mapAddressFromFormValues(formValues)).toMatchObject(getShippingAddress());
expect(mapAddressFromFormValues(formValues))
.toMatchObject(getShippingAddress());
});

it('converts formats date values to YYYY-MM-DD format', () => {
Expand Down
2 changes: 2 additions & 0 deletions src/app/address/mapAddressFromFormValues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { AddressFormValues } from './mapAddressToFormValues';

export default function mapAddressFromFormValues(formValues: AddressFormValues): Address {
const { customFields: customFieldsObject, ...address } = formValues;
const shouldSaveAddress = formValues.shouldSaveAddress;
const customFields: Array<{fieldId: string; fieldValue: string}> = [];

forIn(customFieldsObject, (value, key) => {
Expand All @@ -26,6 +27,7 @@ export default function mapAddressFromFormValues(formValues: AddressFormValues):

return {
...address,
shouldSaveAddress,
customFields,
};
}
8 changes: 6 additions & 2 deletions src/app/address/mapAddressToFormValues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ export default function mapAddressToFormValues(fields: FormField[], address?: Ad
),
});

values.shouldSaveAddress = address && address.shouldSaveAddress !== undefined ?
address.shouldSaveAddress :
true;

// Manually backfill stateOrProvince to avoid Formik warning (uncontrolled to controlled input)
if (values.stateOrProvince === undefined) {
values.stateOrProvince = '';
Expand Down Expand Up @@ -72,6 +76,6 @@ function getDefaultValue(fieldType?: string, defaultValue?: string): string | st
return defaultValue || '';
}

function isSystemAddressFieldName(fieldName: string): fieldName is Exclude<keyof Address, 'customFields'> {
return fieldName !== 'customFields';
function isSystemAddressFieldName(fieldName: string): fieldName is Exclude<keyof Address, 'customFields' | 'shouldSaveAddress'> {
return fieldName !== 'customFields' && fieldName !== 'shouldSaveAddress';
}
1 change: 1 addition & 0 deletions src/app/billing/Billing.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ describe('Billing Component', () => {
stateOrProvinceCode: '',
firstName: 'foo',
lastName: 'Tester',
shouldSaveAddress: true,
});

expect(defaultProps.navigateNextStep).toHaveBeenCalled();
Expand Down
2 changes: 2 additions & 0 deletions src/app/billing/Billing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface WithCheckoutBillingProps {
googleMapsApiKey: string;
isInitializing: boolean;
isUpdating: boolean;
hasSaveAddressFeature: boolean;
shouldShowOrderComments: boolean;
getFields(countryCode?: string): FormField[];
initialize(): Promise<CheckoutSelectors>;
Expand Down Expand Up @@ -166,6 +167,7 @@ function mapToBillingProps({
initialize: checkoutService.loadBillingAddressFields,
isInitializing: isLoadingBillingCountries(),
isUpdating: isUpdatingBillingAddress() || isUpdatingCheckout(),
hasSaveAddressFeature: features['CHECKOUT-4642.uco_save_address_checkbox'],
shouldShowOrderComments: enableOrderComments && getShippableItemsCount(cart) < 1,
updateAddress: checkoutService.updateBillingAddress,
updateCheckout: checkoutService.updateCheckout,
Expand Down
2 changes: 2 additions & 0 deletions src/app/billing/BillingForm.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ describe('BillingForm Component', () => {
customer: getCustomer(),
countries: getCountries(),
googleMapsApiKey: 'key',
hasSaveAddressFeature: true,
getFields: () => getFormFields(),
onUnhandledError: jest.fn(),
updateAddress: jest.fn(),
Expand Down Expand Up @@ -111,6 +112,7 @@ describe('BillingForm Component', () => {
stateOrProvinceCode: '',
firstName: 'foo',
lastName: 'Tester',
shouldSaveAddress: true,
});
});

Expand Down
5 changes: 4 additions & 1 deletion src/app/billing/BillingForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface BillingFormProps {
countriesWithAutocomplete: string[];
googleMapsApiKey: string;
isUpdating: boolean;
hasSaveAddressFeature: boolean;
shouldShowOrderComments: boolean;
getFields(countryCode?: string): FormField[];
onUnhandledError(error: Error): void;
Expand All @@ -43,12 +44,13 @@ class BillingForm extends PureComponent<BillingFormProps & WithLanguageProps & F
googleMapsApiKey,
billingAddress,
countriesWithAutocomplete,
customer: { addresses },
customer: { addresses, isGuest },
getFields,
countries,
isUpdating,
setFieldValue,
shouldShowOrderComments,
hasSaveAddressFeature,
values,
} = this.props;

Expand Down Expand Up @@ -81,6 +83,7 @@ class BillingForm extends PureComponent<BillingFormProps & WithLanguageProps & F
formFields={ getFields(values.countryCode) }
googleMapsApiKey={ googleMapsApiKey }
setFieldValue={ setFieldValue }
shouldShowSaveAddress={ hasSaveAddressFeature && !isGuest }
/>
</LoadingOverlay> }
</Fieldset>
Expand Down
4 changes: 2 additions & 2 deletions src/app/locale/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"phone_number_required_error": "Phone Number is required",
"postal_code_label": "Postal Code",
"postal_code_required_error": "Postal Code is required",
"save_in_addressbook": "Save this address in my address book.",
"select_country_action": "Select a country",
"select_state_action": "Select a state",
"state_label": "State/Province",
Expand All @@ -41,7 +42,7 @@
"billing_heading": "Billing",
"save_billing_address_error": "An error occurred while saving the billing address to your price quote. Please try again.",
"billing_address_amazon": "Same as the Billing address set by you in your Amazon account.",
"use_shipping_address_label": "My Billing address is the same as my Shipping address"
"use_shipping_address_label": "My billing address is the same as my shipping address."
},
"cart": {
"billed_amount_text": "*You will be charged and invoiced {total} ({code}) for this order.",
Expand Down Expand Up @@ -145,7 +146,6 @@
"send": "Send",
"error_temporary_disabled": "Sign-in link functionality is temporary unavailable. Please sign in by entering your password.",
"resend_link": "Didn't get the email? <a>Resend the link</a>",
"use_password_action": "Use Password Instead",
"use_password_link": " or <a>sign in using your password</a> instead."
},
"embedded_checkout": {
Expand Down
4 changes: 4 additions & 0 deletions src/app/shipping/Shipping.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface WithCheckoutShippingProps {
customer: Customer;
customerMessage: string;
googleMapsApiKey: string;
hasSaveAddressFeature: boolean;
isGuest: boolean;
isInitializing: boolean;
isLoading: boolean;
Expand Down Expand Up @@ -102,6 +103,7 @@ class Shipping extends Component<ShippingProps & WithCheckoutShippingProps, Ship
initializeShippingMethod,
deinitializeShippingMethod,
isMultiShippingMode,
hasSaveAddressFeature,
onToggleMultiShipping,
...shippingFormProps
} = this.props;
Expand Down Expand Up @@ -133,6 +135,7 @@ class Shipping extends Component<ShippingProps & WithCheckoutShippingProps, Ship
onMultiShippingSubmit={ this.handleMultiShippingSubmit }
onSingleShippingSubmit={ this.handleSingleShippingSubmit }
onUseNewAddress={ this.handleUseNewAddress }
shouldShowSaveAddress={ !isGuest && hasSaveAddressFeature }
updateAddress={ updateShippingAddress }
/>
</LoadingOverlay>
Expand Down Expand Up @@ -322,6 +325,7 @@ export function mapToShippingProps({
googleMapsApiKey,
initializeShippingMethod: checkoutService.initializeShipping,
isGuest: customer.isGuest,
hasSaveAddressFeature: features['CHECKOUT-4642.uco_save_address_checkbox'],
isInitializing: isLoadingShippingCountries() || isLoadingShippingOptions(),
isLoading,
loadShippingAddressFields: checkoutService.loadShippingAddressFields,
Expand Down
3 changes: 3 additions & 0 deletions src/app/shipping/ShippingAddress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface ShippingAddressProps {
isLoading: boolean;
methodId?: string;
shippingAddress?: Address;
shouldShowSaveAddress?: boolean;
hasRequestedShippingOptions: boolean;
deinitialize(options: ShippingRequestOptions): Promise<CheckoutSelectors>;
initialize(options: ShippingInitializeOptions): Promise<CheckoutSelectors>;
Expand All @@ -44,6 +45,7 @@ const ShippingAddress: FunctionComponent<ShippingAddressProps> = props => {
shippingAddress,
hasRequestedShippingOptions,
addresses,
shouldShowSaveAddress,
onUnhandledError = noop,
} = props;

Expand Down Expand Up @@ -102,6 +104,7 @@ const ShippingAddress: FunctionComponent<ShippingAddressProps> = props => {
onAddressSelect={ onAddressSelect }
onFieldChange={ handleFieldChange }
onUseNewAddress={ onUseNewAddress }
shouldShowSaveAddress={ shouldShowSaveAddress }
/>
);
};
Expand Down
3 changes: 3 additions & 0 deletions src/app/shipping/ShippingAddressForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface ShippingAddressFormProps {
googleMapsApiKey?: string;
isLoading: boolean;
formFields: FormField[];
shouldShowSaveAddress?: boolean;
onUseNewAddress(): void;
onFieldChange(fieldName: string, value: string): void;
onAddressSelect(address: Address): void;
Expand All @@ -31,6 +32,7 @@ class ShippingAddressForm extends Component<ShippingAddressFormProps & ConnectFo
address: shippingAddress,
onAddressSelect,
onUseNewAddress,
shouldShowSaveAddress,
countries,
countriesWithAutocomplete,
formFields,
Expand Down Expand Up @@ -72,6 +74,7 @@ class ShippingAddressForm extends Component<ShippingAddressFormProps & ConnectFo
onAutocompleteToggle={ this.handleAutocompleteToggle }
onChange={ this.handleChange }
setFieldValue={ this.setFieldValue }
shouldShowSaveAddress={ shouldShowSaveAddress }
/>
</LoadingOverlay> }
</Fieldset>
Expand Down
Loading

0 comments on commit d231cc1

Please sign in to comment.