diff --git a/packages/peregrine/lib/Apollo/clearCustomerDataFromCache.js b/packages/peregrine/lib/Apollo/clearCustomerDataFromCache.js
new file mode 100644
index 0000000000..431561ea59
--- /dev/null
+++ b/packages/peregrine/lib/Apollo/clearCustomerDataFromCache.js
@@ -0,0 +1,14 @@
+import { deleteCacheEntry } from './deleteCacheEntry';
+
+/**
+ * Deletes all references to Customer from the apollo cache including entries
+ * that start with "$" which were automatically created by Apollo InMemoryCache.
+ * By coincidence this rule additionally clears CustomerAddress entries, but
+ * we'll need to keep this in mind by adding additional patterns as MyAccount
+ * features are completed.
+ *
+ * @param {ApolloClient} client
+ */
+export const clearCustomerDataFromCache = async client => {
+ await deleteCacheEntry(client, key => key.match(/^\$?Customer/));
+};
diff --git a/packages/peregrine/lib/talons/AuthModal/useAuthModal.js b/packages/peregrine/lib/talons/AuthModal/useAuthModal.js
index 557b5bfbc8..03a8734379 100644
--- a/packages/peregrine/lib/talons/AuthModal/useAuthModal.js
+++ b/packages/peregrine/lib/talons/AuthModal/useAuthModal.js
@@ -4,6 +4,7 @@ import { useApolloClient, useMutation } from '@apollo/react-hooks';
import { useUserContext } from '../../context/user';
import { clearCartDataFromCache } from '../../Apollo/clearCartDataFromCache';
+import { clearCustomerDataFromCache } from '../../Apollo/clearCustomerDataFromCache';
const UNAUTHED_ONLY = ['CREATE_ACCOUNT', 'FORGOT_PASSWORD', 'SIGN_IN'];
@@ -76,6 +77,7 @@ export const useAuthModal = props => {
// Delete cart/user data from the redux store.
await signOut({ revokeToken });
await clearCartDataFromCache(apolloClient);
+ await clearCustomerDataFromCache(apolloClient);
// Refresh the page as a way to say "re-initialize". An alternative
// would be to call apolloClient.resetStore() but that would require
diff --git a/packages/peregrine/lib/talons/CheckoutPage/AddressBook/__tests__/__snapshots__/useAddressBook.spec.js.snap b/packages/peregrine/lib/talons/CheckoutPage/AddressBook/__tests__/__snapshots__/useAddressBook.spec.js.snap
new file mode 100644
index 0000000000..0bdaa8873f
--- /dev/null
+++ b/packages/peregrine/lib/talons/CheckoutPage/AddressBook/__tests__/__snapshots__/useAddressBook.spec.js.snap
@@ -0,0 +1,49 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`callbacks update and return state handleApplyAddress 1`] = `
+Object {
+ "variables": Object {
+ "addressId": 2,
+ "cartId": "cart123",
+ },
+}
+`;
+
+exports[`returns the correct shape 1`] = `
+Object {
+ "activeAddress": undefined,
+ "customerAddresses": Array [
+ Object {
+ "firstname": "Philip",
+ "id": 1,
+ "lastname": "Fry",
+ "street": Array [
+ "3000 57th Street",
+ ],
+ },
+ Object {
+ "firstname": "Bender",
+ "id": 2,
+ "lastname": "Rodríguez",
+ "street": Array [
+ "3000 57th Street",
+ ],
+ },
+ Object {
+ "firstname": "John",
+ "id": 3,
+ "lastname": "Zoidberg",
+ "street": Array [
+ "1 Dumpster Alley",
+ ],
+ },
+ ],
+ "handleAddAddress": [Function],
+ "handleApplyAddress": [Function],
+ "handleCancel": [Function],
+ "handleEditAddress": [Function],
+ "handleSelectAddress": [Function],
+ "isLoading": true,
+ "selectedAddress": 2,
+}
+`;
diff --git a/packages/peregrine/lib/talons/CheckoutPage/AddressBook/__tests__/__snapshots__/useAddressCard.spec.js.snap b/packages/peregrine/lib/talons/CheckoutPage/AddressBook/__tests__/__snapshots__/useAddressCard.spec.js.snap
new file mode 100644
index 0000000000..7184f975eb
--- /dev/null
+++ b/packages/peregrine/lib/talons/CheckoutPage/AddressBook/__tests__/__snapshots__/useAddressCard.spec.js.snap
@@ -0,0 +1,24 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`event handlers fire callbacks handleEditAddress 1`] = `
+Object {
+ "country": Object {
+ "code": "US",
+ },
+ "email": "fry@planet.express",
+ "firstname": "Philip",
+ "id": 66,
+ "region": Object {
+ "id": 22,
+ },
+}
+`;
+
+exports[`returns correct shape 1`] = `
+Object {
+ "handleClick": [Function],
+ "handleEditAddress": [Function],
+ "handleKeyPress": [Function],
+ "hasUpdate": false,
+}
+`;
diff --git a/packages/peregrine/lib/talons/CheckoutPage/AddressBook/__tests__/useAddressBook.spec.js b/packages/peregrine/lib/talons/CheckoutPage/AddressBook/__tests__/useAddressBook.spec.js
new file mode 100644
index 0000000000..50472cb114
--- /dev/null
+++ b/packages/peregrine/lib/talons/CheckoutPage/AddressBook/__tests__/useAddressBook.spec.js
@@ -0,0 +1,198 @@
+import React from 'react';
+import { act } from 'react-test-renderer';
+
+import { useAddressBook } from '../useAddressBook';
+import createTestInstance from '../../../../util/createTestInstance';
+import { useAppContext } from '../../../../context/app';
+
+const mockGetCustomerAddresses = jest.fn().mockReturnValue({
+ data: {
+ customer: {
+ addresses: [
+ {
+ firstname: 'Philip',
+ id: 1,
+ lastname: 'Fry',
+ street: ['3000 57th Street']
+ },
+ {
+ firstname: 'Bender',
+ id: 2,
+ lastname: 'Rodríguez',
+ street: ['3000 57th Street']
+ },
+ {
+ firstname: 'John',
+ id: 3,
+ lastname: 'Zoidberg',
+ street: ['1 Dumpster Alley']
+ }
+ ]
+ }
+ },
+ error: false,
+ loading: false
+});
+
+const mockGetCustomerCartAddress = jest.fn().mockReturnValue({
+ data: {
+ customerCart: {
+ shipping_addresses: [
+ {
+ firstname: 'Bender',
+ lastname: 'Rodríguez',
+ street: ['3000 57th Street']
+ }
+ ]
+ }
+ },
+ error: false,
+ loading: false
+});
+
+const mockSetCustomerAddressOnCart = jest.fn();
+
+jest.mock('@apollo/react-hooks', () => ({
+ useQuery: jest.fn().mockImplementation(query => {
+ if (query === 'getCustomerAddressesQuery')
+ return mockGetCustomerAddresses();
+
+ if (query === 'getCustomerCartAddressQuery')
+ return mockGetCustomerCartAddress();
+
+ return;
+ }),
+ useMutation: jest.fn(() => [
+ mockSetCustomerAddressOnCart,
+ { loading: true }
+ ])
+}));
+
+jest.mock('../../../../context/app', () => {
+ const state = {};
+ const api = {
+ toggleDrawer: jest.fn()
+ };
+ const useAppContext = jest.fn(() => [state, api]);
+
+ return { useAppContext };
+});
+
+jest.mock('../../../../context/cart', () => {
+ const state = {
+ cartId: 'cart123'
+ };
+ const api = {};
+ const useCartContext = jest.fn(() => [state, api]);
+
+ return { useCartContext };
+});
+
+const Component = props => {
+ const talonProps = useAddressBook(props);
+ return ;
+};
+
+const toggleActiveContent = jest.fn();
+const mockProps = {
+ mutations: {},
+ queries: {
+ getCustomerAddressesQuery: 'getCustomerAddressesQuery',
+ getCustomerCartAddressQuery: 'getCustomerCartAddressQuery'
+ },
+ toggleActiveContent
+};
+
+test('returns the correct shape', () => {
+ const tree = createTestInstance();
+ const { root } = tree;
+ const { talonProps } = root.findByType('i').props;
+
+ expect(talonProps).toMatchSnapshot();
+});
+
+test('auto selects new address', () => {
+ mockGetCustomerAddresses.mockReturnValueOnce({
+ data: {
+ customer: {
+ addresses: [
+ {
+ firstname: 'Flexo',
+ id: 44,
+ lastname: 'Rodríguez',
+ street: ['3000 57th Street']
+ }
+ ]
+ }
+ },
+ error: false,
+ loading: false
+ });
+
+ const tree = createTestInstance();
+
+ act(() => {
+ tree.update();
+ });
+
+ const { root } = tree;
+ const { talonProps } = root.findByType('i').props;
+ expect(talonProps.selectedAddress).toBe(3);
+});
+
+describe('callbacks update and return state', () => {
+ const tree = createTestInstance();
+ const { root } = tree;
+ const { talonProps } = root.findByType('i').props;
+
+ test('handleEditAddress', () => {
+ const [, { toggleDrawer }] = useAppContext();
+ const { handleEditAddress } = talonProps;
+
+ act(() => {
+ handleEditAddress('activeAddress');
+ });
+
+ const { talonProps: newTalonProps } = root.findByType('i').props;
+
+ expect(toggleDrawer).toHaveBeenCalled();
+ expect(newTalonProps.activeAddress).toBe('activeAddress');
+ });
+
+ test('handleAddAddress', () => {
+ const [, { toggleDrawer }] = useAppContext();
+ const { handleAddAddress } = talonProps;
+
+ act(() => {
+ handleAddAddress();
+ });
+
+ const { talonProps: newTalonProps } = root.findByType('i').props;
+
+ expect(toggleDrawer).toHaveBeenCalled();
+ expect(newTalonProps.activeAddress).toBeUndefined();
+ });
+
+ test('handleSelectAddress', () => {
+ const { handleSelectAddress } = talonProps;
+
+ act(() => {
+ handleSelectAddress(318);
+ });
+
+ const { talonProps: newTalonProps } = root.findByType('i').props;
+ expect(newTalonProps.selectedAddress).toBe(318);
+ });
+
+ test('handleApplyAddress', async () => {
+ const { handleApplyAddress } = talonProps;
+
+ await act(() => {
+ handleApplyAddress();
+ });
+
+ expect(mockSetCustomerAddressOnCart).toHaveBeenCalled();
+ expect(mockSetCustomerAddressOnCart.mock.calls[0][0]).toMatchSnapshot();
+ expect(toggleActiveContent).toHaveBeenCalled();
+ });
+});
diff --git a/packages/peregrine/lib/talons/CheckoutPage/AddressBook/__tests__/useAddressCard.spec.js b/packages/peregrine/lib/talons/CheckoutPage/AddressBook/__tests__/useAddressCard.spec.js
new file mode 100644
index 0000000000..476f3c6006
--- /dev/null
+++ b/packages/peregrine/lib/talons/CheckoutPage/AddressBook/__tests__/useAddressCard.spec.js
@@ -0,0 +1,86 @@
+import React from 'react';
+import { act } from 'react-test-renderer';
+
+import createTestInstance from '../../../../util/createTestInstance';
+import { useAddressCard } from '../useAddressCard';
+
+const address = {
+ country_code: 'US',
+ email: 'fry@planet.express',
+ firstname: 'Philip',
+ id: 66,
+ region: {
+ region_id: 22
+ }
+};
+const onEdit = jest.fn();
+const onSelection = jest.fn();
+
+const mockProps = {
+ address,
+ onEdit,
+ onSelection
+};
+
+const Component = props => {
+ const talonProps = useAddressCard(props);
+ return ;
+};
+
+test('returns correct shape', () => {
+ const tree = createTestInstance();
+ const { root } = tree;
+ const { talonProps } = root.findByType('i').props;
+
+ expect(talonProps).toMatchSnapshot();
+});
+
+test('returns correct value for update animation', () => {
+ const tree = createTestInstance();
+ const { root } = tree;
+ const { talonProps } = root.findByType('i').props;
+
+ expect(talonProps.hasUpdate).toBe(false);
+
+ act(() => {
+ tree.update(
+
+ );
+ });
+
+ const { talonProps: newTalonProps } = root.findByType('i').props;
+
+ expect(newTalonProps.hasUpdate).toBe(true);
+});
+
+describe('event handlers fire callbacks', () => {
+ const tree = createTestInstance();
+ const { root } = tree;
+ const { talonProps } = root.findByType('i').props;
+
+ test('handleClick', () => {
+ const { handleClick } = talonProps;
+ handleClick();
+ expect(onSelection).toHaveBeenCalledWith(66);
+ });
+
+ test('handleKeyPress', () => {
+ const { handleKeyPress } = talonProps;
+
+ handleKeyPress({ key: 'Tab' });
+ expect(onSelection).not.toBeCalled();
+
+ handleKeyPress({ key: 'Enter' });
+ expect(onSelection).toHaveBeenCalledWith(66);
+ });
+
+ test('handleEditAddress', () => {
+ const { handleEditAddress } = talonProps;
+ handleEditAddress();
+ expect(onEdit).toHaveBeenCalled();
+ expect(onEdit.mock.calls[0][0]).toMatchSnapshot();
+ });
+});
diff --git a/packages/peregrine/lib/talons/CheckoutPage/AddressBook/useAddressBook.js b/packages/peregrine/lib/talons/CheckoutPage/AddressBook/useAddressBook.js
new file mode 100644
index 0000000000..a317fa1630
--- /dev/null
+++ b/packages/peregrine/lib/talons/CheckoutPage/AddressBook/useAddressBook.js
@@ -0,0 +1,149 @@
+import { useCallback, useEffect, useState, useRef } from 'react';
+import { useMutation, useQuery } from '@apollo/react-hooks';
+
+import { useAppContext } from '../../../context/app';
+import { useCartContext } from '../../../context/cart';
+
+export const useAddressBook = props => {
+ const {
+ mutations: { setCustomerAddressOnCartMutation },
+ queries: { getCustomerAddressesQuery, getCustomerCartAddressQuery },
+ toggleActiveContent
+ } = props;
+
+ const [, { toggleDrawer }] = useAppContext();
+ const [{ cartId }] = useCartContext();
+
+ const addressCount = useRef();
+ const [activeAddress, setActiveAddress] = useState();
+ const [selectedAddress, setSelectedAddress] = useState();
+
+ const [
+ setCustomerAddressOnCart,
+ { loading: setCustomerAddressOnCartLoading }
+ ] = useMutation(setCustomerAddressOnCartMutation);
+
+ const {
+ data: customerAddressesData,
+ error: customerAddressesError,
+ loading: customerAddressesLoading
+ } = useQuery(getCustomerAddressesQuery);
+
+ const {
+ data: customerCartAddressData,
+ error: customerCartAddressError,
+ loading: customerCartAddressLoading
+ } = useQuery(getCustomerCartAddressQuery);
+
+ useEffect(() => {
+ if (customerAddressesError) {
+ console.error(customerAddressesError);
+ }
+
+ if (customerCartAddressError) {
+ console.error(customerCartAddressError);
+ }
+ }, [customerAddressesError, customerCartAddressError]);
+
+ const isLoading =
+ customerAddressesLoading ||
+ customerCartAddressLoading ||
+ setCustomerAddressOnCartLoading;
+
+ const customerAddresses =
+ (customerAddressesData && customerAddressesData.customer.addresses) ||
+ [];
+
+ useEffect(() => {
+ if (customerAddresses.length !== addressCount.current) {
+ // Auto-select newly added address when count changes
+ if (addressCount.current) {
+ const newestAddress =
+ customerAddresses[customerAddresses.length - 1];
+ setSelectedAddress(newestAddress.id);
+ }
+
+ addressCount.current = customerAddresses.length;
+ }
+ }, [customerAddresses]);
+
+ const handleEditAddress = useCallback(
+ address => {
+ setActiveAddress(address);
+ toggleDrawer('shippingInformation.edit');
+ },
+ [toggleDrawer]
+ );
+
+ const handleAddAddress = useCallback(() => {
+ handleEditAddress();
+ }, [handleEditAddress]);
+
+ const handleSelectAddress = useCallback(addressId => {
+ setSelectedAddress(addressId);
+ }, []);
+
+ // GraphQL doesn't return which customer address is selected, so perform
+ // a simple search to initialize this selected address value.
+ if (
+ customerAddresses.length &&
+ customerCartAddressData &&
+ !selectedAddress
+ ) {
+ const { customerCart } = customerCartAddressData;
+ const { shipping_addresses: shippingAddresses } = customerCart;
+ if (shippingAddresses.length) {
+ const primaryCartAddress = shippingAddresses[0];
+
+ const foundSelectedAddress = customerAddresses.find(
+ customerAddress =>
+ customerAddress.street[0] ===
+ primaryCartAddress.street[0] &&
+ customerAddress.firstname ===
+ primaryCartAddress.firstname &&
+ customerAddress.lastname === primaryCartAddress.lastname
+ );
+
+ if (foundSelectedAddress) {
+ setSelectedAddress(foundSelectedAddress.id);
+ }
+ }
+ }
+
+ const handleApplyAddress = useCallback(async () => {
+ try {
+ await setCustomerAddressOnCart({
+ variables: {
+ cartId,
+ addressId: selectedAddress
+ }
+ });
+ } catch (error) {
+ console.error(error);
+ }
+
+ toggleActiveContent();
+ }, [
+ cartId,
+ selectedAddress,
+ setCustomerAddressOnCart,
+ toggleActiveContent
+ ]);
+
+ const handleCancel = useCallback(() => {
+ setSelectedAddress();
+ toggleActiveContent();
+ }, [toggleActiveContent]);
+
+ return {
+ activeAddress,
+ customerAddresses,
+ isLoading,
+ handleAddAddress,
+ handleApplyAddress,
+ handleCancel,
+ handleSelectAddress,
+ handleEditAddress,
+ selectedAddress
+ };
+};
diff --git a/packages/peregrine/lib/talons/CheckoutPage/AddressBook/useAddressCard.js b/packages/peregrine/lib/talons/CheckoutPage/AddressBook/useAddressCard.js
new file mode 100644
index 0000000000..74dec54dd5
--- /dev/null
+++ b/packages/peregrine/lib/talons/CheckoutPage/AddressBook/useAddressCard.js
@@ -0,0 +1,68 @@
+import { useCallback, useEffect, useMemo, useState, useRef } from 'react';
+
+export const useAddressCard = props => {
+ const { address, onEdit, onSelection } = props;
+ const { id: addressId } = address;
+
+ const [hasUpdate, setHasUpdate] = useState(false);
+ const hasRendered = useRef(false);
+
+ useEffect(() => {
+ let updateTimer;
+ if (address !== undefined) {
+ if (hasRendered.current) {
+ setHasUpdate(true);
+ updateTimer = setTimeout(() => {
+ setHasUpdate(false);
+ }, 2000);
+ } else {
+ hasRendered.current = true;
+ }
+ }
+
+ return () => {
+ if (updateTimer) {
+ clearTimeout(updateTimer);
+ }
+ };
+ }, [hasRendered, address]);
+
+ const addressForEdit = useMemo(() => {
+ const { country_code: countryCode, region, ...addressRest } = address;
+ const { region_id: regionId } = region;
+
+ return {
+ ...addressRest,
+ country: {
+ code: countryCode
+ },
+ region: {
+ id: regionId
+ }
+ };
+ }, [address]);
+
+ const handleClick = useCallback(() => {
+ onSelection(addressId);
+ }, [addressId, onSelection]);
+
+ const handleKeyPress = useCallback(
+ event => {
+ if (event.key === 'Enter') {
+ onSelection(addressId);
+ }
+ },
+ [addressId, onSelection]
+ );
+
+ const handleEditAddress = useCallback(() => {
+ onEdit(addressForEdit);
+ }, [addressForEdit, onEdit]);
+
+ return {
+ handleClick,
+ handleEditAddress,
+ handleKeyPress,
+ hasUpdate
+ };
+};
diff --git a/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/useCreateAccount.js b/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/useCreateAccount.js
index 164dd61e70..c7b806452b 100644
--- a/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/useCreateAccount.js
+++ b/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/useCreateAccount.js
@@ -4,6 +4,7 @@ import { useUserContext } from '@magento/peregrine/lib/context/user';
import { useCartContext } from '@magento/peregrine/lib/context/cart';
import { useAwaitQuery } from '@magento/peregrine/lib/hooks/useAwaitQuery';
import { clearCartDataFromCache } from '../../../Apollo/clearCartDataFromCache';
+import { clearCustomerDataFromCache } from '../../../Apollo/clearCustomerDataFromCache';
/**
* Returns props necessary to render CreateAccount component. In particular this
@@ -98,6 +99,7 @@ export const useCreateAccount = props => {
await removeCart();
await clearCartDataFromCache(apolloClient);
+ await clearCustomerDataFromCache(apolloClient);
await createCart({
fetchCartId
diff --git a/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/AddressForm/__tests__/__snapshots__/useCustomerForm.spec.js.snap b/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/AddressForm/__tests__/__snapshots__/useCustomerForm.spec.js.snap
new file mode 100644
index 0000000000..d0bfe0a437
--- /dev/null
+++ b/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/AddressForm/__tests__/__snapshots__/useCustomerForm.spec.js.snap
@@ -0,0 +1,93 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`return correct shape for initial address entry 1`] = `
+Object {
+ "handleCancel": [Function],
+ "handleSubmit": [Function],
+ "hasDefaultShipping": false,
+ "initialValues": Object {
+ "country": "US",
+ "email": "fry@planet.express",
+ "firstname": "Philip",
+ "lastname": "Fry",
+ "region": null,
+ },
+ "isLoading": false,
+ "isSaving": false,
+ "isUpdate": false,
+}
+`;
+
+exports[`return correct shape for new address and fire create mutation 1`] = `
+Object {
+ "handleCancel": [Function],
+ "handleSubmit": [Function],
+ "hasDefaultShipping": true,
+ "initialValues": Object {
+ "country": "US",
+ "region": null,
+ },
+ "isLoading": true,
+ "isSaving": false,
+ "isUpdate": false,
+}
+`;
+
+exports[`return correct shape for new address and fire create mutation 2`] = `
+Object {
+ "refetchQueries": Array [
+ Object {
+ "query": "getCustomerAddressesQuery",
+ },
+ Object {
+ "query": "getCustomerAddressesQuery",
+ },
+ ],
+ "variables": Object {
+ "address": Object {
+ "country_code": "US",
+ "firstname": "Philip",
+ "region": Object {
+ "region_id": 2,
+ },
+ },
+ },
+}
+`;
+
+exports[`return correct shape for update address and fire update mutation 1`] = `
+Object {
+ "handleCancel": [Function],
+ "handleSubmit": [Function],
+ "hasDefaultShipping": true,
+ "initialValues": Object {
+ "city": "New York",
+ "country": "US",
+ "id": 66,
+ "region": null,
+ },
+ "isLoading": false,
+ "isSaving": false,
+ "isUpdate": true,
+}
+`;
+
+exports[`return correct shape for update address and fire update mutation 2`] = `
+Object {
+ "refetchQueries": Array [
+ Object {
+ "query": "getCustomerAddressesQuery",
+ },
+ ],
+ "variables": Object {
+ "address": Object {
+ "country_code": "UK",
+ "firstname": "Bender",
+ "region": Object {
+ "region_id": 5,
+ },
+ },
+ "addressId": 66,
+ },
+}
+`;
diff --git a/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/EditForm/__tests__/__snapshots__/useEditForm.spec.js.snap b/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/AddressForm/__tests__/__snapshots__/useGuestForm.spec.js.snap
similarity index 100%
rename from packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/EditForm/__tests__/__snapshots__/useEditForm.spec.js.snap
rename to packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/AddressForm/__tests__/__snapshots__/useGuestForm.spec.js.snap
diff --git a/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/AddressForm/__tests__/useCustomerForm.spec.js b/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/AddressForm/__tests__/useCustomerForm.spec.js
new file mode 100644
index 0000000000..ddd81b29fa
--- /dev/null
+++ b/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/AddressForm/__tests__/useCustomerForm.spec.js
@@ -0,0 +1,179 @@
+import React from 'react';
+import { useMutation, useQuery } from '@apollo/react-hooks';
+
+import createTestInstance from '../../../../../util/createTestInstance';
+import { useCustomerForm } from '../useCustomerForm';
+
+const mockCreateCustomerAddress = jest.fn();
+const mockUpdateCustomerAddress = jest.fn();
+
+jest.mock('@apollo/react-hooks', () => ({
+ useMutation: jest.fn().mockImplementation(mutation => {
+ if (mutation === 'createCustomerAddressMutation')
+ return [mockCreateCustomerAddress, { loading: false }];
+
+ if (mutation === 'updateCustomerAddressMutation')
+ return [mockUpdateCustomerAddress, { loading: false }];
+
+ return;
+ }),
+ useQuery: jest.fn().mockReturnValue({
+ data: {
+ customer: {
+ default_shipping: null,
+ email: 'fry@planet.express',
+ firstname: 'Philip',
+ lastname: 'Fry'
+ }
+ },
+ error: null,
+ loading: false
+ })
+}));
+
+const Component = props => {
+ const talonProps = useCustomerForm(props);
+ return ;
+};
+
+const afterSubmit = jest.fn();
+const onCancel = jest.fn();
+const shippingData = {
+ country: {
+ code: 'US'
+ },
+ region: {
+ id: null
+ }
+};
+
+const mockProps = {
+ afterSubmit,
+ mutations: {
+ createCustomerAddressMutation: 'createCustomerAddressMutation',
+ updateCustomerAddressMutation: 'updateCustomerAddressMutation'
+ },
+ onCancel,
+ queries: {
+ getCustomerQuery: 'getCustomerQuery',
+ getCustomerAddressesQuery: 'getCustomerAddressesQuery',
+ getDefaultShippingQuery: 'getCustomerAddressesQuery'
+ },
+ shippingData
+};
+
+test('return correct shape for initial address entry', () => {
+ const tree = createTestInstance();
+ const { root } = tree;
+ const { talonProps } = root.findByType('i').props;
+
+ expect(talonProps).toMatchSnapshot();
+});
+
+test('return correct shape for new address and fire create mutation', async () => {
+ useQuery.mockReturnValueOnce({
+ data: {
+ customer: {
+ default_shipping: 5,
+ email: 'fry@planet.express',
+ firstname: 'Philip',
+ lastname: 'Fry'
+ }
+ },
+ error: null,
+ loading: true
+ });
+
+ const tree = createTestInstance();
+ const { root } = tree;
+ const { talonProps } = root.findByType('i').props;
+
+ expect(talonProps).toMatchSnapshot();
+
+ const { handleSubmit } = talonProps;
+
+ await handleSubmit({
+ country: 'US',
+ email: 'fry@planet.express',
+ firstname: 'Philip',
+ region: 2
+ });
+
+ expect(mockCreateCustomerAddress).toHaveBeenCalled();
+ expect(mockCreateCustomerAddress.mock.calls[0][0]).toMatchSnapshot();
+});
+
+test('return correct shape for update address and fire update mutation', async () => {
+ useQuery.mockReturnValueOnce({
+ data: {
+ customer: {
+ default_shipping: 5,
+ email: 'fry@planet.express',
+ firstname: 'Philip',
+ lastname: 'Fry'
+ }
+ },
+ error: null,
+ loading: false
+ });
+
+ const tree = createTestInstance(
+
+ );
+ const { root } = tree;
+ const { talonProps } = root.findByType('i').props;
+
+ expect(talonProps).toMatchSnapshot();
+
+ const { handleSubmit } = talonProps;
+
+ await handleSubmit({
+ country: 'UK',
+ email: 'bender@planet.express',
+ firstname: 'Bender',
+ region: 5
+ });
+
+ expect(mockUpdateCustomerAddress).toHaveBeenCalled();
+ expect(mockUpdateCustomerAddress.mock.calls[0][0]).toMatchSnapshot();
+ expect(afterSubmit).toHaveBeenCalled();
+});
+
+test('update isSaving while mutations are in flight', () => {
+ useMutation.mockReturnValueOnce([
+ jest.fn(),
+ {
+ loading: true
+ }
+ ]);
+
+ const tree = createTestInstance(
+
+ );
+ const { root } = tree;
+ const { talonProps } = root.findByType('i').props;
+
+ expect(talonProps.isSaving).toBe(true);
+});
+
+test('handleCancel fires provided callback', () => {
+ const tree = createTestInstance(
+
+ );
+ const { root } = tree;
+ const { talonProps } = root.findByType('i').props;
+ const { handleCancel } = talonProps;
+
+ handleCancel();
+
+ expect(onCancel).toHaveBeenCalled();
+});
diff --git a/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/EditForm/__tests__/useEditForm.spec.js b/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/AddressForm/__tests__/useGuestForm.spec.js
similarity index 96%
rename from packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/EditForm/__tests__/useEditForm.spec.js
rename to packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/AddressForm/__tests__/useGuestForm.spec.js
index b9361a0c51..868b32bd08 100644
--- a/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/EditForm/__tests__/useEditForm.spec.js
+++ b/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/AddressForm/__tests__/useGuestForm.spec.js
@@ -3,7 +3,7 @@ import { act } from 'react-test-renderer';
import { useMutation } from '@apollo/react-hooks';
import createTestInstance from '../../../../../util/createTestInstance';
-import { useEditForm } from '../useEditForm';
+import { useGuestForm } from '../useGuestForm';
jest.mock('@apollo/react-hooks', () => ({
useMutation: jest
@@ -22,7 +22,7 @@ jest.mock('../../../../../context/cart', () => {
});
const Component = props => {
- const talonProps = useEditForm(props);
+ const talonProps = useGuestForm(props);
return ;
};
diff --git a/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/AddressForm/useCustomerForm.js b/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/AddressForm/useCustomerForm.js
new file mode 100644
index 0000000000..2a046407b1
--- /dev/null
+++ b/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/AddressForm/useCustomerForm.js
@@ -0,0 +1,137 @@
+import { useCallback, useEffect } from 'react';
+import { useMutation, useQuery } from '@apollo/react-hooks';
+
+export const useCustomerForm = props => {
+ const {
+ afterSubmit,
+ mutations: {
+ createCustomerAddressMutation,
+ updateCustomerAddressMutation
+ },
+ onCancel,
+ queries: {
+ getCustomerQuery,
+ getCustomerAddressesQuery,
+ getDefaultShippingQuery
+ },
+ shippingData
+ } = props;
+
+ const [
+ createCustomerAddress,
+ { loading: createCustomerAddressLoading }
+ ] = useMutation(createCustomerAddressMutation);
+
+ const [
+ updateCustomerAddress,
+ { loading: updateCustomerAddressLoading }
+ ] = useMutation(updateCustomerAddressMutation);
+
+ const {
+ error: getCustomerError,
+ data: customerData,
+ loading: getCustomerLoading
+ } = useQuery(getCustomerQuery);
+
+ useEffect(() => {
+ if (getCustomerError) {
+ console.error(getCustomerError);
+ }
+ }, [getCustomerError]);
+
+ const isSaving =
+ createCustomerAddressLoading || updateCustomerAddressLoading;
+
+ // Simple heuristic to indicate form was submitted prior to this render
+ const isUpdate = !!shippingData.city;
+
+ const { country, region } = shippingData;
+ const { code: countryCode } = country;
+ const { id: regionId } = region;
+
+ let initialValues = {
+ ...shippingData,
+ country: countryCode,
+ region: regionId && regionId.toString()
+ };
+
+ const hasDefaultShipping =
+ !!customerData && !!customerData.customer.default_shipping;
+
+ // For first time creation pre-fill the form with Customer data
+ if (!isUpdate && !getCustomerLoading && !hasDefaultShipping) {
+ const { customer } = customerData;
+ const { email, firstname, lastname } = customer;
+ const defaultUserData = { email, firstname, lastname };
+ initialValues = {
+ ...initialValues,
+ ...defaultUserData
+ };
+ }
+
+ const handleSubmit = useCallback(
+ async formValues => {
+ // eslint-disable-next-line no-unused-vars
+ const { country, email, region, ...address } = formValues;
+ try {
+ const customerAddress = {
+ ...address,
+ country_code: country,
+ region: {
+ region_id: region
+ }
+ };
+
+ if (isUpdate) {
+ const { id: addressId } = shippingData;
+ await updateCustomerAddress({
+ variables: {
+ addressId,
+ address: customerAddress
+ },
+ refetchQueries: [{ query: getCustomerAddressesQuery }]
+ });
+ } else {
+ await createCustomerAddress({
+ variables: {
+ address: customerAddress
+ },
+ refetchQueries: [
+ { query: getCustomerAddressesQuery },
+ { query: getDefaultShippingQuery }
+ ]
+ });
+ }
+ } catch (error) {
+ console.error(error);
+ }
+
+ if (afterSubmit) {
+ afterSubmit();
+ }
+ },
+ [
+ afterSubmit,
+ createCustomerAddress,
+ getCustomerAddressesQuery,
+ getDefaultShippingQuery,
+ isUpdate,
+ shippingData,
+ updateCustomerAddress
+ ]
+ );
+
+ const handleCancel = useCallback(() => {
+ onCancel();
+ }, [onCancel]);
+
+ return {
+ handleCancel,
+ handleSubmit,
+ hasDefaultShipping,
+ initialValues,
+ isLoading: getCustomerLoading,
+ isSaving,
+ isUpdate
+ };
+};
diff --git a/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/EditForm/useEditForm.js b/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/AddressForm/useGuestForm.js
similarity index 81%
rename from packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/EditForm/useEditForm.js
rename to packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/AddressForm/useGuestForm.js
index c2e31dc9c8..eb2bec3382 100644
--- a/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/EditForm/useEditForm.js
+++ b/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/AddressForm/useGuestForm.js
@@ -3,17 +3,18 @@ import { useMutation } from '@apollo/react-hooks';
import { useCartContext } from '../../../../context/cart';
-export const useEditForm = props => {
+export const useGuestForm = props => {
const {
afterSubmit,
- mutations: { setShippingInformationMutation },
+ mutations: { setGuestShippingMutation },
onCancel,
shippingData
} = props;
const [{ cartId }] = useCartContext();
- const [setShippingInformation, { called, loading }] = useMutation(
- setShippingInformationMutation
+
+ const [setGuestShipping, { loading }] = useMutation(
+ setGuestShippingMutation
);
const { country, region } = shippingData;
@@ -33,7 +34,7 @@ export const useEditForm = props => {
async formValues => {
const { country, email, ...address } = formValues;
try {
- await setShippingInformation({
+ await setGuestShipping({
variables: {
cartId,
email,
@@ -51,7 +52,7 @@ export const useEditForm = props => {
afterSubmit();
}
},
- [afterSubmit, cartId, setShippingInformation]
+ [afterSubmit, cartId, setGuestShipping]
);
const handleCancel = useCallback(() => {
@@ -62,7 +63,7 @@ export const useEditForm = props => {
handleCancel,
handleSubmit,
initialValues,
- isSaving: called && loading,
+ isSaving: loading,
isUpdate
};
};
diff --git a/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/__tests__/__snapshots__/useShippingInformation.spec.js.snap b/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/__tests__/__snapshots__/useShippingInformation.spec.js.snap
index 19a8601266..cd15479a5a 100644
--- a/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/__tests__/__snapshots__/useShippingInformation.spec.js.snap
+++ b/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/__tests__/__snapshots__/useShippingInformation.spec.js.snap
@@ -4,7 +4,9 @@ exports[`return correct shape with mock data from estimate 1`] = `
Object {
"doneEditing": false,
"handleEditShipping": [Function],
- "loading": false,
+ "hasUpdate": false,
+ "isLoading": false,
+ "isSignedIn": false,
"shippingData": Object {
"city": "",
"country": "USA",
@@ -25,7 +27,9 @@ exports[`return correct shape with no data filled in 1`] = `
Object {
"doneEditing": false,
"handleEditShipping": [Function],
- "loading": false,
+ "hasUpdate": false,
+ "isLoading": false,
+ "isSignedIn": false,
"shippingData": undefined,
}
`;
@@ -34,7 +38,9 @@ exports[`return correct shape with real data 1`] = `
Object {
"doneEditing": true,
"handleEditShipping": [Function],
- "loading": false,
+ "hasUpdate": false,
+ "isLoading": false,
+ "isSignedIn": false,
"shippingData": Object {
"city": "Manhattan",
"country": "USA",
@@ -56,7 +62,9 @@ exports[`return correct shape without cart id 1`] = `
Object {
"doneEditing": false,
"handleEditShipping": [Function],
- "loading": true,
+ "hasUpdate": false,
+ "isLoading": false,
+ "isSignedIn": false,
"shippingData": undefined,
}
`;
diff --git a/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/__tests__/useShippingInformation.spec.js b/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/__tests__/useShippingInformation.spec.js
index a46efc6981..449131d8a4 100644
--- a/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/__tests__/useShippingInformation.spec.js
+++ b/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/__tests__/useShippingInformation.spec.js
@@ -1,22 +1,35 @@
import React from 'react';
-import { useLazyQuery } from '@apollo/react-hooks';
+import { act } from 'react-test-renderer';
+import { useMutation } from '@apollo/react-hooks';
import { useAppContext } from '../../../../context/app';
import { useCartContext } from '../../../../context/cart';
import createTestInstance from '../../../../util/createTestInstance';
import { useShippingInformation } from '../useShippingInformation';
+import { useUserContext } from '../../../../context/user';
+
+const mockGetShippingInformationResult = jest.fn().mockReturnValue({
+ data: null,
+ loading: false
+});
+
+const mockGetDefaultShippingResult = jest.fn().mockReturnValue({
+ data: null,
+ loading: false
+});
jest.mock('@apollo/react-hooks', () => ({
- useLazyQuery: jest.fn().mockReturnValue([
- jest.fn(),
- {
- called: false,
- data: null,
- error: null,
- loading: false
- }
- ])
+ useQuery: jest.fn().mockImplementation(query => {
+ if (query === 'getShippingInformationQuery')
+ return mockGetShippingInformationResult();
+
+ if (query === 'getDefaultShippingQuery')
+ return mockGetDefaultShippingResult();
+
+ return;
+ }),
+ useMutation: jest.fn().mockReturnValue([jest.fn(), { loading: false }])
}));
jest.mock('../../../../context/app', () => {
@@ -39,15 +52,32 @@ jest.mock('../../../../context/cart', () => {
return { useCartContext };
});
+jest.mock('../../../../context/user', () => {
+ const state = {
+ isSignedIn: false
+ };
+ const api = {};
+ const useUserContext = jest.fn(() => [state, api]);
+
+ return { useUserContext };
+});
+
const Component = props => {
const talonProps = useShippingInformation(props);
return ;
};
+const mockProps = {
+ mutations: {},
+ onSave: jest.fn(),
+ queries: {
+ getDefaultShippingQuery: 'getDefaultShippingQuery',
+ getShippingInformationQuery: 'getShippingInformationQuery'
+ }
+};
+
test('return correct shape without cart id', () => {
- const tree = createTestInstance(
-
- );
+ const tree = createTestInstance();
const { root } = tree;
const { talonProps } = root.findByType('i').props;
@@ -55,24 +85,17 @@ test('return correct shape without cart id', () => {
});
test('return correct shape with no data filled in', () => {
- useLazyQuery.mockReturnValueOnce([
- jest.fn(),
- {
- called: true,
- data: {
- cart: {
- email: null,
- shipping_addresses: []
- }
- },
- error: null,
- loading: false
- }
- ]);
+ mockGetShippingInformationResult.mockReturnValueOnce({
+ data: {
+ cart: {
+ email: null,
+ shipping_addresses: []
+ }
+ },
+ loading: false
+ });
- const tree = createTestInstance(
-
- );
+ const tree = createTestInstance();
const { root } = tree;
const { talonProps } = root.findByType('i').props;
@@ -80,35 +103,28 @@ test('return correct shape with no data filled in', () => {
});
test('return correct shape with mock data from estimate', () => {
- useLazyQuery.mockReturnValueOnce([
- jest.fn(),
- {
- called: true,
- data: {
- cart: {
- email: null,
- shipping_addresses: [
- {
- city: 'city',
- country: 'USA',
- firstname: 'firstname',
- lastname: 'lastname',
- postcode: '10019',
- region: 'New York',
- street: ['street'],
- telephone: 'telephone'
- }
- ]
- }
- },
- error: null,
- loading: false
- }
- ]);
+ mockGetShippingInformationResult.mockReturnValueOnce({
+ data: {
+ cart: {
+ email: null,
+ shipping_addresses: [
+ {
+ city: 'city',
+ country: 'USA',
+ firstname: 'firstname',
+ lastname: 'lastname',
+ postcode: '10019',
+ region: 'New York',
+ street: ['street'],
+ telephone: 'telephone'
+ }
+ ]
+ }
+ },
+ loading: false
+ });
- const tree = createTestInstance(
-
- );
+ const tree = createTestInstance();
const { root } = tree;
const { talonProps } = root.findByType('i').props;
@@ -116,48 +132,56 @@ test('return correct shape with mock data from estimate', () => {
});
test('return correct shape with real data', () => {
- useLazyQuery.mockReturnValueOnce([
- jest.fn(),
- {
- called: true,
- data: {
- cart: {
- email: null,
- shipping_addresses: [
- {
- city: 'Manhattan',
- country: 'USA',
- email: 'fry@planet.express',
- firstname: 'Philip',
- lastname: 'Fry',
- postcode: '10019',
- region: 'New York',
- street: ['3000 57th Street', 'Suite 200'],
- telephone: '(123) 456-7890'
- }
- ]
- }
- },
- error: null,
- loading: false
- }
- ]);
+ mockGetShippingInformationResult.mockReturnValueOnce({
+ data: {
+ cart: {
+ email: null,
+ shipping_addresses: [
+ {
+ city: 'Manhattan',
+ country: 'USA',
+ email: 'fry@planet.express',
+ firstname: 'Philip',
+ lastname: 'Fry',
+ postcode: '10019',
+ region: 'New York',
+ street: ['3000 57th Street', 'Suite 200'],
+ telephone: '(123) 456-7890'
+ }
+ ]
+ }
+ },
+ loading: false
+ });
- const tree = createTestInstance(
-
- );
+ const tree = createTestInstance();
const { root } = tree;
const { talonProps } = root.findByType('i').props;
expect(talonProps).toMatchSnapshot();
});
-test('edit handler calls toggle drawer', () => {
+test('edit handler calls toggle drawer for guest', () => {
const [, { toggleDrawer }] = useAppContext();
useCartContext.mockReturnValueOnce([{}]);
+ const tree = createTestInstance();
+ const { root } = tree;
+ const { talonProps } = root.findByType('i').props;
+ const { handleEditShipping } = talonProps;
+
+ handleEditShipping();
+
+ expect(toggleDrawer).toHaveBeenCalled();
+});
+
+test('edit handler calls toggle active content for customer', () => {
+ useCartContext.mockReturnValueOnce([{}]);
+ useUserContext.mockReturnValueOnce([{ isSignedIn: true }]);
+
+ const toggleActiveContent = jest.fn();
const tree = createTestInstance(
-
+
);
const { root } = tree;
const { talonProps } = root.findByType('i').props;
@@ -165,5 +189,96 @@ test('edit handler calls toggle drawer', () => {
handleEditShipping();
- expect(toggleDrawer).toHaveBeenCalled();
+ expect(toggleActiveContent).toHaveBeenCalled();
+});
+
+test('customer default address is auto selected', () => {
+ mockGetShippingInformationResult.mockReturnValueOnce({
+ data: {
+ cart: {
+ email: null,
+ shipping_addresses: []
+ }
+ },
+ loading: false
+ });
+
+ mockGetDefaultShippingResult.mockReturnValueOnce({
+ data: {
+ customer: {
+ default_shipping: '1'
+ }
+ },
+ loading: false
+ });
+
+ const setDefaultAddressOnCart = jest.fn();
+ useMutation.mockReturnValueOnce([
+ setDefaultAddressOnCart,
+ { loading: false }
+ ]);
+
+ createTestInstance();
+
+ expect(setDefaultAddressOnCart).toHaveBeenCalled();
+});
+
+test('receives update on data change', () => {
+ mockGetShippingInformationResult.mockReturnValueOnce({
+ data: {
+ cart: {
+ email: null,
+ shipping_addresses: [
+ {
+ city: 'Manhattan',
+ country: 'USA',
+ email: 'fry@planet.express',
+ firstname: 'Philip',
+ lastname: 'Fry',
+ postcode: '10019',
+ region: 'New York',
+ street: ['3000 57th Street', 'Suite 200'],
+ telephone: '(123) 456-7890'
+ }
+ ]
+ }
+ },
+ loading: false
+ });
+
+ mockGetShippingInformationResult.mockReturnValueOnce({
+ data: {
+ cart: {
+ email: null,
+ shipping_addresses: [
+ {
+ city: 'Manhattan',
+ country: 'USA',
+ email: 'bender@planet.express',
+ firstname: 'Bender',
+ lastname: 'Rodríguez',
+ postcode: '10019',
+ region: 'New York',
+ street: ['00100 100 001 00100'],
+ telephone: '(555) 456-7890'
+ }
+ ]
+ }
+ },
+ loading: false
+ });
+
+ const tree = createTestInstance();
+ const { root } = tree;
+ const { talonProps } = root.findByType('i').props;
+ const { hasUpdate } = talonProps;
+
+ expect(hasUpdate).toBe(false);
+
+ act(() => {
+ tree.update();
+ });
+
+ const { talonProps: newTalonProps } = root.findByType('i').props;
+ expect(newTalonProps.hasUpdate).toBe(true);
});
diff --git a/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/useShippingInformation.js b/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/useShippingInformation.js
index acb05ee064..f71ed53452 100644
--- a/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/useShippingInformation.js
+++ b/packages/peregrine/lib/talons/CheckoutPage/ShippingInformation/useShippingInformation.js
@@ -1,37 +1,55 @@
-import { useCallback, useEffect, useMemo } from 'react';
-import { useLazyQuery } from '@apollo/react-hooks';
+import { useCallback, useEffect, useMemo, useState, useRef } from 'react';
+import { useMutation, useQuery } from '@apollo/react-hooks';
import { useAppContext } from '../../../context/app';
import { useCartContext } from '../../../context/cart';
+import { useUserContext } from '../../../context/user';
import { MOCKED_ADDRESS } from '../../CartPage/PriceAdjustments/ShippingMethods/useShippingForm';
export const useShippingInformation = props => {
const {
+ mutations: { setDefaultAddressOnCartMutation },
onSave,
- queries: { getShippingInformationQuery }
+ queries: { getDefaultShippingQuery, getShippingInformationQuery },
+ toggleActiveContent
} = props;
const [, { toggleDrawer }] = useAppContext();
const [{ cartId }] = useCartContext();
+ const [{ isSignedIn }] = useUserContext();
- const [fetchShippingInformation, { called, data, loading }] = useLazyQuery(
- getShippingInformationQuery
- );
+ const [hasUpdate, setHasUpdate] = useState(false);
+ const hasLoadedData = useRef(false);
- useEffect(() => {
- if (cartId) {
- fetchShippingInformation({
- variables: {
- cartId
- }
- });
+ const {
+ data: shippingInformationData,
+ loading: getShippingInformationLoading
+ } = useQuery(getShippingInformationQuery, {
+ skip: !cartId,
+ variables: {
+ cartId
}
- }, [cartId, fetchShippingInformation]);
+ });
+
+ const {
+ data: defaultShippingData,
+ loading: getDefaultShippingLoading
+ } = useQuery(getDefaultShippingQuery, { skip: !isSignedIn });
+
+ const [
+ setDefaultAddressOnCart,
+ { loading: setDefaultAddressLoading }
+ ] = useMutation(setDefaultAddressOnCartMutation);
+
+ const isLoading =
+ getShippingInformationLoading ||
+ getDefaultShippingLoading ||
+ setDefaultAddressLoading;
const shippingData = useMemo(() => {
let filteredData;
- if (data) {
- const { cart } = data;
+ if (shippingInformationData) {
+ const { cart } = shippingInformationData;
const { email, shipping_addresses: shippingAddresses } = cart;
if (shippingAddresses.length) {
const primaryAddress = shippingAddresses[0];
@@ -56,7 +74,7 @@ export const useShippingInformation = props => {
}
return filteredData;
- }, [data]);
+ }, [shippingInformationData]);
// Simple heuristic to check shipping data existed prior to this render.
// On first submission, when we have data, we should tell the checkout page
@@ -69,14 +87,66 @@ export const useShippingInformation = props => {
}
}, [doneEditing, onSave]);
+ useEffect(() => {
+ let updateTimer;
+ if (shippingData !== undefined) {
+ if (hasLoadedData.current) {
+ setHasUpdate(true);
+ updateTimer = setTimeout(() => {
+ setHasUpdate(false);
+ }, 2000);
+ } else {
+ hasLoadedData.current = true;
+ }
+ }
+
+ return () => {
+ if (updateTimer) {
+ clearTimeout(updateTimer);
+ }
+ };
+ }, [hasLoadedData, shippingData]);
+
+ useEffect(() => {
+ if (
+ shippingInformationData &&
+ !doneEditing &&
+ cartId &&
+ defaultShippingData
+ ) {
+ const { customer } = defaultShippingData;
+ const { default_shipping: defaultAddressId } = customer;
+ if (defaultAddressId) {
+ setDefaultAddressOnCart({
+ variables: {
+ cartId,
+ addressId: parseInt(defaultAddressId)
+ }
+ });
+ }
+ }
+ }, [
+ cartId,
+ doneEditing,
+ defaultShippingData,
+ setDefaultAddressOnCart,
+ shippingInformationData
+ ]);
+
const handleEditShipping = useCallback(() => {
- toggleDrawer('shippingInformation.edit');
- }, [toggleDrawer]);
+ if (isSignedIn) {
+ toggleActiveContent();
+ } else {
+ toggleDrawer('shippingInformation.edit');
+ }
+ }, [isSignedIn, toggleActiveContent, toggleDrawer]);
return {
doneEditing,
handleEditShipping,
- loading: !called || loading,
+ hasUpdate,
+ isLoading,
+ isSignedIn,
shippingData
};
};
diff --git a/packages/peregrine/lib/talons/CheckoutPage/useCheckoutPage.js b/packages/peregrine/lib/talons/CheckoutPage/useCheckoutPage.js
index 39d73599a8..87afca4f93 100644
--- a/packages/peregrine/lib/talons/CheckoutPage/useCheckoutPage.js
+++ b/packages/peregrine/lib/talons/CheckoutPage/useCheckoutPage.js
@@ -1,8 +1,9 @@
-import { useCallback, useEffect, useState } from 'react';
+import { useCallback, useState } from 'react';
import {
useApolloClient,
useLazyQuery,
- useMutation
+ useMutation,
+ useQuery
} from '@apollo/react-hooks';
import { useAppContext } from '../../context/app';
@@ -20,7 +21,11 @@ export const CHECKOUT_STEP = {
export const useCheckoutPage = props => {
const {
mutations: { createCartMutation, placeOrderMutation },
- queries: { getCheckoutDetailsQuery, getOrderDetailsQuery }
+ queries: {
+ getCheckoutDetailsQuery,
+ getCustomerQuery,
+ getOrderDetailsQuery
+ }
} = props;
const [reviewOrderButtonClicked, setReviewOrderButtonClicked] = useState(
@@ -29,6 +34,7 @@ export const useCheckoutPage = props => {
const apolloClient = useApolloClient();
const [isUpdating, setIsUpdating] = useState(false);
+ const [activeContent, setActiveContent] = useState('checkout');
const [checkoutStep, setCheckoutStep] = useState(
CHECKOUT_STEP.SHIPPING_ADDRESS
);
@@ -56,10 +62,29 @@ export const useCheckoutPage = props => {
fetchPolicy: 'network-only'
});
- const [
- getCheckoutDetails,
- { data: checkoutData, called: checkoutCalled, loading: checkoutLoading }
- ] = useLazyQuery(getCheckoutDetailsQuery);
+ const { data: customerData, loading: customerLoading } = useQuery(
+ getCustomerQuery,
+ { skip: !isSignedIn }
+ );
+
+ const {
+ data: checkoutData,
+ called: checkoutCalled,
+ loading: checkoutLoading
+ } = useQuery(getCheckoutDetailsQuery, {
+ skip: !cartId,
+ variables: {
+ cartId
+ }
+ });
+
+ const customer = customerData && customerData.customer;
+
+ const toggleActiveContent = useCallback(() => {
+ const nextContentState =
+ activeContent === 'checkout' ? 'addressBook' : 'checkout';
+ setActiveContent(nextContentState);
+ }, [activeContent]);
const handleSignIn = useCallback(() => {
// TODO: set navigation state to "SIGN_IN". useNavigation:showSignIn doesn't work.
@@ -133,25 +158,20 @@ export const useCheckoutPage = props => {
removeCart
]);
- useEffect(() => {
- if (cartId) {
- getCheckoutDetails({
- variables: {
- cartId
- }
- });
- }
- }, [cartId, getCheckoutDetails]);
-
return {
+ activeContent,
checkoutStep,
+ customer,
error: placeOrderError,
handleSignIn,
handlePlaceOrder,
hasError: !!placeOrderError,
isCartEmpty: !(checkoutData && checkoutData.cart.total_quantity),
isGuestCheckout: !isSignedIn,
- isLoading: !checkoutCalled || (checkoutCalled && checkoutLoading),
+ isLoading:
+ !checkoutCalled ||
+ (checkoutCalled && checkoutLoading) ||
+ customerLoading,
isUpdating,
orderDetailsData,
orderDetailsLoading,
@@ -166,6 +186,7 @@ export const useCheckoutPage = props => {
setPaymentInformationDone,
resetReviewOrderButtonClicked,
handleReviewOrder,
- reviewOrderButtonClicked
+ reviewOrderButtonClicked,
+ toggleActiveContent
};
};
diff --git a/packages/peregrine/lib/talons/CreateAccount/useCreateAccount.js b/packages/peregrine/lib/talons/CreateAccount/useCreateAccount.js
index 6ef1693e46..c77bf964bb 100644
--- a/packages/peregrine/lib/talons/CreateAccount/useCreateAccount.js
+++ b/packages/peregrine/lib/talons/CreateAccount/useCreateAccount.js
@@ -4,6 +4,7 @@ import { useUserContext } from '@magento/peregrine/lib/context/user';
import { useCartContext } from '@magento/peregrine/lib/context/cart';
import { useAwaitQuery } from '@magento/peregrine/lib/hooks/useAwaitQuery';
import { clearCartDataFromCache } from '../../Apollo/clearCartDataFromCache';
+import { clearCustomerDataFromCache } from '../../Apollo/clearCustomerDataFromCache';
/**
* Returns props necessary to render CreateAccount component. In particular this
@@ -96,6 +97,7 @@ export const useCreateAccount = props => {
await removeCart();
await clearCartDataFromCache(apolloClient);
+ await clearCustomerDataFromCache(apolloClient);
await createCart({
fetchCartId
diff --git a/packages/peregrine/lib/talons/Region/__tests__/__snapshots__/useRegion.spec.js.snap b/packages/peregrine/lib/talons/Region/__tests__/__snapshots__/useRegion.spec.js.snap
index 22f4a99b96..2c115e9f80 100644
--- a/packages/peregrine/lib/talons/Region/__tests__/__snapshots__/useRegion.spec.js.snap
+++ b/packages/peregrine/lib/talons/Region/__tests__/__snapshots__/useRegion.spec.js.snap
@@ -28,3 +28,26 @@ Object {
],
}
`;
+
+exports[`returns formatted regions with id as the key 1`] = `
+Object {
+ "regions": Array [
+ Object {
+ "disabled": true,
+ "hidden": true,
+ "label": "",
+ "value": "",
+ },
+ Object {
+ "key": 1,
+ "label": "New York",
+ "value": 1,
+ },
+ Object {
+ "key": 2,
+ "label": "Texas",
+ "value": 2,
+ },
+ ],
+}
+`;
diff --git a/packages/peregrine/lib/talons/Region/__tests__/useRegion.spec.js b/packages/peregrine/lib/talons/Region/__tests__/useRegion.spec.js
index c143f600d9..4a2a716584 100644
--- a/packages/peregrine/lib/talons/Region/__tests__/useRegion.spec.js
+++ b/packages/peregrine/lib/talons/Region/__tests__/useRegion.spec.js
@@ -34,6 +34,11 @@ test('returns formatted regions', () => {
expect(talonProps).toMatchSnapshot();
});
+test('returns formatted regions with id as the key', () => {
+ const talonProps = useRegion({ ...props, optionValueKey: 'id' });
+ expect(talonProps).toMatchSnapshot();
+});
+
test('returns empty array if no available regions', () => {
useQuery.mockReturnValueOnce({
data: {
diff --git a/packages/peregrine/lib/talons/Region/useRegion.js b/packages/peregrine/lib/talons/Region/useRegion.js
index 1d106549fd..910df95c78 100644
--- a/packages/peregrine/lib/talons/Region/useRegion.js
+++ b/packages/peregrine/lib/talons/Region/useRegion.js
@@ -4,6 +4,7 @@ import { useFieldState } from 'informed';
export const useRegion = props => {
const {
countryCodeField = 'country',
+ optionValueKey = 'code',
queries: { getRegionsQuery }
} = props;
@@ -22,7 +23,7 @@ export const useRegion = props => {
formattedRegionsData = availableRegions.map(region => ({
key: region.id,
label: region.name,
- value: region.code
+ value: region[optionValueKey]
}));
formattedRegionsData.unshift({
disabled: true,
diff --git a/packages/peregrine/lib/talons/SignIn/useSignIn.js b/packages/peregrine/lib/talons/SignIn/useSignIn.js
index 38517aa29c..ecd6e11bd6 100644
--- a/packages/peregrine/lib/talons/SignIn/useSignIn.js
+++ b/packages/peregrine/lib/talons/SignIn/useSignIn.js
@@ -4,6 +4,7 @@ import { useApolloClient, useMutation } from '@apollo/react-hooks';
import { useCartContext } from '../../context/cart';
import { useAwaitQuery } from '../../hooks/useAwaitQuery';
import { clearCartDataFromCache } from '../../Apollo/clearCartDataFromCache';
+import { clearCustomerDataFromCache } from '../../Apollo/clearCustomerDataFromCache';
export const useSignIn = props => {
const {
@@ -62,6 +63,7 @@ export const useSignIn = props => {
await removeCart();
await clearCartDataFromCache(apolloClient);
+ await clearCustomerDataFromCache(apolloClient);
await createCart({
fetchCartId
diff --git a/packages/venia-ui/lib/components/Checkbox/checkbox.css b/packages/venia-ui/lib/components/Checkbox/checkbox.css
index 1a80211564..af256957f5 100644
--- a/packages/venia-ui/lib/components/Checkbox/checkbox.css
+++ b/packages/venia-ui/lib/components/Checkbox/checkbox.css
@@ -19,6 +19,7 @@
grid-row: 1 / span 1;
height: 1.25rem;
justify-content: center;
+ pointer-events: none;
width: 1.25rem;
}
@@ -26,6 +27,7 @@
background: none;
border: 1px solid rgb(var(--venia-text));
border-radius: 2px;
+ cursor: pointer;
display: inline-flex;
grid-column: 1 / span 1;
grid-row: 1 / span 1;
@@ -35,6 +37,10 @@
-webkit-appearance: none;
}
+.input:disabled {
+ cursor: default;
+}
+
.input:focus {
border-color: rgb(var(--venia-teal));
box-shadow: 0 0 0 2px rgb(var(--venia-teal-light)),
diff --git a/packages/venia-ui/lib/components/CheckoutPage/AddressBook/__tests__/__snapshots__/addressBook.spec.js.snap b/packages/venia-ui/lib/components/CheckoutPage/AddressBook/__tests__/__snapshots__/addressBook.spec.js.snap
new file mode 100644
index 0000000000..a419e69140
--- /dev/null
+++ b/packages/venia-ui/lib/components/CheckoutPage/AddressBook/__tests__/__snapshots__/addressBook.spec.js.snap
@@ -0,0 +1,145 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`render active state 1`] = `
+Array [
+
+
+ Change Shipping Information
+
+
+
+
+
+
+
+
+
+
+
+
,
+ ,
+]
+`;
+
+exports[`render hidden state with disabled buttons 1`] = `
+Array [
+
+
+ Change Shipping Information
+
+
+
+
+
+
+
+
+
,
+ ,
+]
+`;
diff --git a/packages/venia-ui/lib/components/CheckoutPage/AddressBook/__tests__/__snapshots__/addressCard.spec.js.snap b/packages/venia-ui/lib/components/CheckoutPage/AddressBook/__tests__/__snapshots__/addressCard.spec.js.snap
new file mode 100644
index 0000000000..88eff42981
--- /dev/null
+++ b/packages/venia-ui/lib/components/CheckoutPage/AddressBook/__tests__/__snapshots__/addressCard.spec.js.snap
@@ -0,0 +1,136 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders base card state 1`] = `
+
+
+ Default
+
+
+ Philip Fry
+
+
+ 3000 57th Street
+
+
+ Suite 200
+
+
+ Manhattan, NY 10019 US
+
+
+`;
+
+exports[`renders selected card state 1`] = `
+
+
+
+ Philip Fry
+
+
+ 3000 57th Street
+
+
+ Suite 200
+
+
+ Manhattan, NY 10019 US
+
+
+`;
+
+exports[`renders updated card state 1`] = `
+
+
+
+ Default
+
+
+ Philip Fry
+
+
+ 3000 57th Street
+
+
+ Suite 200
+
+
+ Manhattan, NY 10019 US
+
+
+`;
diff --git a/packages/venia-ui/lib/components/CheckoutPage/AddressBook/__tests__/addressBook.spec.js b/packages/venia-ui/lib/components/CheckoutPage/AddressBook/__tests__/addressBook.spec.js
new file mode 100644
index 0000000000..bff2041a85
--- /dev/null
+++ b/packages/venia-ui/lib/components/CheckoutPage/AddressBook/__tests__/addressBook.spec.js
@@ -0,0 +1,56 @@
+import React from 'react';
+import { createTestInstance } from '@magento/peregrine';
+import { useAddressBook } from '@magento/peregrine/lib/talons/CheckoutPage/AddressBook/useAddressBook';
+
+import AddressBook from '../addressBook';
+
+jest.mock(
+ '@magento/peregrine/lib/talons/CheckoutPage/AddressBook/useAddressBook'
+);
+jest.mock('../../../../classify');
+jest.mock('../../ShippingInformation/editModal', () => 'EditModal');
+jest.mock('../addressCard', () => 'AddressCard');
+
+test('render active state', () => {
+ useAddressBook.mockReturnValueOnce({
+ activeAddress: 'activeAddress',
+ customerAddresses: [
+ { id: 1, default_shipping: false, name: 'Philip' },
+ { id: 2, default_shipping: true, name: 'Bender' },
+ { id: 3, default_shipping: false, name: 'John' }
+ ],
+ handleAddAddress: jest.fn().mockName('handleAddAddress'),
+ handleApplyAddress: jest.fn().mockName('handleApplyAddress'),
+ handleCancel: jest.fn().mockName('handleCancel'),
+ handleEditAddress: jest.fn().mockName('handleEditAddress'),
+ handleSelectAddress: jest.fn().mockName('handleSelectAddress'),
+ isLoading: false,
+ selectedAddress: 3
+ });
+
+ const tree = createTestInstance(
+
+ );
+ expect(tree.toJSON()).toMatchSnapshot();
+});
+
+test('render hidden state with disabled buttons', () => {
+ useAddressBook.mockReturnValueOnce({
+ customerAddresses: [],
+ handleAddAddress: jest.fn().mockName('handleAddAddress'),
+ handleApplyAddress: jest.fn().mockName('handleApplyAddress'),
+ handleCancel: jest.fn().mockName('handleCancel'),
+ isLoading: true
+ });
+
+ const tree = createTestInstance(
+
+ );
+ expect(tree.toJSON()).toMatchSnapshot();
+});
diff --git a/packages/venia-ui/lib/components/CheckoutPage/AddressBook/__tests__/addressCard.spec.js b/packages/venia-ui/lib/components/CheckoutPage/AddressBook/__tests__/addressCard.spec.js
new file mode 100644
index 0000000000..c453d9fad5
--- /dev/null
+++ b/packages/venia-ui/lib/components/CheckoutPage/AddressBook/__tests__/addressCard.spec.js
@@ -0,0 +1,59 @@
+import React from 'react';
+import { createTestInstance } from '@magento/peregrine';
+import { useAddressCard } from '@magento/peregrine/lib/talons/CheckoutPage/AddressBook/useAddressCard';
+
+import AddressCard from '../addressCard';
+
+jest.mock(
+ '@magento/peregrine/lib/talons/CheckoutPage/AddressBook/useAddressCard'
+);
+jest.mock('../../../../classify');
+
+const mockAddress = {
+ city: 'Manhattan',
+ country_code: 'US',
+ default_shipping: true,
+ firstname: 'Philip',
+ lastname: 'Fry',
+ postcode: '10019',
+ region: { region: 'NY' },
+ street: ['3000 57th Street', 'Suite 200'],
+ telephone: '(123) 456-7890'
+};
+
+const talonProps = {
+ handleClick: jest.fn().mockName('handleClick'),
+ handleEditAddress: jest.fn().mockName('handleEditAddress'),
+ handleKeyPress: jest.fn().mockName('handleKeyPress'),
+ hasUpdate: false
+};
+
+test('renders base card state', () => {
+ useAddressCard.mockReturnValueOnce(talonProps);
+
+ const tree = createTestInstance(
+
+ );
+ expect(tree.toJSON()).toMatchSnapshot();
+});
+
+test('renders selected card state', () => {
+ useAddressCard.mockReturnValueOnce(talonProps);
+
+ const tree = createTestInstance(
+
+ );
+ expect(tree.toJSON()).toMatchSnapshot();
+});
+
+test('renders updated card state', () => {
+ useAddressCard.mockReturnValueOnce({ ...talonProps, hasUpdate: true });
+
+ const tree = createTestInstance(
+
+ );
+ expect(tree.toJSON()).toMatchSnapshot();
+});
diff --git a/packages/venia-ui/lib/components/CheckoutPage/AddressBook/addressBook.css b/packages/venia-ui/lib/components/CheckoutPage/AddressBook/addressBook.css
new file mode 100644
index 0000000000..d3d6d0f0f2
--- /dev/null
+++ b/packages/venia-ui/lib/components/CheckoutPage/AddressBook/addressBook.css
@@ -0,0 +1,74 @@
+.root {
+ display: none;
+}
+
+.root_active {
+ composes: root;
+ align-items: center;
+ display: grid;
+ grid-template-areas:
+ 'header buttons'
+ 'content content';
+ grid-template-columns: auto auto;
+ grid-template-rows: 60px 1fr;
+ justify-content: space-between;
+ row-gap: 1rem;
+}
+
+.headerText {
+ grid-area: header;
+ color: rgb(var(--venia-text-alt));
+ line-height: 1.25em;
+}
+
+.buttonContainer {
+ column-gap: 1rem;
+ display: grid;
+ grid-area: buttons;
+ grid-auto-flow: column;
+ justify-content: end;
+}
+
+.content {
+ border-top: 1px solid rgb(var(--venia-border));
+ display: grid;
+ gap: 1rem;
+ grid-area: content;
+ grid-auto-rows: minmax(6rem, max-content);
+ grid-template-columns: 1fr 1fr 1fr;
+ padding-top: 2rem;
+}
+
+.addButton {
+ border: 1px dashed rgb(var(--venia-border));
+ border-radius: 5px;
+ font-size: 0.875rem;
+ font-weight: 600;
+ transition: border-color 384ms var(--venia-anim-standard);
+}
+
+.addButton:hover {
+ border: 1px dashed rgb(var(--venia-grey-darker));
+ box-shadow: -1px 1px 1px rgb(var(--venia-grey));
+}
+
+@media (max-width: 960px) {
+ .root_active {
+ grid-template-areas:
+ 'header'
+ 'content'
+ 'buttons';
+ grid-template-columns: 1fr;
+ grid-template-rows: 60px 1fr 60px;
+ }
+
+ .buttonContainer {
+ justify-content: center;
+ }
+
+ .content {
+ border-top: none;
+ grid-template-columns: 1fr;
+ padding-top: 0;
+ }
+}
diff --git a/packages/venia-ui/lib/components/CheckoutPage/AddressBook/addressBook.gql.js b/packages/venia-ui/lib/components/CheckoutPage/AddressBook/addressBook.gql.js
new file mode 100644
index 0000000000..f139303263
--- /dev/null
+++ b/packages/venia-ui/lib/components/CheckoutPage/AddressBook/addressBook.gql.js
@@ -0,0 +1,38 @@
+import gql from 'graphql-tag';
+
+import { SET_CUSTOMER_ADDRESS_ON_CART } from '../ShippingInformation/shippingInformation.gql';
+import { CustomerAddressFragment } from './addressBookFragments.gql';
+import { ShippingInformationFragment } from '../ShippingInformation/shippingInformationFragments.gql';
+
+export const GET_CUSTOMER_ADDRESSES = gql`
+ query GetCustomerAddresses {
+ customer {
+ id
+ addresses {
+ id
+ ...CustomerAddressFragment
+ }
+ }
+ }
+ ${CustomerAddressFragment}
+`;
+
+export const GET_CUSTOMER_CART_ADDRESS = gql`
+ query GetCustomerCartAddress {
+ customerCart {
+ id
+ ...ShippingInformationFragment
+ }
+ }
+ ${ShippingInformationFragment}
+`;
+
+export default {
+ mutations: {
+ setCustomerAddressOnCartMutation: SET_CUSTOMER_ADDRESS_ON_CART
+ },
+ queries: {
+ getCustomerAddressesQuery: GET_CUSTOMER_ADDRESSES,
+ getCustomerCartAddressQuery: GET_CUSTOMER_CART_ADDRESS
+ }
+};
diff --git a/packages/venia-ui/lib/components/CheckoutPage/AddressBook/addressBook.js b/packages/venia-ui/lib/components/CheckoutPage/AddressBook/addressBook.js
new file mode 100644
index 0000000000..4a0d096c63
--- /dev/null
+++ b/packages/venia-ui/lib/components/CheckoutPage/AddressBook/addressBook.js
@@ -0,0 +1,127 @@
+import React, { Fragment, useMemo } from 'react';
+import { useAddressBook } from '@magento/peregrine/lib/talons/CheckoutPage/AddressBook/useAddressBook';
+
+import { mergeClasses } from '../../../classify';
+import Button from '../../Button';
+import defaultClasses from './addressBook.css';
+import AddressBookOperations from './addressBook.gql';
+import EditModal from '../ShippingInformation/editModal';
+import AddressCard from './addressCard';
+import { shape, string, func } from 'prop-types';
+
+const AddressBook = props => {
+ const { activeContent, classes: propClasses, toggleActiveContent } = props;
+
+ const talonProps = useAddressBook({
+ ...AddressBookOperations,
+ toggleActiveContent
+ });
+
+ const {
+ activeAddress,
+ customerAddresses,
+ handleAddAddress,
+ handleApplyAddress,
+ handleCancel,
+ handleEditAddress,
+ handleSelectAddress,
+ isLoading,
+ selectedAddress
+ } = talonProps;
+
+ const classes = mergeClasses(defaultClasses, propClasses);
+
+ const rootClass =
+ activeContent === 'addressBook' ? classes.root_active : classes.root;
+
+ const addAddressButton = (
+
+ );
+
+ const addressElements = useMemo(() => {
+ let defaultIndex;
+ const addresses = customerAddresses.map((address, index) => {
+ const isSelected = selectedAddress === address.id;
+
+ if (address.default_shipping) {
+ defaultIndex = index;
+ }
+
+ return (
+
+ );
+ });
+
+ // Position the default address first in the elements list
+ if (defaultIndex) {
+ [addresses[0], addresses[defaultIndex]] = [
+ addresses[defaultIndex],
+ addresses[0]
+ ];
+ }
+
+ return [...addresses, addAddressButton];
+ }, [
+ addAddressButton,
+ customerAddresses,
+ handleEditAddress,
+ handleSelectAddress,
+ selectedAddress
+ ]);
+
+ return (
+
+
+
+ Change Shipping Information
+
+
+
+
+
+
+
{addressElements}
+
+
+
+ );
+};
+
+export default AddressBook;
+
+AddressBook.propTypes = {
+ activeContent: string.isRequired,
+ classes: shape({
+ root: string,
+ root_active: string,
+ headerText: string,
+ buttonContainer: string,
+ content: string,
+ addButton: string
+ }),
+ toggleActiveContent: func.isRequired
+};
diff --git a/packages/venia-ui/lib/components/CheckoutPage/AddressBook/addressBookFragments.gql.js b/packages/venia-ui/lib/components/CheckoutPage/AddressBook/addressBookFragments.gql.js
new file mode 100644
index 0000000000..0d8e686c96
--- /dev/null
+++ b/packages/venia-ui/lib/components/CheckoutPage/AddressBook/addressBookFragments.gql.js
@@ -0,0 +1,31 @@
+import gql from 'graphql-tag';
+
+export const CustomerAddressFragment = gql`
+ fragment CustomerAddressFragment on CustomerAddress {
+ id
+ city
+ country_code
+ default_shipping
+ firstname
+ lastname
+ postcode
+ region {
+ region
+ region_code
+ region_id
+ }
+ street
+ telephone
+ }
+`;
+
+export const AddressBookFragment = gql`
+ fragment AddressBookFragment on Customer {
+ id
+ addresses {
+ id
+ ...CustomerAddressFragment
+ }
+ }
+ ${CustomerAddressFragment}
+`;
diff --git a/packages/venia-ui/lib/components/CheckoutPage/AddressBook/addressCard.css b/packages/venia-ui/lib/components/CheckoutPage/AddressBook/addressCard.css
new file mode 100644
index 0000000000..f7fe806567
--- /dev/null
+++ b/packages/venia-ui/lib/components/CheckoutPage/AddressBook/addressCard.css
@@ -0,0 +1,87 @@
+.root {
+ align-content: flex-start;
+ border: 1px solid rgb(var(--venia-border));
+ border-radius: 5px;
+ box-shadow: none;
+ cursor: pointer;
+ display: grid;
+ font-size: 0.875rem;
+ padding: 1.5rem 1rem;
+ position: relative;
+ row-gap: 0.5rem;
+ transition: border-color 384ms var(--venia-anim-in);
+ outline: none;
+}
+
+.root_selected {
+ composes: root;
+ border-color: rgb(var(--venia-grey-darker));
+ box-shadow: -1px 4px 4px rgb(var(--venia-grey-dark)),
+ inset 0 0 0 1px rgb(var(--venia-grey-darker));
+ cursor: default;
+}
+
+.root_updated {
+ composes: root_selected;
+ animation: flash var(--venia-anim-bounce) 640ms 2;
+}
+
+.root:focus,
+.root:hover {
+ box-shadow: -1px 2px 2px rgb(var(--venia-grey-dark));
+}
+
+.root_selected:focus,
+.root_selected:hover {
+ box-shadow: -1px 4px 4px rgb(var(--venia-grey-dark)),
+ inset 0 0 0 1px rgb(var(--venia-grey-darker));
+}
+
+.defaultCard {
+ grid-area: 1 / 1;
+}
+
+.editButton {
+ padding: 1rem;
+ position: absolute;
+ right: 0;
+ top: 0;
+}
+
+.editButton:hover {
+ --fill: black;
+}
+
+.editIcon {
+ fill: var(--fill, white);
+ transition: fill 384ms var(--venia-anim-standard);
+}
+
+.defaultBadge {
+ color: rgb(var(--venia-text-hint));
+ font-size: 0.75rem;
+ font-weight: 600;
+ text-transform: uppercase;
+}
+
+.name {
+ font-size: 1rem;
+ font-weight: 600;
+}
+
+.address {
+ display: grid;
+ gap: 0.5rem;
+}
+
+@keyframes flash {
+ 0% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0.5;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
diff --git a/packages/venia-ui/lib/components/CheckoutPage/AddressBook/addressCard.js b/packages/venia-ui/lib/components/CheckoutPage/AddressBook/addressCard.js
new file mode 100644
index 0000000000..5129637a7a
--- /dev/null
+++ b/packages/venia-ui/lib/components/CheckoutPage/AddressBook/addressCard.js
@@ -0,0 +1,110 @@
+import React from 'react';
+import { shape, string, bool, func, arrayOf } from 'prop-types';
+import { Edit2 as EditIcon } from 'react-feather';
+import { useAddressCard } from '@magento/peregrine/lib/talons/CheckoutPage/AddressBook/useAddressCard';
+
+import { mergeClasses } from '../../../classify';
+import Icon from '../../Icon';
+import defaultClasses from './addressCard.css';
+
+const AddressCard = props => {
+ const {
+ address,
+ classes: propClasses,
+ isSelected,
+ onEdit,
+ onSelection
+ } = props;
+
+ const talonProps = useAddressCard({ address, onEdit, onSelection });
+ const {
+ handleClick,
+ handleEditAddress,
+ handleKeyPress,
+ hasUpdate
+ } = talonProps;
+
+ const {
+ city,
+ country_code,
+ default_shipping,
+ firstname,
+ lastname,
+ postcode,
+ region: { region },
+ street
+ } = address;
+
+ const streetRows = street.map((row, index) => {
+ return {row};
+ });
+
+ const classes = mergeClasses(defaultClasses, propClasses);
+
+ const rootClass = isSelected
+ ? hasUpdate
+ ? classes.root_updated
+ : classes.root_selected
+ : classes.root;
+
+ const editButton = isSelected ? (
+
+ ) : null;
+
+ const defaultBadge = default_shipping ? (
+ {'Default'}
+ ) : null;
+
+ return (
+
+ {editButton}
+ {defaultBadge}
+ {`${firstname} ${lastname}`}
+ {streetRows}
+ {`${city}, ${region} ${postcode} ${country_code}`}
+
+ );
+};
+
+export default AddressCard;
+
+AddressCard.propTypes = {
+ address: shape({
+ city: string,
+ country_code: string,
+ default_shipping: bool,
+ firstname: string,
+ lastname: string,
+ postcode: string,
+ region: shape({
+ region_code: string,
+ region: string
+ }),
+ street: arrayOf(string)
+ }).isRequired,
+ classes: shape({
+ root: string,
+ root_selected: string,
+ root_updated: string,
+ editButton: string,
+ editIcon: string,
+ defaultBadge: string,
+ name: string,
+ address: string
+ }),
+ isSelected: bool.isRequired,
+ onEdit: func.isRequired,
+ onSelection: func.isRequired
+};
diff --git a/packages/venia-ui/lib/components/CheckoutPage/AddressBook/index.js b/packages/venia-ui/lib/components/CheckoutPage/AddressBook/index.js
new file mode 100644
index 0000000000..8d18a69b30
--- /dev/null
+++ b/packages/venia-ui/lib/components/CheckoutPage/AddressBook/index.js
@@ -0,0 +1 @@
+export { default } from './addressBook';
diff --git a/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/__tests__/__snapshots__/addressForm.spec.js.snap b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/__tests__/__snapshots__/addressForm.spec.js.snap
new file mode 100644
index 0000000000..0b326a4621
--- /dev/null
+++ b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/__tests__/__snapshots__/addressForm.spec.js.snap
@@ -0,0 +1,15 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders customer form 1`] = `
+
+`;
+
+exports[`renders guest form 1`] = `
+
+`;
diff --git a/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/__tests__/__snapshots__/customerForm.spec.js.snap b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/__tests__/__snapshots__/customerForm.spec.js.snap
new file mode 100644
index 0000000000..a9cdd589c6
--- /dev/null
+++ b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/__tests__/__snapshots__/customerForm.spec.js.snap
@@ -0,0 +1,1294 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders loading indicator 1`] = `
+
+
+
+
+
+ Fetching Customer Details...
+
+
+`;
+
+exports[`renders prefilled form with data with disabled buttons 1`] = `
+
+`;
+
+exports[`renders prefilled form with data with enabled buttons 1`] = `
+
+`;
+
+exports[`renders special form for initial default address entry 1`] = `
+
+`;
diff --git a/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/EditForm/__tests__/__snapshots__/editForm.spec.js.snap b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/__tests__/__snapshots__/guestForm.spec.js.snap
similarity index 100%
rename from packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/EditForm/__tests__/__snapshots__/editForm.spec.js.snap
rename to packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/__tests__/__snapshots__/guestForm.spec.js.snap
diff --git a/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/__tests__/addressForm.spec.js b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/__tests__/addressForm.spec.js
new file mode 100644
index 0000000000..c086862c63
--- /dev/null
+++ b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/__tests__/addressForm.spec.js
@@ -0,0 +1,29 @@
+import React from 'react';
+import { useUserContext } from '@magento/peregrine/lib/context/user';
+import { createTestInstance } from '@magento/peregrine';
+
+import AddressForm from '../addressForm';
+
+jest.mock('@magento/peregrine/lib/context/user', () => ({
+ useUserContext: jest.fn()
+}));
+jest.mock('../guestForm', () => 'GuestForm');
+jest.mock('../customerForm', () => 'CustomerForm');
+
+test('renders guest form', () => {
+ useUserContext.mockReturnValueOnce([{ isSignedIn: false }]);
+
+ const tree = createTestInstance(
+
+ );
+ expect(tree.toJSON()).toMatchSnapshot();
+});
+
+test('renders customer form', () => {
+ useUserContext.mockReturnValueOnce([{ isSignedIn: true }]);
+
+ const tree = createTestInstance(
+
+ );
+ expect(tree.toJSON()).toMatchSnapshot();
+});
diff --git a/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/__tests__/customerForm.spec.js b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/__tests__/customerForm.spec.js
new file mode 100644
index 0000000000..b47129561f
--- /dev/null
+++ b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/__tests__/customerForm.spec.js
@@ -0,0 +1,98 @@
+import React from 'react';
+import { createTestInstance } from '@magento/peregrine';
+import { useCustomerForm } from '@magento/peregrine/lib/talons/CheckoutPage/ShippingInformation/AddressForm/useCustomerForm';
+
+import CustomerForm from '../customerForm';
+
+jest.mock(
+ '@magento/peregrine/lib/talons/CheckoutPage/ShippingInformation/AddressForm/useCustomerForm'
+);
+jest.mock('../../../../../classify');
+jest.mock('../../../../Country', () => 'Country');
+jest.mock('../../../../Region', () => 'Region');
+
+const mockProps = {
+ afterSubmit: jest.fn(),
+ onCancel: jest.fn()
+};
+
+const handleCancel = jest.fn().mockName('handleCancel');
+const handleSubmit = jest.fn().mockName('handleSubmit');
+
+test('renders loading indicator', () => {
+ useCustomerForm.mockReturnValueOnce({
+ isLoading: true
+ });
+
+ const tree = createTestInstance();
+ expect(tree.toJSON()).toMatchSnapshot();
+});
+
+test('renders special form for initial default address entry', () => {
+ useCustomerForm.mockReturnValueOnce({
+ handleCancel,
+ handleSubmit,
+ hasDefaultShipping: false,
+ initialValues: {
+ country: 'US',
+ email: 'fry@planet.express',
+ firstname: 'Philip',
+ lastname: 'Fry',
+ region: ''
+ },
+ isLoading: false,
+ isSaving: false,
+ isUpdate: false
+ });
+
+ const tree = createTestInstance();
+ expect(tree.toJSON()).toMatchSnapshot();
+});
+
+describe('renders prefilled form with data', () => {
+ const initialValues = {
+ city: 'Manhattan',
+ country: 'US',
+ default_shipping: true,
+ email: 'fry@planet.express',
+ firstname: 'Philip',
+ lastname: 'Fry',
+ postcode: '10019',
+ region: 'NY',
+ street: ['3000 57th Street', 'Suite 200'],
+ telephone: '(123) 456-7890'
+ };
+
+ test('with enabled buttons', () => {
+ useCustomerForm.mockReturnValueOnce({
+ handleCancel,
+ handleSubmit,
+ hasDefaultShipping: true,
+ initialValues,
+ isLoading: false,
+ isSaving: false,
+ isUpdate: true
+ });
+
+ const tree = createTestInstance();
+ expect(tree.toJSON()).toMatchSnapshot();
+ });
+
+ test('with disabled buttons', () => {
+ useCustomerForm.mockReturnValueOnce({
+ handleCancel,
+ handleSubmit,
+ hasDefaultShipping: true,
+ initialValues: {
+ ...initialValues,
+ default_shipping: false
+ },
+ isLoading: false,
+ isSaving: true,
+ isUpdate: true
+ });
+
+ const tree = createTestInstance();
+ expect(tree.toJSON()).toMatchSnapshot();
+ });
+});
diff --git a/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/EditForm/__tests__/editForm.spec.js b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/__tests__/guestForm.spec.js
similarity index 75%
rename from packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/EditForm/__tests__/editForm.spec.js
rename to packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/__tests__/guestForm.spec.js
index a16e4511e4..4021e2016f 100644
--- a/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/EditForm/__tests__/editForm.spec.js
+++ b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/__tests__/guestForm.spec.js
@@ -1,11 +1,11 @@
import React from 'react';
import { createTestInstance } from '@magento/peregrine';
-import { useEditForm } from '@magento/peregrine/lib/talons/CheckoutPage/ShippingInformation/EditForm/useEditForm';
+import { useGuestForm } from '@magento/peregrine/lib/talons/CheckoutPage/ShippingInformation/AddressForm/useGuestForm';
-import EditForm from '../editForm';
+import GuestForm from '../guestForm';
jest.mock(
- '@magento/peregrine/lib/talons/CheckoutPage/ShippingInformation/EditForm/useEditForm'
+ '@magento/peregrine/lib/talons/CheckoutPage/ShippingInformation/AddressForm/useGuestForm'
);
jest.mock('../../../../../classify');
jest.mock('../../../../Country', () => 'Country');
@@ -20,7 +20,7 @@ const handleCancel = jest.fn().mockName('handleCancel');
const handleSubmit = jest.fn().mockName('handleSubmit');
test('renders empty form without data', () => {
- useEditForm.mockReturnValueOnce({
+ useGuestForm.mockReturnValueOnce({
handleCancel,
handleSubmit,
initialValues: {
@@ -31,7 +31,7 @@ test('renders empty form without data', () => {
isUpdate: false
});
- const tree = createTestInstance();
+ const tree = createTestInstance();
expect(tree.toJSON()).toMatchSnapshot();
});
@@ -49,7 +49,7 @@ describe('renders prefilled form with data', () => {
};
test('with enabled buttons', () => {
- useEditForm.mockReturnValueOnce({
+ useGuestForm.mockReturnValueOnce({
handleCancel,
handleSubmit,
initialValues,
@@ -57,12 +57,12 @@ describe('renders prefilled form with data', () => {
isUpdate: true
});
- const tree = createTestInstance();
+ const tree = createTestInstance();
expect(tree.toJSON()).toMatchSnapshot();
});
test('with disabled buttons', () => {
- useEditForm.mockReturnValueOnce({
+ useGuestForm.mockReturnValueOnce({
handleCancel,
handleSubmit,
initialValues,
@@ -70,7 +70,7 @@ describe('renders prefilled form with data', () => {
isUpdate: true
});
- const tree = createTestInstance();
+ const tree = createTestInstance();
expect(tree.toJSON()).toMatchSnapshot();
});
});
diff --git a/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/addressForm.js b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/addressForm.js
new file mode 100644
index 0000000000..0cb7afecb3
--- /dev/null
+++ b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/addressForm.js
@@ -0,0 +1,18 @@
+import React from 'react';
+import { useUserContext } from '@magento/peregrine/lib/context/user';
+
+import CustomerForm from './customerForm';
+import GuestForm from './guestForm';
+
+/**
+ * Simple component that acts like an AddressForm factory, giving the client
+ * the correct form to render based on the current signed in state.
+ */
+const AddressForm = props => {
+ const [{ isSignedIn }] = useUserContext();
+ const AddressForm = isSignedIn ? CustomerForm : GuestForm;
+
+ return ;
+};
+
+export default AddressForm;
diff --git a/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/customerForm.css b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/customerForm.css
new file mode 100644
index 0000000000..e093344a46
--- /dev/null
+++ b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/customerForm.css
@@ -0,0 +1,62 @@
+.root {
+ display: grid;
+ gap: 0.5rem 1.5rem;
+ grid-template-columns: 1fr 1fr;
+ width: 100%;
+}
+
+.field {
+ grid-column-end: span 2;
+}
+
+.formMessage,
+.email,
+.country,
+.street0,
+.street1,
+.city,
+.region,
+.postcode,
+.telephone {
+ composes: field;
+}
+
+.defaultShipping {
+ composes: field;
+ padding-top: 1rem;
+}
+
+.firstname,
+.lastname {
+ grid-column-end: span 1;
+}
+
+.buttons {
+ composes: field;
+ display: grid;
+ gap: 1rem;
+ grid-auto-flow: column;
+ justify-self: center;
+ padding: 1rem;
+}
+
+.submit {
+ composes: root_normalPriority from '../../../Button/button.css';
+ font-size: 0.875rem;
+ font-weight: 600;
+}
+
+.submit_update {
+ composes: submit;
+ composes: filled from '../../../Button/button.css';
+}
+
+@media (max-width: 960px) {
+ .firstname {
+ grid-column: 1 / span 2;
+ }
+
+ .lastname {
+ grid-column: 1 / span 2;
+ }
+}
diff --git a/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/customerForm.gql.js b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/customerForm.gql.js
new file mode 100644
index 0000000000..575602da78
--- /dev/null
+++ b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/customerForm.gql.js
@@ -0,0 +1,59 @@
+import gql from 'graphql-tag';
+
+import { GET_CUSTOMER_ADDRESSES } from '../../AddressBook/addressBook.gql';
+import { CustomerAddressFragment } from '../../AddressBook/addressBookFragments.gql';
+import { GET_DEFAULT_SHIPPING } from '../shippingInformation.gql';
+
+export const CREATE_CUSTOMER_ADDRESS_MUTATION = gql`
+ mutation CreateCustomerAddress($address: CustomerAddressInput!) {
+ createCustomerAddress(input: $address)
+ @connection(key: "createCustomerAddress") {
+ id
+ ...CustomerAddressFragment
+ }
+ }
+ ${CustomerAddressFragment}
+`;
+
+/**
+ * We would normally use the CustomerAddressFragment here for the response
+ * but due to GraphQL returning null region data, we return minimal data and
+ * rely on refetching after performing this mutation to get accurate data.
+ *
+ * Fragment will be added back after MC-33948 is resolved.
+ */
+export const UPDATE_CUSTOMER_ADDRESS_MUTATION = gql`
+ mutation UpdateCustomerAddress(
+ $addressId: Int!
+ $address: CustomerAddressInput!
+ ) {
+ updateCustomerAddress(id: $addressId, input: $address)
+ @connection(key: "updateCustomerAddress") {
+ id
+ }
+ }
+`;
+
+export const GET_CUSTOMER_QUERY = gql`
+ query GetCustomer {
+ customer {
+ id
+ default_shipping
+ email
+ firstname
+ lastname
+ }
+ }
+`;
+
+export default {
+ mutations: {
+ createCustomerAddressMutation: CREATE_CUSTOMER_ADDRESS_MUTATION,
+ updateCustomerAddressMutation: UPDATE_CUSTOMER_ADDRESS_MUTATION
+ },
+ queries: {
+ getCustomerQuery: GET_CUSTOMER_QUERY,
+ getCustomerAddressesQuery: GET_CUSTOMER_ADDRESSES,
+ getDefaultShippingQuery: GET_DEFAULT_SHIPPING
+ }
+};
diff --git a/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/customerForm.js b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/customerForm.js
new file mode 100644
index 0000000000..139dfb67bb
--- /dev/null
+++ b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/customerForm.js
@@ -0,0 +1,227 @@
+import React from 'react';
+import { Form, Text } from 'informed';
+import { func, shape, string, arrayOf, number, bool } from 'prop-types';
+import { useCustomerForm } from '@magento/peregrine/lib/talons/CheckoutPage/ShippingInformation/AddressForm/useCustomerForm';
+
+import { mergeClasses } from '../../../../classify';
+import { isRequired } from '../../../../util/formValidators';
+import Button from '../../../Button';
+import Checkbox from '../../../Checkbox';
+import Country from '../../../Country';
+import Field, { Message } from '../../../Field';
+import Region from '../../../Region';
+import TextInput from '../../../TextInput';
+import defaultClasses from './customerForm.css';
+import CustomerFormOperations from './customerForm.gql';
+import LoadingIndicator from '../../../LoadingIndicator';
+
+const CustomerForm = props => {
+ const { afterSubmit, classes: propClasses, onCancel, shippingData } = props;
+
+ const talonProps = useCustomerForm({
+ afterSubmit,
+ ...CustomerFormOperations,
+ onCancel,
+ shippingData
+ });
+ const {
+ handleCancel,
+ handleSubmit,
+ hasDefaultShipping,
+ initialValues,
+ isLoading,
+ isSaving,
+ isUpdate
+ } = talonProps;
+
+ if (isLoading) {
+ return (
+ Fetching Customer Details...
+ );
+ }
+
+ const classes = mergeClasses(defaultClasses, propClasses);
+
+ const emailRow = !hasDefaultShipping ? (
+
+
+
+
+
+ ) : null;
+
+ const formMessageRow = !hasDefaultShipping ? (
+
+
+ {
+ 'The shipping address you enter will be saved to your address book and set as your default for future purchases.'
+ }
+
+
+ ) : null;
+
+ const cancelButton = isUpdate ? (
+
+ ) : null;
+
+ const submitButtonText = !hasDefaultShipping
+ ? 'Save and Continue'
+ : isUpdate
+ ? 'Update'
+ : 'Add';
+
+ const submitButtonProps = {
+ classes: {
+ root_normalPriority: classes.submit,
+ root_highPriority: classes.submit_update
+ },
+ disabled: isSaving,
+ priority: isUpdate ? 'high' : 'normal',
+ type: 'submit'
+ };
+
+ const defaultShippingElement = hasDefaultShipping ? (
+
+
+
+ ) : (
+
+ );
+
+ return (
+
+ );
+};
+
+export default CustomerForm;
+
+CustomerForm.defaultProps = {
+ shippingData: {
+ country: {
+ code: 'US'
+ },
+ region: {
+ id: null
+ }
+ }
+};
+
+CustomerForm.propTypes = {
+ afterSubmit: func,
+ classes: shape({
+ root: string,
+ field: string,
+ email: string,
+ firstname: string,
+ lastname: string,
+ country: string,
+ street0: string,
+ street1: string,
+ city: string,
+ region: string,
+ postcode: string,
+ telephone: string,
+ buttons: string,
+ submit: string,
+ submit_update: string,
+ formMessage: string,
+ defaultShipping: string
+ }),
+ onCancel: func,
+ shippingData: shape({
+ city: string,
+ country: shape({
+ code: string.isRequired
+ }).isRequired,
+ default_shipping: bool,
+ email: string,
+ firstname: string,
+ id: number,
+ lastname: string,
+ postcode: string,
+ region: shape({
+ id: number
+ }).isRequired,
+ street: arrayOf(string),
+ telephone: string
+ })
+};
diff --git a/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/EditForm/editForm.css b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/guestForm.css
similarity index 100%
rename from packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/EditForm/editForm.css
rename to packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/guestForm.css
diff --git a/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/EditForm/editForm.gql.js b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/guestForm.gql.js
similarity index 80%
rename from packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/EditForm/editForm.gql.js
rename to packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/guestForm.gql.js
index 79c5183202..d40877576b 100644
--- a/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/EditForm/editForm.gql.js
+++ b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/guestForm.gql.js
@@ -3,14 +3,14 @@ import gql from 'graphql-tag';
import { ShippingInformationFragment } from '../shippingInformationFragments.gql';
import { ShippingMethodsCheckoutFragment } from '../../ShippingMethod/shippingMethodFragments.gql';
-export const SET_SHIPPING_INFORMATION_MUTATION = gql`
- mutation SetShippingInformation(
+export const SET_GUEST_SHIPPING_MUTATION = gql`
+ mutation SetGuestShipping(
$cartId: String!
$email: String!
$address: CartAddressInput!
) {
setGuestEmailOnCart(input: { cart_id: $cartId, email: $email })
- @connection(key: setGuestEmailOnCart) {
+ @connection(key: "setGuestEmailOnCart") {
cart {
id
}
@@ -35,6 +35,7 @@ export const SET_SHIPPING_INFORMATION_MUTATION = gql`
export default {
mutations: {
- setShippingInformationMutation: SET_SHIPPING_INFORMATION_MUTATION
- }
+ setGuestShippingMutation: SET_GUEST_SHIPPING_MUTATION
+ },
+ queries: {}
};
diff --git a/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/EditForm/editForm.js b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/guestForm.js
similarity index 82%
rename from packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/EditForm/editForm.js
rename to packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/guestForm.js
index 3ca0673cac..e3b0163277 100644
--- a/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/EditForm/editForm.js
+++ b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/guestForm.js
@@ -1,7 +1,7 @@
import React from 'react';
import { Form } from 'informed';
import { func, shape, string, arrayOf } from 'prop-types';
-import { useEditForm } from '@magento/peregrine/lib/talons/CheckoutPage/ShippingInformation/EditForm/useEditForm';
+import { useGuestForm } from '@magento/peregrine/lib/talons/CheckoutPage/ShippingInformation/AddressForm/useGuestForm';
import { mergeClasses } from '../../../../classify';
import { isRequired } from '../../../../util/formValidators';
@@ -10,15 +10,15 @@ import Country from '../../../Country';
import Field, { Message } from '../../../Field';
import Region from '../../../Region';
import TextInput from '../../../TextInput';
-import defaultClasses from './editForm.css';
-import EditFormOperations from './editForm.gql';
+import defaultClasses from './guestForm.css';
+import GuestFormOperations from './guestForm.gql';
-const EditForm = props => {
+const GuestForm = props => {
const { afterSubmit, classes: propClasses, onCancel, shippingData } = props;
- const talonProps = useEditForm({
+ const talonProps = useGuestForm({
afterSubmit,
- ...EditFormOperations,
+ ...GuestFormOperations,
onCancel,
shippingData
});
@@ -32,7 +32,7 @@ const EditForm = props => {
const classes = mergeClasses(defaultClasses, propClasses);
- const messageElement = !isUpdate ? (
+ const guestEmailMessage = !isUpdate ? (
{
'Set a password at the end of guest checkout to create an account in one easy step.'
@@ -53,19 +53,19 @@ const EditForm = props => {
) : null;
- const submitButton = (
-
- );
+ const submitButtonText = isUpdate
+ ? 'Update'
+ : 'Continue to Shipping Method';
+
+ const submitButtonProps = {
+ classes: {
+ root_normalPriority: classes.submit,
+ root_highPriority: classes.submit_update
+ },
+ disabled: isSaving,
+ priority: isUpdate ? 'high' : 'normal',
+ type: 'submit'
+ };
return (
);
};
-export default EditForm;
+export default GuestForm;
-EditForm.defaultProps = {
+GuestForm.defaultProps = {
shippingData: {
country: {
code: 'US'
@@ -141,7 +141,7 @@ EditForm.defaultProps = {
}
};
-EditForm.propTypes = {
+GuestForm.propTypes = {
afterSubmit: func,
classes: shape({
root: string,
diff --git a/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/index.js b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/index.js
new file mode 100644
index 0000000000..c50616a40c
--- /dev/null
+++ b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/AddressForm/index.js
@@ -0,0 +1,3 @@
+export { default } from './addressForm';
+export { default as GuestForm } from './guestForm';
+export { default as CustomerForm } from './customerForm';
diff --git a/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/EditForm/index.js b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/EditForm/index.js
deleted file mode 100644
index f49cf662ea..0000000000
--- a/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/EditForm/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './editForm';
diff --git a/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/__tests__/__snapshots__/editModal.spec.js.snap b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/__tests__/__snapshots__/editModal.spec.js.snap
index 9e0b059540..69c1154821 100644
--- a/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/__tests__/__snapshots__/editModal.spec.js.snap
+++ b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/__tests__/__snapshots__/editModal.spec.js.snap
@@ -106,7 +106,7 @@ exports[`renders open modal 1`] = `
-
+
+
+ Shipping Information
+
+
+
+
+
+`;
+
+exports[`renders card state with guest data 1`] = `
@@ -63,7 +111,7 @@ exports[`renders form state without data 1`] = `
-
diff --git a/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/__tests__/editModal.spec.js b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/__tests__/editModal.spec.js
index 2056248f26..0ac5de8be3 100644
--- a/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/__tests__/editModal.spec.js
+++ b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/__tests__/editModal.spec.js
@@ -11,7 +11,7 @@ jest.mock('../../../../classify');
jest.mock('../../../Modal', () => ({
Modal: props =>
{props.children}
}));
-jest.mock('../EditForm', () => 'EditForm');
+jest.mock('../AddressForm', () => 'AddressForm');
const handleClose = jest.fn().mockName('handleClose');
diff --git a/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/__tests__/shippingInformation.spec.js b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/__tests__/shippingInformation.spec.js
index 029aa1d69f..e0e846ccaf 100644
--- a/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/__tests__/shippingInformation.spec.js
+++ b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/__tests__/shippingInformation.spec.js
@@ -11,24 +11,37 @@ jest.mock('../../../../classify');
jest.mock('../../../LoadingIndicator', () => 'LoadingIndicator');
jest.mock('../card', () => 'Card');
-jest.mock('../EditForm', () => 'EditForm');
+jest.mock('../AddressForm', () => 'AddressForm');
jest.mock('../editModal', () => 'EditModal');
test('renders loading element', () => {
useShippingInformation.mockReturnValueOnce({
doneEditing: false,
- loading: true
+ isLoading: true
});
const tree = createTestInstance(
);
expect(tree.toJSON()).toMatchSnapshot();
});
-test('renders card state with data', () => {
+test('renders card state with guest data', () => {
useShippingInformation.mockReturnValueOnce({
doneEditing: true,
handleEditShipping: jest.fn().mockName('handleEditShipping'),
- loading: false,
+ isLoading: false,
+ shippingData: 'Shipping Data'
+ });
+
+ const tree = createTestInstance(
);
+ expect(tree.toJSON()).toMatchSnapshot();
+});
+
+test('renders card state with customer data', () => {
+ useShippingInformation.mockReturnValueOnce({
+ doneEditing: true,
+ handleEditShipping: jest.fn().mockName('handleEditShipping'),
+ isLoading: false,
+ isSignedIn: true,
shippingData: 'Shipping Data'
});
@@ -39,7 +52,7 @@ test('renders card state with data', () => {
test('renders form state without data', () => {
useShippingInformation.mockReturnValueOnce({
doneEditing: false,
- loading: false,
+ isLoading: false,
shippingData: 'Shipping Data'
});
diff --git a/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/editModal.js b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/editModal.js
index 07922b4da2..65fbe73df6 100644
--- a/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/editModal.js
+++ b/packages/venia-ui/lib/components/CheckoutPage/ShippingInformation/editModal.js
@@ -6,7 +6,7 @@ import { useEditModal } from '@magento/peregrine/lib/talons/CheckoutPage/Shippin
import { mergeClasses } from '../../../classify';
import Icon from '../../Icon';
import { Modal } from '../../Modal';
-import EditForm from './EditForm';
+import AddressForm from './AddressForm';
import defaultClasses from './editModal.css';
const EditModal = props => {
@@ -19,7 +19,7 @@ const EditModal = props => {
// Unmount the form to force a reset back to original values on close
const bodyElement = isOpen ? (
-
{
- const { classes: propClasses, onSave } = props;
+ const { classes: propClasses, onSave, toggleActiveContent } = props;
const talonProps = useShippingInformation({
onSave,
+ toggleActiveContent,
...ShippingInformationOperations
});
const {
doneEditing,
handleEditShipping,
- loading,
+ hasUpdate,
+ isSignedIn,
+ isLoading,
shippingData
} = talonProps;
const classes = mergeClasses(defaultClasses, propClasses);
- const rootClassName = doneEditing ? classes.root : classes.root_editMode;
+ const rootClassName = !doneEditing
+ ? classes.root_editMode
+ : hasUpdate
+ ? classes.root_updated
+ : classes.root;
- if (loading) {
+ if (isLoading) {
return (
Fetching Shipping Information...
@@ -37,6 +44,10 @@ const ShippingInformation = props => {
);
}
+ const editModal = !isSignedIn ? (
+
+ ) : null;
+
const shippingInformation = doneEditing ? (
@@ -51,16 +62,17 @@ const ShippingInformation = props => {
-
+ {editModal}
) : (
{'1. Shipping Information'}
);
+
return {shippingInformation}
;
};
@@ -75,5 +87,6 @@ ShippingInformation.propTypes = {
editWrapper: string,
editTitle: string
}),
- onSave: func.isRequired
+ onSave: func.isRequired,
+ toggleActiveContent: func.isRequired
};
diff --git a/packages/venia-ui/lib/components/CheckoutPage/__tests__/__snapshots__/checkoutPage.spec.js.snap b/packages/venia-ui/lib/components/CheckoutPage/__tests__/__snapshots__/checkoutPage.spec.js.snap
new file mode 100644
index 0000000000..a3ebb20b74
--- /dev/null
+++ b/packages/venia-ui/lib/components/CheckoutPage/__tests__/__snapshots__/checkoutPage.spec.js.snap
@@ -0,0 +1,321 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`CheckoutPage renders address book for customer 1`] = `
+
+ Title
+
+
+
+ Review and Place Order
+
+
+
+
+
+
+
+ 2. Shipping Method
+
+
+
+
+ 3. Payment Information
+
+
+
+
+
+
+
+
+`;
+
+exports[`CheckoutPage renders checkout content for customer - default address 1`] = `
+
+ Title
+
+
+
+ Review and Place Order
+
+
+
+
+
+
+
+ 2. Shipping Method
+
+
+
+
+ 3. Payment Information
+
+
+
+
+
+
+
+
+`;
+
+exports[`CheckoutPage renders checkout content for customer - no default address 1`] = `
+
+ Title
+
+
+
+ Welcome Eloise!
+
+
+
+
+
+
+
+ 2. Shipping Method
+
+
+
+
+ 3. Payment Information
+
+
+
+
+
+
+
+
+`;
+
+exports[`CheckoutPage renders checkout content for guest 1`] = `
+
+ Title
+
+
+
+
+
+
+ Guest Checkout
+
+
+
+
+
+
+
+ 2. Shipping Method
+
+
+
+
+ 3. Payment Information
+
+
+
+
+
+
+
+`;
+
+exports[`CheckoutPage renders loading indicator 1`] = `
+
+
+
+
+
+ Fetching Data...
+
+
+`;
diff --git a/packages/venia-ui/lib/components/CheckoutPage/__tests__/checkoutPage.spec.js b/packages/venia-ui/lib/components/CheckoutPage/__tests__/checkoutPage.spec.js
index d5f7c7ba83..084c6f56bc 100644
--- a/packages/venia-ui/lib/components/CheckoutPage/__tests__/checkoutPage.spec.js
+++ b/packages/venia-ui/lib/components/CheckoutPage/__tests__/checkoutPage.spec.js
@@ -33,20 +33,25 @@ jest.mock('@magento/peregrine/lib/talons/CheckoutPage/useCheckoutPage', () => {
};
});
+jest.mock('../../../classify');
+
jest.mock('../../../components/Head', () => ({ Title: () => 'Title' }));
jest.mock('../ItemsReview', () => 'ItemsReview');
jest.mock('../OrderSummary', () => 'OrderSummary');
jest.mock('../OrderConfirmationPage', () => 'OrderConfirmationPage');
jest.mock('../ShippingInformation', () => 'ShippingInformation');
jest.mock('../ShippingMethod', () => 'ShippingMethod');
-jest.mock('../PaymentInformation', () => 'Payment Information');
-jest.mock('../PriceAdjustments', () => 'Price Adjustments');
+jest.mock('../PaymentInformation', () => 'PaymentInformation');
+jest.mock('../PriceAdjustments', () => 'PriceAdjustments');
+jest.mock('../AddressBook', () => 'AddressBook');
const defaultTalonProps = {
+ activeContent: 'checkout',
checkoutStep: 1,
+ customer: null,
error: false,
- handleSignIn: jest.fn(),
- handlePlaceOrder: jest.fn(),
+ handleSignIn: jest.fn().mockName('handleSignIn'),
+ handlePlaceOrder: jest.fn().mockName('handlePlaceOrder'),
hasError: false,
isCartEmpty: false,
isGuestCheckout: true,
@@ -56,10 +61,13 @@ const defaultTalonProps = {
orderDetailsLoading: false,
orderNumber: 1,
placeOrderLoading: false,
- setIsUpdating: jest.fn(),
- setShippingInformationDone: jest.fn(),
- setShippingMethodDone: jest.fn(),
- setPaymentInformationDone: jest.fn()
+ setIsUpdating: jest.fn().mockName('setIsUpdating'),
+ setShippingInformationDone: jest
+ .fn()
+ .mockName('setShippingInformationDone'),
+ setShippingMethodDone: jest.fn().mockName('setShippingMethodDone'),
+ setPaymentInformationDone: jest.fn().mockName('setPaymentInformationDone'),
+ toggleActiveContent: jest.fn().mockName('toggleActiveContent')
};
describe('CheckoutPage', () => {
test('throws a toast if there is an error', () => {
@@ -102,4 +110,54 @@ describe('CheckoutPage', () => {
expect(button).toBeTruthy();
expect(button.props.disabled).toBe(true);
});
+
+ test('renders loading indicator', () => {
+ useCheckoutPage.mockReturnValueOnce({
+ isLoading: true
+ });
+
+ const tree = createTestInstance();
+ expect(tree.toJSON()).toMatchSnapshot();
+ });
+
+ test('renders checkout content for guest', () => {
+ useCheckoutPage.mockReturnValueOnce(defaultTalonProps);
+
+ const tree = createTestInstance();
+ expect(tree.toJSON()).toMatchSnapshot();
+ });
+
+ test('renders checkout content for customer - no default address', () => {
+ useCheckoutPage.mockReturnValueOnce({
+ ...defaultTalonProps,
+ customer: { default_shipping: null, firstname: 'Eloise' },
+ isGuestCheckout: false
+ });
+
+ const tree = createTestInstance();
+ expect(tree.toJSON()).toMatchSnapshot();
+ });
+
+ test('renders checkout content for customer - default address', () => {
+ useCheckoutPage.mockReturnValueOnce({
+ ...defaultTalonProps,
+ customer: { default_shipping: '1' },
+ isGuestCheckout: false
+ });
+
+ const tree = createTestInstance();
+ expect(tree.toJSON()).toMatchSnapshot();
+ });
+
+ test('renders address book for customer', () => {
+ useCheckoutPage.mockReturnValueOnce({
+ ...defaultTalonProps,
+ activeContent: 'addressBook',
+ customer: { default_shipping: '1' },
+ isGuestCheckout: false
+ });
+
+ const tree = createTestInstance();
+ expect(tree.toJSON()).toMatchSnapshot();
+ });
});
diff --git a/packages/venia-ui/lib/components/CheckoutPage/checkoutPage.css b/packages/venia-ui/lib/components/CheckoutPage/checkoutPage.css
index 650a134b99..810dd976f2 100644
--- a/packages/venia-ui/lib/components/CheckoutPage/checkoutPage.css
+++ b/packages/venia-ui/lib/components/CheckoutPage/checkoutPage.css
@@ -2,11 +2,19 @@
padding: 2.5rem 3rem;
max-width: 1080px;
margin: 0 auto;
+}
+
+.checkoutContent {
display: grid;
gap: 2rem;
grid-template-columns: 2fr 1fr;
}
+.checkoutContent_hidden {
+ composes: checkoutContent;
+ display: none;
+}
+
.heading_container {
display: flex;
align-items: baseline;
@@ -99,6 +107,9 @@
.root {
padding-left: 1.5rem;
padding-right: 1.5rem;
+ }
+
+ .checkoutContent {
/* Only one column in mobile view. */
grid-template-columns: 1fr;
gap: 1rem;
diff --git a/packages/venia-ui/lib/components/CheckoutPage/checkoutPage.gql.js b/packages/venia-ui/lib/components/CheckoutPage/checkoutPage.gql.js
index 83f1886840..11d8ecb75c 100644
--- a/packages/venia-ui/lib/components/CheckoutPage/checkoutPage.gql.js
+++ b/packages/venia-ui/lib/components/CheckoutPage/checkoutPage.gql.js
@@ -42,6 +42,16 @@ export const GET_CHECKOUT_DETAILS = gql`
${CheckoutPageFragment}
`;
+export const GET_CUSTOMER = gql`
+ query GetCustomer {
+ customer {
+ id
+ default_shipping
+ firstname
+ }
+ }
+`;
+
export default {
mutations: {
createCartMutation: CREATE_CART,
@@ -49,6 +59,7 @@ export default {
},
queries: {
getCheckoutDetailsQuery: GET_CHECKOUT_DETAILS,
+ getCustomerQuery: GET_CUSTOMER,
getOrderDetailsQuery: GET_ORDER_DETAILS
}
};
diff --git a/packages/venia-ui/lib/components/CheckoutPage/checkoutPage.js b/packages/venia-ui/lib/components/CheckoutPage/checkoutPage.js
index 1da21f78cc..a44cf88723 100644
--- a/packages/venia-ui/lib/components/CheckoutPage/checkoutPage.js
+++ b/packages/venia-ui/lib/components/CheckoutPage/checkoutPage.js
@@ -1,4 +1,4 @@
-import React, { Fragment, useEffect } from 'react';
+import React, { useEffect } from 'react';
import { AlertCircle as AlertCircleIcon } from 'react-feather';
import { useWindowSize, useToasts } from '@magento/peregrine';
@@ -11,6 +11,7 @@ import { Title } from '../../components/Head';
import Button from '../Button';
import Icon from '../Icon';
import { fullPageLoadingIndicator } from '../LoadingIndicator';
+import AddressBook from './AddressBook';
import OrderSummary from './OrderSummary';
import PaymentInformation from './PaymentInformation';
import PriceAdjustments from './PriceAdjustments';
@@ -38,7 +39,9 @@ const CheckoutPage = props => {
* Enum, one of:
* SHIPPING_ADDRESS, SHIPPING_METHOD, PAYMENT, REVIEW
*/
+ activeContent,
checkoutStep,
+ customer,
error,
handleSignIn,
handlePlaceOrder,
@@ -58,7 +61,8 @@ const CheckoutPage = props => {
setPaymentInformationDone,
resetReviewOrderButtonClicked,
handleReviewOrder,
- reviewOrderButtonClicked
+ reviewOrderButtonClicked,
+ toggleActiveContent
} = talonProps;
const [, { addToast }] = useToasts();
@@ -85,7 +89,7 @@ const CheckoutPage = props => {
const windowSize = useWindowSize();
const isMobile = windowSize.innerWidth <= 960;
- let content;
+ let checkoutContent;
if (isLoading) {
return fullPageLoadingIndicator;
}
@@ -98,7 +102,7 @@ const CheckoutPage = props => {
/>
);
} else if (isCartEmpty) {
- content = (
+ checkoutContent = (
@@ -201,10 +205,17 @@ const CheckoutPage = props => {
const guestCheckoutHeaderText = isGuestCheckout
? 'Guest Checkout'
- : 'Review and Place Order';
+ : customer.default_shipping
+ ? 'Review and Place Order'
+ : `Welcome ${customer.firstname}!`;
- content = (
-
+ const checkoutContentClass =
+ activeContent === 'checkout'
+ ? classes.checkoutContent
+ : classes.checkoutContent_hidden;
+
+ checkoutContent = (
+
{loginButton}
@@ -212,7 +223,10 @@ const CheckoutPage = props => {
-
+
{shippingMethodSection}
@@ -225,14 +239,22 @@ const CheckoutPage = props => {
{itemsReview}
{orderSummary}
{placeOrderButton}
-
+
);
}
+ const addressBookElement = !isGuestCheckout ? (
+
+ ) : null;
+
return (
{`Checkout - ${STORE_NAME}`}
- {content}
+ {checkoutContent}
+ {addressBookElement}
);
};
diff --git a/packages/venia-ui/lib/components/Region/region.js b/packages/venia-ui/lib/components/Region/region.js
index 3f94464558..89c3ca79fb 100644
--- a/packages/venia-ui/lib/components/Region/region.js
+++ b/packages/venia-ui/lib/components/Region/region.js
@@ -9,20 +9,28 @@ import TextInput from '../TextInput';
import defaultClasses from './region.css';
import { GET_REGIONS_QUERY } from './region.gql';
+/**
+ * Form component for Region that is seeded with backend data.
+ *
+ * @param {string} props.optionValueKey - Key to use for returned option values. In a future release, this will be removed and hard-coded to use "id" once GraphQL has resolved MC-30886.
+ */
const Region = props => {
- const talonProps = useRegion({
- queries: { getRegionsQuery: GET_REGIONS_QUERY }
- });
- const { regions } = talonProps;
const {
classes: propClasses,
field,
label,
validate,
initialValue,
+ optionValueKey,
...inputProps
} = props;
+ const talonProps = useRegion({
+ optionValueKey,
+ queries: { getRegionsQuery: GET_REGIONS_QUERY }
+ });
+ const { regions } = talonProps;
+
const classes = mergeClasses(defaultClasses, propClasses);
const regionProps = {
field,
@@ -49,7 +57,8 @@ export default Region;
Region.defaultProps = {
field: 'region',
- label: 'State'
+ label: 'State',
+ optionValueKey: 'code'
};
Region.propTypes = {
@@ -58,6 +67,7 @@ Region.propTypes = {
}),
field: string,
label: string,
+ optionValueKey: string,
validate: func,
initialValue: string
};
diff --git a/packages/venia-ui/lib/components/TextInput/textInput.css b/packages/venia-ui/lib/components/TextInput/textInput.css
index 1d9f685126..3968880594 100644
--- a/packages/venia-ui/lib/components/TextInput/textInput.css
+++ b/packages/venia-ui/lib/components/TextInput/textInput.css
@@ -1,3 +1,7 @@
.input {
composes: input from '../Field/field.css';
}
+
+.input:disabled {
+ color: rgb(var(--venia-grey-darker));
+}
diff --git a/packages/venia-ui/lib/util/apolloCache.js b/packages/venia-ui/lib/util/apolloCache.js
index f4059f2230..fe751ea57b 100644
--- a/packages/venia-ui/lib/util/apolloCache.js
+++ b/packages/venia-ui/lib/util/apolloCache.js
@@ -7,6 +7,7 @@ export const MagentoGraphQLTypes = {
BundleProduct: 'BundleProduct',
Cart: 'Cart',
ConfigurableProduct: 'ConfigurableProduct',
+ Customer: 'Customer',
DownloadableProduct: 'DownloadableProduct',
GiftCardProduct: 'GiftCardProduct',
GroupedProduct: 'GroupedProduct',
@@ -53,7 +54,10 @@ export const cacheKeyFromType = object => {
: null;
// Only maintain a single cart entry
case MagentoGraphQLTypes.Cart:
- return 'Cart';
+ return MagentoGraphQLTypes.Cart;
+ // Only maintain single customer entry
+ case MagentoGraphQLTypes.Customer:
+ return MagentoGraphQLTypes.Customer;
// Fallback to default handling.
default:
return defaultDataIdFromObject(object);