diff --git a/packages/peregrine/lib/talons/CheckoutPage/ItemsReview/__fixtures__/cartItems.js b/packages/peregrine/lib/talons/CheckoutPage/ItemsReview/__fixtures__/cartItems.js index 93ebf7be41..e2dac48fe3 100644 --- a/packages/peregrine/lib/talons/CheckoutPage/ItemsReview/__fixtures__/cartItems.js +++ b/packages/peregrine/lib/talons/CheckoutPage/ItemsReview/__fixtures__/cartItems.js @@ -1,102 +1,129 @@ -export default { - cart: { - id: 'GXtkt675mPd3gYuvhWLd5iw5ekVoDj1b', - total_quantity: 7, - items: [ +export default [ + { + id: '29568', + product: { + id: 1093, + name: 'Jillian Top', + thumbnail: { + url: + 'https://master-7rqtwti-c5v7sxvquxwl4.us-4.magentosite.cloud/media/catalog/product/cache/d3ba9f7bcd3b0724e976dc5144b29c7d/v/t/vt12-kh_main_2.jpg', + __typename: 'ProductImage' + }, + __typename: 'ConfigurableProduct' + }, + quantity: 3, + configurable_options: [ + { + configurable_product_option_uid: 179, + option_label: 'Fashion Color', + configurable_product_option_value_uid: 18, + value_label: 'Peach', + __typename: 'SelectedConfigurableOption' + }, + { + configurable_product_option_uid: 182, + option_label: 'Fashion Size', + configurable_product_option_value_uid: 27, + value_label: 'M', + __typename: 'SelectedConfigurableOption' + } + ], + __typename: 'ConfigurableCartItem' + }, + { + id: '29570', + product: { + id: 1115, + name: 'Juno Sweater', + thumbnail: { + url: + 'https://master-7rqtwti-c5v7sxvquxwl4.us-4.magentosite.cloud/media/catalog/product/cache/d3ba9f7bcd3b0724e976dc5144b29c7d/v/s/vsw02-pe_main_2.jpg', + __typename: 'ProductImage' + }, + __typename: 'ConfigurableProduct' + }, + quantity: 1, + configurable_options: [ + { + configurable_product_option_uid: 179, + option_label: 'Fashion Color', + configurable_product_option_value_uid: 21, + value_label: 'Rain', + __typename: 'SelectedConfigurableOption' + }, { - id: '29568', - product: { - id: 1093, - name: 'Jillian Top', - thumbnail: { - url: - 'https://master-7rqtwti-c5v7sxvquxwl4.eu-4.magentosite.cloud/media/catalog/product/cache/d3ba9f7bcd3b0724e976dc5144b29c7d/v/t/vt12-kh_main_2.jpg', - __typename: 'ProductImage' - }, - __typename: 'ConfigurableProduct' - }, - quantity: 3, - configurable_options: [ - { - configurable_product_option_uid: 179, - option_label: 'Fashion Color', - configurable_product_option_value_uid: 18, - value_label: 'Peach', - __typename: 'SelectedConfigurableOption' - }, - { - configurable_product_option_uid: 182, - option_label: 'Fashion Size', - configurable_product_option_value_uid: 27, - value_label: 'M', - __typename: 'SelectedConfigurableOption' - } - ], - __typename: 'ConfigurableCartItem' + configurable_product_option_uid: 182, + option_label: 'Fashion Size', + configurable_product_option_value_uid: 29, + value_label: 'XS', + __typename: 'SelectedConfigurableOption' + } + ], + __typename: 'ConfigurableCartItem' + }, + { + id: '29572', + product: { + id: 1152, + name: 'Angelina Tank Dress', + thumbnail: { + url: + 'https://master-7rqtwti-c5v7sxvquxwl4.us-4.magentosite.cloud/media/catalog/product/cache/d3ba9f7bcd3b0724e976dc5144b29c7d/v/d/vd01-ll_main_2.jpg', + __typename: 'ProductImage' + }, + __typename: 'ConfigurableProduct' + }, + quantity: 3, + configurable_options: [ + { + configurable_product_option_uid: 179, + option_label: 'Fashion Color', + configurable_product_option_value_uid: 20, + value_label: 'Lilac', + __typename: 'SelectedConfigurableOption' + }, + { + configurable_product_option_uid: 182, + option_label: 'Fashion Size', + configurable_product_option_value_uid: 26, + value_label: 'L', + __typename: 'SelectedConfigurableOption' + } + ], + __typename: 'ConfigurableCartItem' + } +]; + +export const singleItem = [ + { + id: '29568', + product: { + id: 1093, + name: 'Jillian Top', + thumbnail: { + url: + 'https://master-7rqtwti-c5v7sxvquxwl4.us-4.magentosite.cloud/media/catalog/product/cache/d3ba9f7bcd3b0724e976dc5144b29c7d/v/t/vt12-kh_main_2.jpg', + __typename: 'ProductImage' }, + __typename: 'ConfigurableProduct' + }, + quantity: 3, + configurable_options: [ { - id: '29570', - product: { - id: 1115, - name: 'Juno Sweater', - thumbnail: { - url: - 'https://master-7rqtwti-c5v7sxvquxwl4.eu-4.magentosite.cloud/media/catalog/product/cache/d3ba9f7bcd3b0724e976dc5144b29c7d/v/s/vsw02-pe_main_2.jpg', - __typename: 'ProductImage' - }, - __typename: 'ConfigurableProduct' - }, - quantity: 1, - configurable_options: [ - { - configurable_product_option_uid: 179, - option_label: 'Fashion Color', - configurable_product_option_value_uid: 21, - value_label: 'Rain', - __typename: 'SelectedConfigurableOption' - }, - { - configurable_product_option_uid: 182, - option_label: 'Fashion Size', - configurable_product_option_value_uid: 29, - value_label: 'XS', - __typename: 'SelectedConfigurableOption' - } - ], - __typename: 'ConfigurableCartItem' + configurable_product_option_uid: 179, + option_label: 'Fashion Color', + configurable_product_option_value_uid: 18, + value_label: 'Peach', + __typename: 'SelectedConfigurableOption' }, { - id: '29572', - product: { - id: 1152, - name: 'Angelina Tank Dress', - thumbnail: { - url: - 'https://master-7rqtwti-c5v7sxvquxwl4.eu-4.magentosite.cloud/media/catalog/product/cache/d3ba9f7bcd3b0724e976dc5144b29c7d/v/d/vd01-ll_main_2.jpg', - __typename: 'ProductImage' - }, - __typename: 'ConfigurableProduct' - }, - quantity: 3, - configurable_options: [ - { - configurable_product_option_uid: 179, - option_label: 'Fashion Color', - configurable_product_option_value_uid: 20, - value_label: 'Lilac', - __typename: 'SelectedConfigurableOption' - }, - { - configurable_product_option_uid: 182, - option_label: 'Fashion Size', - configurable_product_option_value_uid: 26, - value_label: 'L', - __typename: 'SelectedConfigurableOption' - } - ], - __typename: 'ConfigurableCartItem' + configurable_product_option_uid: 182, + option_label: 'Fashion Size', + configurable_product_option_value_uid: 27, + value_label: 'M', + __typename: 'SelectedConfigurableOption' } ], - __typename: 'Cart' + __typename: 'ConfigurableCartItem' } -}; +]; diff --git a/packages/peregrine/lib/talons/CheckoutPage/ItemsReview/__tests__/__snapshots__/useItemsReview.spec.js.snap b/packages/peregrine/lib/talons/CheckoutPage/ItemsReview/__tests__/__snapshots__/useItemsReview.spec.js.snap index 6f543c1a49..34821e73ac 100644 --- a/packages/peregrine/lib/talons/CheckoutPage/ItemsReview/__tests__/__snapshots__/useItemsReview.spec.js.snap +++ b/packages/peregrine/lib/talons/CheckoutPage/ItemsReview/__tests__/__snapshots__/useItemsReview.spec.js.snap @@ -3,27 +3,100 @@ exports[`returns correct shape 1`] = ` Object { "configurableThumbnailSource": "parent", - "hasErrors": false, - "isLoading": true, - "items": Array [], - "setShowAllItems": [Function], - "showAllItems": false, - "totalQuantity": 0, -} -`; - -exports[`uses static data if provided 1`] = ` -Object { - "configurableThumbnailSource": "parent", - "hasErrors": false, - "isLoading": true, "items": Array [ Object { - "name": "static item", + "__typename": "ConfigurableCartItem", + "configurable_options": Array [ + Object { + "__typename": "SelectedConfigurableOption", + "configurable_product_option_uid": 179, + "configurable_product_option_value_uid": 18, + "option_label": "Fashion Color", + "value_label": "Peach", + }, + Object { + "__typename": "SelectedConfigurableOption", + "configurable_product_option_uid": 182, + "configurable_product_option_value_uid": 27, + "option_label": "Fashion Size", + "value_label": "M", + }, + ], + "id": "29568", + "product": Object { + "__typename": "ConfigurableProduct", + "id": 1093, + "name": "Jillian Top", + "thumbnail": Object { + "__typename": "ProductImage", + "url": "https://master-7rqtwti-c5v7sxvquxwl4.us-4.magentosite.cloud/media/catalog/product/cache/d3ba9f7bcd3b0724e976dc5144b29c7d/v/t/vt12-kh_main_2.jpg", + }, + }, + "quantity": 3, + }, + Object { + "__typename": "ConfigurableCartItem", + "configurable_options": Array [ + Object { + "__typename": "SelectedConfigurableOption", + "configurable_product_option_uid": 179, + "configurable_product_option_value_uid": 21, + "option_label": "Fashion Color", + "value_label": "Rain", + }, + Object { + "__typename": "SelectedConfigurableOption", + "configurable_product_option_uid": 182, + "configurable_product_option_value_uid": 29, + "option_label": "Fashion Size", + "value_label": "XS", + }, + ], + "id": "29570", + "product": Object { + "__typename": "ConfigurableProduct", + "id": 1115, + "name": "Juno Sweater", + "thumbnail": Object { + "__typename": "ProductImage", + "url": "https://master-7rqtwti-c5v7sxvquxwl4.us-4.magentosite.cloud/media/catalog/product/cache/d3ba9f7bcd3b0724e976dc5144b29c7d/v/s/vsw02-pe_main_2.jpg", + }, + }, + "quantity": 1, + }, + Object { + "__typename": "ConfigurableCartItem", + "configurable_options": Array [ + Object { + "__typename": "SelectedConfigurableOption", + "configurable_product_option_uid": 179, + "configurable_product_option_value_uid": 20, + "option_label": "Fashion Color", + "value_label": "Lilac", + }, + Object { + "__typename": "SelectedConfigurableOption", + "configurable_product_option_uid": 182, + "configurable_product_option_value_uid": 26, + "option_label": "Fashion Size", + "value_label": "L", + }, + ], + "id": "29572", + "product": Object { + "__typename": "ConfigurableProduct", + "id": 1152, + "name": "Angelina Tank Dress", + "thumbnail": Object { + "__typename": "ProductImage", + "url": "https://master-7rqtwti-c5v7sxvquxwl4.us-4.magentosite.cloud/media/catalog/product/cache/d3ba9f7bcd3b0724e976dc5144b29c7d/v/d/vd01-ll_main_2.jpg", + }, + }, + "quantity": 3, }, ], "setShowAllItems": [Function], - "showAllItems": true, - "totalQuantity": 1, + "showAllItems": false, + "totalQuantity": 7, } `; diff --git a/packages/peregrine/lib/talons/CheckoutPage/ItemsReview/__tests__/useItemsReview.spec.js b/packages/peregrine/lib/talons/CheckoutPage/ItemsReview/__tests__/useItemsReview.spec.js index 4bd9e02804..73b9cdad47 100644 --- a/packages/peregrine/lib/talons/CheckoutPage/ItemsReview/__tests__/useItemsReview.spec.js +++ b/packages/peregrine/lib/talons/CheckoutPage/ItemsReview/__tests__/useItemsReview.spec.js @@ -1,40 +1,21 @@ import React from 'react'; -import { useLazyQuery, useQuery } from '@apollo/client'; +import { useQuery } from '@apollo/client'; import createTestInstance from '../../../../../lib/util/createTestInstance'; import { useItemsReview } from '../useItemsReview'; -import cartItems from '../__fixtures__/cartItems'; +import cartItems, { singleItem } from '../__fixtures__/cartItems'; jest.mock('@apollo/client', () => { const apolloClient = jest.requireActual('@apollo/client'); return { ...apolloClient, - useQuery: jest.fn(), - useLazyQuery: jest.fn() + useQuery: jest.fn() }; }); -jest.mock('../../../../context/cart', () => { - const state = { - cartId: 'cart123' - }; - const api = {}; - const useCartContext = jest.fn(() => [state, api]); - - return { useCartContext }; -}); - beforeAll(() => { - useLazyQuery.mockReturnValue([ - () => {}, - { - data: null, - error: null, - loading: true - } - ]); useQuery.mockReturnValue({ data: { storeConfig: { @@ -52,98 +33,48 @@ const Component = props => { }; test('returns correct shape', () => { - const tree = createTestInstance( - - ); + const tree = createTestInstance(); const { root } = tree; const { talonProps } = root.findByType('i').props; expect(talonProps).toMatchSnapshot(); }); -test('uses static data if provided', () => { - const queries = { getItemsInCart: jest.fn() }; - const data = { - cart: { - items: [ - { - name: 'static item' - } - ], - total_quantity: 1 - } - }; - const tree = createTestInstance( - - ); +test('Should return correct total quantity', () => { + const tree = createTestInstance(); const { root } = tree; const { talonProps } = root.findByType('i').props; - expect(talonProps).toMatchSnapshot(); -}); + const expectedQuantity = 7; -test('Should return total quantity from gql query', () => { - useLazyQuery.mockReturnValueOnce([ - () => {}, - { - data: cartItems, - error: null, - loading: false - } - ]); - const tree = createTestInstance( - - ); - const { root } = tree; - const { talonProps } = root.findByType('i').props; - - expect(talonProps.totalQuantity).toBe(cartItems.cart.total_quantity); + expect(talonProps.totalQuantity).toBe(expectedQuantity); }); -test('Should return 0 for total quantity if gql does not return total_quality', () => { - const newCartItems = { - ...cartItems - }; - newCartItems.cart.total_quantity = null; - useLazyQuery.mockReturnValueOnce([ - () => {}, - { - data: newCartItems, - error: null, - loading: false - } - ]); - const tree = createTestInstance( - - ); +test('Should cases where no item data is provided', () => { + const tree = createTestInstance(); const { root } = tree; const { talonProps } = root.findByType('i').props; expect(talonProps.totalQuantity).toBe(0); + expect(talonProps.items).toEqual([]); }); -test('hasErrors in return props should be set to true if gql throws any errors', () => { - useLazyQuery.mockReturnValueOnce([ - () => {}, - { data: null, loading: false, error: 'some error' } - ]); +test('handles no configurable thumbnail source data', () => { + useQuery.mockReturnValueOnce({}); + const tree = createTestInstance( ); const { root } = tree; const { talonProps } = root.findByType('i').props; - expect(talonProps.hasErrors).toBeTruthy(); + expect(talonProps.configurableThumbnailSource).toBeUndefined(); }); -test('handles no configurable thumbnail source data', () => { - useQuery.mockReturnValueOnce({}); - - const tree = createTestInstance( - - ); +test('set show all items to true when there is less than two items in the cart', () => { + const tree = createTestInstance(); const { root } = tree; const { talonProps } = root.findByType('i').props; - expect(talonProps.configurableThumbnailSource).toBeUndefined(); + expect(talonProps.showAllItems).toBeTruthy(); }); diff --git a/packages/peregrine/lib/talons/CheckoutPage/ItemsReview/useItemsReview.js b/packages/peregrine/lib/talons/CheckoutPage/ItemsReview/useItemsReview.js index 03298b8a5d..28f1194285 100644 --- a/packages/peregrine/lib/talons/CheckoutPage/ItemsReview/useItemsReview.js +++ b/packages/peregrine/lib/talons/CheckoutPage/ItemsReview/useItemsReview.js @@ -1,7 +1,6 @@ import { useEffect, useState, useCallback, useMemo } from 'react'; -import { useLazyQuery, useQuery } from '@apollo/client'; +import { useQuery } from '@apollo/client'; -import { useCartContext } from '../../../context/cart'; import mergeOperations from '../../../util/shallowMerge'; import DEFAULT_OPERATIONS from './itemsReview.gql'; @@ -9,9 +8,9 @@ export const useItemsReview = props => { const [showAllItems, setShowAllItems] = useState(false); const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations); - const { getItemsInCart, getConfigurableThumbnailSource } = operations; + const { getConfigurableThumbnailSource } = operations; - const [{ cartId }] = useCartContext(); + const { items: itemsData } = props; const { data: configurableThumbnailSourceData } = useQuery( getConfigurableThumbnailSource, @@ -27,48 +26,29 @@ export const useItemsReview = props => { } }, [configurableThumbnailSourceData]); - const [ - fetchItemsInCart, - { data: queryData, error, loading } - ] = useLazyQuery(getItemsInCart, { - fetchPolicy: 'cache-and-network' - }); - - // If static data was provided, use that instead of query data. - const data = props.data || queryData; - const setShowAllItemsFlag = useCallback(() => setShowAllItems(true), [ setShowAllItems ]); - useEffect(() => { - if (cartId && !props.data) { - fetchItemsInCart({ - variables: { - cartId - } - }); - } - }, [cartId, fetchItemsInCart, props.data]); - useEffect(() => { /** * If there are 2 or less than 2 items in cart * set show all items to `true`. */ - if (data && data.cart && data.cart.items.length <= 2) { + if (itemsData && itemsData.length <= 2) { setShowAllItems(true); } - }, [data]); + }, [itemsData]); - const items = data ? data.cart.items : []; + const items = itemsData || []; - const totalQuantity = data ? +data.cart.total_quantity : 0; + const totalQuantity = items.reduce( + (previousValue, currentValue) => previousValue + currentValue.quantity, + 0 + ); return { - isLoading: !!loading, items, - hasErrors: !!error, totalQuantity, showAllItems, setShowAllItems: setShowAllItemsFlag, diff --git a/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/__tests__/__snapshots__/useOrderConfirmationPage.spec.js.snap b/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/__tests__/__snapshots__/useOrderConfirmationPage.spec.js.snap index aa2b8a1207..fc533a8265 100644 --- a/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/__tests__/__snapshots__/useOrderConfirmationPage.spec.js.snap +++ b/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/__tests__/__snapshots__/useOrderConfirmationPage.spec.js.snap @@ -1,7 +1,8 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`returns the correct shape 1`] = ` +exports[`for authenticated customers returns the correct shape 1`] = ` Object { + "error": undefined, "flatData": Object { "city": "city", "country": "country", @@ -14,8 +15,29 @@ Object { "street": Array [ "street", ], - "totalItemQuantity": 1, }, "isSignedIn": true, + "loading": false, +} +`; + +exports[`for guest returns the correct shape 1`] = ` +Object { + "error": undefined, + "flatData": Object { + "city": "city", + "country": "country", + "email": "email", + "firstname": "firstname", + "lastname": "lastname", + "postcode": "postcode", + "region": "region", + "shippingMethod": "carrier - method", + "street": Array [ + "street", + ], + }, + "isSignedIn": false, + "loading": undefined, } `; diff --git a/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/__tests__/useOrderConfirmationPage.spec.js b/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/__tests__/useOrderConfirmationPage.spec.js index 8772608a1b..de70d4354a 100644 --- a/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/__tests__/useOrderConfirmationPage.spec.js +++ b/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/__tests__/useOrderConfirmationPage.spec.js @@ -1,8 +1,14 @@ import React from 'react'; -import { flatten, useOrderConfirmationPage } from '../useOrderConfirmationPage'; +import { + flattenCustomerOrderData, + flattenGuestCartData, + useOrderConfirmationPage +} from '../useOrderConfirmationPage'; import createTestInstance from '../../../../../lib/util/createTestInstance'; +import { useLazyQuery } from '@apollo/client'; + import { useUserContext } from '../../../../context/user'; jest.mock('../../../../context/user'); @@ -14,13 +20,21 @@ useUserContext.mockImplementation(() => { ]; }); +jest.mock('@apollo/client', () => { + const apolloClient = jest.requireActual('@apollo/client'); + return { + ...apolloClient, + useLazyQuery: jest.fn().mockReturnValue([jest.fn(), {}]) + }; +}); + const Component = props => { const talonProps = useOrderConfirmationPage(props); return ; }; -const mockData = { +const mockGuestCartData = { cart: { email: 'email', shipping_addresses: [ @@ -46,33 +60,112 @@ const mockData = { total_quantity: 1 } }; + +const mockCustomerOrderData = { + customer: { + email: 'email', + orders: { + items: [ + { + shipping_address: { + firstname: 'firstname', + lastname: 'lastname', + street: ['street'], + city: 'city', + region: 'region', + postcode: 'postcode', + country_code: 'country' + }, + shipping_method: 'carrier - method' + } + ] + } + } +}; + const DEFAULT_PROPS = { - data: mockData + data: mockGuestCartData +}; + +const expectedFlatData = { + city: 'city', + country: 'country', + email: 'email', + firstname: 'firstname', + lastname: 'lastname', + postcode: 'postcode', + region: 'region', + shippingMethod: 'carrier - method', + street: ['street'] }; -describe('#flatten', () => { +describe('#flattenGuestCartData', () => { + it('returns flat cart data', () => { + expect(flattenGuestCartData(mockGuestCartData)).toEqual( + expectedFlatData + ); + }); + + it('returns nothing when there is no data given', () => { + expect(flattenGuestCartData(undefined)).toBeUndefined(); + }); +}); + +describe('#flattenCustomerOrderData', () => { it('returns flat cart data', () => { - const expected = { - city: 'city', - country: 'country', - email: 'email', - firstname: 'firstname', - lastname: 'lastname', - postcode: 'postcode', - region: 'region', - shippingMethod: 'carrier - method', - street: ['street'], - totalItemQuantity: 1 - }; - expect(flatten(mockData)).toEqual(expected); + expect(flattenCustomerOrderData(mockCustomerOrderData)).toEqual( + expectedFlatData + ); + }); + it('returns nothing when there is no data given', () => { + expect(flattenCustomerOrderData(undefined)).toBeUndefined(); }); }); -it('returns the correct shape', () => { - const tree = createTestInstance(); +describe('for guest', () => { + it('returns the correct shape', () => { + useUserContext.mockImplementationOnce(() => { + return [ + { + isSignedIn: false + } + ]; + }); + const tree = createTestInstance(); + + const { root } = tree; + const { talonProps } = root.findByType('i').props; - const { root } = tree; - const { talonProps } = root.findByType('i').props; + expect(talonProps).toMatchSnapshot(); + }); +}); - expect(talonProps).toMatchSnapshot(); +describe('for authenticated customers', () => { + it('returns the correct shape', () => { + const mockFetch = jest.fn(); + + useLazyQuery.mockReturnValueOnce([ + mockFetch, + { + data: mockCustomerOrderData, + error: undefined, + loading: false + } + ]); + + const mockOrderNumber = '12345'; + + const tree = createTestInstance( + + ); + + const { root } = tree; + const { talonProps } = root.findByType('i').props; + + expect(talonProps).toMatchSnapshot(); + + expect(mockFetch).toHaveBeenCalledWith({ + variables: { orderNumber: mockOrderNumber } + }); + }); }); diff --git a/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/orderConfirmationPage.gql.js b/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/orderConfirmationPage.gql.js new file mode 100644 index 0000000000..4c67d9e7cf --- /dev/null +++ b/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/orderConfirmationPage.gql.js @@ -0,0 +1,30 @@ +import { gql } from '@apollo/client'; + +export const GET_ORDER_CONFIRMATION_DETAILS = gql` + query getOrderConfirmationDetails($orderNumber: String!) { + # eslint-disable-next-line @graphql-eslint/require-id-when-available + customer { + email + # eslint-disable-next-line @graphql-eslint/require-id-when-available + orders(filter: { number: { eq: $orderNumber } }) { + items { + id + shipping_address { + firstname + lastname + street + city + region + postcode + country_code + } + shipping_method + } + } + } + } +`; + +export default { + getOrderConfirmationDetailsQuery: GET_ORDER_CONFIRMATION_DETAILS +}; diff --git a/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/useOrderConfirmationPage.js b/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/useOrderConfirmationPage.js index 31b6430974..2c7f75e185 100644 --- a/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/useOrderConfirmationPage.js +++ b/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/useOrderConfirmationPage.js @@ -1,6 +1,14 @@ +import { useEffect } from 'react'; import { useUserContext } from '../../../context/user'; +import { useLazyQuery } from '@apollo/client'; -export const flatten = data => { +import mergeOperations from '../../../util/shallowMerge'; +import DEFAULT_OPERATIONS from './orderConfirmationPage.gql'; + +export const flattenGuestCartData = data => { + if (!data) { + return; + } const { cart } = data; const { shipping_addresses } = cart; const address = shipping_addresses[0]; @@ -18,17 +26,60 @@ export const flatten = data => { postcode: address.postcode, region: address.region.label, shippingMethod, + street: address.street + }; +}; + +export const flattenCustomerOrderData = data => { + if (!data) { + return; + } + const { customer } = data; + const order = customer.orders.items[0]; + const { shipping_address: address } = order; + + return { + city: address.city, + country: address.country_code, + email: customer.email, + firstname: address.firstname, + lastname: address.lastname, + postcode: address.postcode, + region: address.region, street: address.street, - totalItemQuantity: cart.total_quantity + shippingMethod: order.shipping_method }; }; export const useOrderConfirmationPage = props => { - const { data } = props; + const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations); + const { getOrderConfirmationDetailsQuery } = operations; + const [{ isSignedIn }] = useUserContext(); + const [ + fetchOrderConfirmationDetails, + { data: queryData, error, loading } + ] = useLazyQuery(getOrderConfirmationDetailsQuery); + + const flatData = + flattenGuestCartData(props.data) || flattenCustomerOrderData(queryData); + + useEffect(() => { + if (props.orderNumber && !props.data) { + const orderNumber = props.orderNumber; + fetchOrderConfirmationDetails({ + variables: { + orderNumber + } + }); + } + }, [props.orderNumber, props.data, fetchOrderConfirmationDetails]); + return { - flatData: flatten(data), - isSignedIn + flatData, + isSignedIn, + error, + loading }; }; diff --git a/packages/peregrine/lib/talons/CheckoutPage/__tests__/useCheckoutPage.spec.js b/packages/peregrine/lib/talons/CheckoutPage/__tests__/useCheckoutPage.spec.js index 216297d2d4..18b3036a0c 100644 --- a/packages/peregrine/lib/talons/CheckoutPage/__tests__/useCheckoutPage.spec.js +++ b/packages/peregrine/lib/talons/CheckoutPage/__tests__/useCheckoutPage.spec.js @@ -6,7 +6,7 @@ import { useQuery } from '@apollo/client'; import { act } from 'react-test-renderer'; - +import { useHistory } from 'react-router-dom'; import { useCheckoutPage, CHECKOUT_STEP } from '../useCheckoutPage'; import createTestInstance from '../../../util/createTestInstance'; import { useCartContext } from '../../../context/cart'; @@ -27,6 +27,11 @@ jest.mock('@apollo/client', () => { useQuery: jest.fn() }; }); +jest.mock('react-router-dom', () => { + return { + useHistory: jest.fn() + }; +}); jest.mock('react', () => ({ ...jest.requireActual('react'), @@ -158,8 +163,10 @@ const getTalonProps = props => { /** * beforeAll */ - +const mockPush = jest.fn(); // Create a mock function for push +const mockHistory = { push: mockPush }; beforeEach(() => { + useHistory.mockReturnValue(mockHistory); useQuery.mockImplementation(query => { if (query === getCheckoutDetailsQuery) { return getCheckoutDetailsQueryResult(); @@ -451,6 +458,7 @@ test('orderDetailsLoading should be loading status of the getOrderDetailsQuery', }); test('orderNumber should be the order_number from the place order mutation result', () => { + useUserContext.mockReturnValueOnce([{ isSignedIn: false }]); placeOrderMutationResult.mockReturnValueOnce([ () => {}, { diff --git a/packages/peregrine/lib/talons/CheckoutPage/useCheckoutPage.js b/packages/peregrine/lib/talons/CheckoutPage/useCheckoutPage.js index e084d05794..d9e99b0978 100644 --- a/packages/peregrine/lib/talons/CheckoutPage/useCheckoutPage.js +++ b/packages/peregrine/lib/talons/CheckoutPage/useCheckoutPage.js @@ -7,6 +7,8 @@ import { } from '@apollo/client'; import { useEventingContext } from '../../context/eventing'; +import { useHistory } from 'react-router-dom'; + import { useUserContext } from '../../context/user'; import { useCartContext } from '../../context/cart'; @@ -68,8 +70,8 @@ export const CHECKOUT_STEP = { * } */ export const useCheckoutPage = (props = {}) => { + const history = useHistory(); const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations); - const { createCartMutation, getCheckoutDetailsQuery, @@ -249,6 +251,7 @@ export const useCheckoutPage = (props = {}) => { }); setPlaceOrderButtonClicked(true); setIsPlacingOrder(true); + localStorage.setItem('orderCount', '1'); }, [cartId, getOrderDetails]); const handlePlaceOrderEnterKeyPress = useCallback(() => { @@ -383,6 +386,16 @@ export const useCheckoutPage = (props = {}) => { isPlacingOrder, reviewOrderButtonClicked ]); + useEffect(() => { + if (isSignedIn && placeOrderData) { + history.push('/order-confirmation', { + orderNumber: placeOrderData.placeOrder.order.order_number, + items: cartItems + }); + } else if (!isSignedIn && placeOrderData) { + history.push('/checkout'); + } + }, [isSignedIn, placeOrderData, cartItems, history]); return { activeContent, diff --git a/packages/venia-ui/lib/components/CheckoutPage/ItemsReview/itemsReview.js b/packages/venia-ui/lib/components/CheckoutPage/ItemsReview/itemsReview.js index ea3f1a75b6..0a2cb13b72 100644 --- a/packages/venia-ui/lib/components/CheckoutPage/ItemsReview/itemsReview.js +++ b/packages/venia-ui/lib/components/CheckoutPage/ItemsReview/itemsReview.js @@ -5,7 +5,6 @@ import { useItemsReview } from '@magento/peregrine/lib/talons/CheckoutPage/Items import Item from './item'; import ShowAllButton from './showAllButton'; -import LoadingIndicator from '../../LoadingIndicator'; import { useStyle } from '../../../classify'; import defaultClasses from './itemsReview.module.css'; @@ -19,16 +18,13 @@ const ItemsReview = props => { const classes = useStyle(defaultClasses, propClasses); - const talonProps = useItemsReview({ - data: props.data - }); + const talonProps = useItemsReview(props); const { items: itemsInCart, totalQuantity, showAllItems, setShowAllItems, - isLoading, configurableThumbnailSource } = talonProps; @@ -45,17 +41,6 @@ const ItemsReview = props => { ) : null; - if (isLoading) { - return ( - - - - ); - } - return (
- +
`; + +exports[`OrderConfirmationPage renders component using location state when there are no props provided 1`] = ` +
+ Title +
+

+ +

+
+ +
+
+ +
+
+ + badvirus@covid.com + + + Stuck Indoors + + + 123 Stir Crazy Dr. + + + Austin, TX 91111 US + +
+
+ +
+
+ Flat Rate - Fixed +
+
+ +
+
+ +
+
+
+
+`; diff --git a/packages/venia-ui/lib/components/CheckoutPage/OrderConfirmationPage/__tests__/orderConfirmationPage.spec.js b/packages/venia-ui/lib/components/CheckoutPage/OrderConfirmationPage/__tests__/orderConfirmationPage.spec.js index 6e0e76ae6e..5811a0f908 100644 --- a/packages/venia-ui/lib/components/CheckoutPage/OrderConfirmationPage/__tests__/orderConfirmationPage.spec.js +++ b/packages/venia-ui/lib/components/CheckoutPage/OrderConfirmationPage/__tests__/orderConfirmationPage.spec.js @@ -5,6 +5,9 @@ import { useOrderConfirmationPage } from '@magento/peregrine/lib/talons/Checkout import OrderConfirmationPage from '../orderConfirmationPage'; import CreateAccount from '../createAccount'; +import { useLocation } from 'react-router-dom'; +import cartItems from '../__fixtures__/cartItems'; + jest.mock('@magento/peregrine', () => { const actual = jest.requireActual('@magento/peregrine'); const useToasts = jest.fn().mockReturnValue([{}, { addToast: jest.fn() }]); @@ -27,6 +30,12 @@ jest.mock('../../../../components/Head', () => ({ StoreTitle: () => 'Title' })); jest.mock('../createAccount', () => 'CreateAccount'); jest.mock('../../ItemsReview', () => 'ItemsReview'); +jest.mock('react-router-dom', () => { + return { + useLocation: jest.fn().mockReturnValue(jest.fn()) + }; +}); + const defaultTalonProps = { flatData: { city: 'Austin', @@ -46,23 +55,51 @@ describe('OrderConfirmationPage', () => { beforeEach(() => { globalThis.scrollTo = jest.fn(); }); + test('renders OrderConfirmationPage component', () => { useOrderConfirmationPage.mockReturnValue({ ...defaultTalonProps }); + const mockData = { + cart: { + items: cartItems + } + }; const instance = createTestInstance( - + ); expect(instance.toJSON()).toMatchSnapshot(); }); + test('renders component using location state when there are no props provided', () => { + useOrderConfirmationPage.mockReturnValue({ + ...defaultTalonProps, + isSignedIn: true + }); + useLocation.mockReturnValue({ + state: { + orderNumber: 123, + items: cartItems + } + }); + const instance = createTestInstance(); + expect(instance.toJSON()).toMatchSnapshot(); + }); + test('renders CreateAccount view if not signed in', () => { useOrderConfirmationPage.mockReturnValueOnce({ ...defaultTalonProps, - isDisabled: true + isSignedIn: false }); + const mockData = { + cart: { + items: cartItems + } + }; - const instance = createTestInstance(); + const instance = createTestInstance( + + ); const component = instance.root.findByType(CreateAccount); expect(component).toBeTruthy(); diff --git a/packages/venia-ui/lib/components/CheckoutPage/OrderConfirmationPage/orderConfirmationPage.js b/packages/venia-ui/lib/components/CheckoutPage/OrderConfirmationPage/orderConfirmationPage.js index 369c6e4012..eaeae5867e 100644 --- a/packages/venia-ui/lib/components/CheckoutPage/OrderConfirmationPage/orderConfirmationPage.js +++ b/packages/venia-ui/lib/components/CheckoutPage/OrderConfirmationPage/orderConfirmationPage.js @@ -3,6 +3,8 @@ import { FormattedMessage, useIntl } from 'react-intl'; import { object, shape, string } from 'prop-types'; import { useOrderConfirmationPage } from '@magento/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/useOrderConfirmationPage'; +import { useLocation } from 'react-router-dom'; +import { fullPageLoadingIndicator } from '../../LoadingIndicator'; import { useStyle } from '../../../classify'; import { StoreTitle } from '../../../components/Head'; import CreateAccount from './createAccount'; @@ -11,34 +13,19 @@ import defaultClasses from './orderConfirmationPage.module.css'; const OrderConfirmationPage = props => { const classes = useStyle(defaultClasses, props.classes); - const { data, orderNumber } = props; + const location = useLocation(); + const data = props.data; + const orderNumber = props.orderNumber || location.state.orderNumber; + const cartItems = data ? data.cart.items : location.state.items; + const { formatMessage } = useIntl(); const talonProps = useOrderConfirmationPage({ - data + data, + orderNumber }); - const { flatData, isSignedIn } = talonProps; - - const { - city, - country, - email, - firstname, - lastname, - postcode, - region, - shippingMethod, - street - } = flatData; - - const streetRows = street.map((row, index) => { - return ( - - {row} - - ); - }); + const { flatData, isSignedIn, loading } = talonProps; useEffect(() => { const { scrollTo } = globalThis; @@ -52,90 +39,118 @@ const OrderConfirmationPage = props => { } }, []); - const createAccountForm = !isSignedIn ? ( - - ) : null; + if (!flatData || loading) { + return fullPageLoadingIndicator; + } else { + const { + city, + country, + email, + firstname, + lastname, + postcode, + region, + shippingMethod, + street + } = flatData; - const nameString = `${firstname} ${lastname}`; - const additionalAddressString = `${city}, ${region} ${postcode} ${country}`; + const streetRows = street.map((row, index) => { + return ( + + {row} + + ); + }); - return ( -
- - {formatMessage({ - id: 'checkoutPage.titleReceipt', - defaultMessage: 'Receipt' - })} - -
-

- -

-
- -
-
- -
-
- {email} - {nameString} - {streetRows} - - {additionalAddressString} - -
-
- -
-
{shippingMethod}
-
- + const createAccountForm = !isSignedIn ? ( + + ) : null; + + const nameString = `${firstname} ${lastname}`; + const additionalAddressString = `${city}, ${region} ${postcode} ${country}`; + + return ( +
+ + {formatMessage({ + id: 'checkoutPage.titleReceipt', + defaultMessage: 'Receipt' + })} + +
+

+ +

+
+ +
+
+ +
+
+ {email} + {nameString} + {streetRows} + + {additionalAddressString} + +
+
+ +
+
+ {shippingMethod} +
+
+ +
+
+ +
-
- +
+ {createAccountForm}
-
{createAccountForm}
-
- ); + ); + } }; export default OrderConfirmationPage; @@ -157,6 +172,6 @@ OrderConfirmationPage.propTypes = { additionalText: string, sidebarContainer: string }), - data: object.isRequired, + data: object, orderNumber: string }; diff --git a/packages/venia-ui/lib/components/CheckoutPage/checkoutPage.js b/packages/venia-ui/lib/components/CheckoutPage/checkoutPage.js index cb319f4d5f..0dba1ba255 100644 --- a/packages/venia-ui/lib/components/CheckoutPage/checkoutPage.js +++ b/packages/venia-ui/lib/components/CheckoutPage/checkoutPage.js @@ -2,14 +2,13 @@ import React, { Fragment, useEffect } from 'react'; import { shape, string } from 'prop-types'; import { FormattedMessage, useIntl } from 'react-intl'; import { AlertCircle as AlertCircleIcon } from 'react-feather'; -import { Link } from 'react-router-dom'; +import { Link, useHistory } from 'react-router-dom'; import { useWindowSize, useToasts } from '@magento/peregrine'; import { CHECKOUT_STEP, useCheckoutPage } from '@magento/peregrine/lib/talons/CheckoutPage/useCheckoutPage'; - import { useStyle } from '../../classify'; import Button from '../Button'; import { StoreTitle } from '../Head'; @@ -25,8 +24,8 @@ import payments from './PaymentInformation/paymentMethodCollection'; import PriceAdjustments from './PriceAdjustments'; import ShippingMethod from './ShippingMethod'; import ShippingInformation from './ShippingInformation'; -import OrderConfirmationPage from './OrderConfirmationPage'; import ItemsReview from './ItemsReview'; +import OrderConfirmationPage from './OrderConfirmationPage'; import GoogleReCaptcha from '../GoogleReCaptcha'; import defaultClasses from './checkoutPage.module.css'; @@ -35,6 +34,7 @@ import ScrollAnchor from '../ScrollAnchor/scrollAnchor'; const errorIcon = ; const CheckoutPage = props => { + const history = useHistory(); const { classes: propClasses } = props; const { formatMessage } = useIntl(); const talonProps = useCheckoutPage(); @@ -58,8 +58,8 @@ const CheckoutPage = props => { isGuestCheckout, isLoading, isUpdating, - orderDetailsData, orderDetailsLoading, + orderDetailsData, orderNumber, placeOrderLoading, placeOrderButtonClicked, @@ -83,7 +83,15 @@ const CheckoutPage = props => { } = talonProps; const [, { addToast }] = useToasts(); - + const orderCount = localStorage.getItem('orderCount'); + useEffect(() => { + if (isGuestCheckout && !orderDetailsData) { + if (orderCount === '1') { + history.push('/'); + localStorage.setItem('orderCount', '0'); + } + } + }, [isGuestCheckout, history, orderDetailsData, orderCount]); useEffect(() => { if (hasError) { const message = @@ -125,7 +133,7 @@ const CheckoutPage = props => { defaultMessage: 'Checkout' }); - if (orderNumber && orderDetailsData) { + if (isGuestCheckout && orderDetailsData && orderNumber) { return ( { const itemsReview = checkoutStep === CHECKOUT_STEP.REVIEW ? (
- +
) : null; diff --git a/packages/venia-ui/lib/defaultRoutes.json b/packages/venia-ui/lib/defaultRoutes.json index e869dafdb8..3adb4587e7 100644 --- a/packages/venia-ui/lib/defaultRoutes.json +++ b/packages/venia-ui/lib/defaultRoutes.json @@ -53,6 +53,12 @@ "exact": true, "path": "../ForgotPasswordPage" }, + { + "name": "OrderConfirmation", + "pattern": "/order-confirmation", + "exact": true, + "path": "../CheckoutPage/OrderConfirmationPage" + }, { "name": "OrderHistory", "pattern": "/order-history",