diff --git a/packages/core/src/app/shipping/ConsignmentListItem.tsx b/packages/core/src/app/shipping/ConsignmentListItem.tsx index 0c101d8730..439a0ec836 100644 --- a/packages/core/src/app/shipping/ConsignmentListItem.tsx +++ b/packages/core/src/app/shipping/ConsignmentListItem.tsx @@ -21,6 +21,7 @@ export interface ConsignmentListItemProps { isLoading: boolean; shippingQuoteFailedMessage: string; onUnhandledError(error: Error): void; + resetErrorConsignmentNumber(): void; } const ConsignmentListItem: FunctionComponent = ({ @@ -31,13 +32,15 @@ const ConsignmentListItem: FunctionComponent = ({ isLoading, shippingQuoteFailedMessage, onUnhandledError, + resetErrorConsignmentNumber, }: ConsignmentListItemProps) => { const { checkoutService: { deleteConsignment } } = useCheckout(); const handleClose = async () => { await deleteConsignment(consignment.id); - } + resetErrorConsignmentNumber(); + }; return (
@@ -68,6 +71,7 @@ const ConsignmentListItem: FunctionComponent = ({
diff --git a/packages/core/src/app/shipping/MultiShippingFormV2.test.tsx b/packages/core/src/app/shipping/MultiShippingFormV2.test.tsx index abcce30e32..52642bb6c8 100644 --- a/packages/core/src/app/shipping/MultiShippingFormV2.test.tsx +++ b/packages/core/src/app/shipping/MultiShippingFormV2.test.tsx @@ -152,9 +152,19 @@ describe('MultiShippingFormV2 Component', () => { const addShippingDestinationButton = screen.getByRole('button', { name: 'Add new destination' }); expect(addShippingDestinationButton).toBeInTheDocument(); + + await userEvent.click(addShippingDestinationButton); + + expect(screen.queryByText(/Please complete the address/i)).not.toBeInTheDocument(); + await userEvent.click(addShippingDestinationButton); expect(screen.getByText('Destination #2')).toBeInTheDocument(); + expect( + screen.getByText( + 'Please complete the address, item allocation, and method selection for Destination #2.', + ), + ).toBeInTheDocument(); await waitFor(() => { expect(screen.queryByText('No items allocated')).not.toBeInTheDocument(); @@ -521,7 +531,7 @@ describe('MultiShippingFormV2 Component', () => { jest.spyOn(checkoutService, 'deleteConsignment').mockReturnValue( Promise.resolve(checkoutService.getState()), ); - + jest.spyOn(checkoutState.data, 'getCheckout').mockReturnValue({ ...getCheckout(), consignments: [{ diff --git a/packages/core/src/app/shipping/MultiShippingFormV2.tsx b/packages/core/src/app/shipping/MultiShippingFormV2.tsx index 036f3bfae9..29e97fcab3 100644 --- a/packages/core/src/app/shipping/MultiShippingFormV2.tsx +++ b/packages/core/src/app/shipping/MultiShippingFormV2.tsx @@ -36,6 +36,7 @@ const MultiShippingFormV2: FunctionComponent = ({ onUnhandledError, }: MultiShippingFormV2Props) => { const [isAddShippingDestination, setIsAddShippingDestination] = useState(false); + const [errorConsignmentNumber, setErrorConsignmentNumber] = useState(); const { checkoutState: { @@ -47,8 +48,9 @@ const MultiShippingFormV2: FunctionComponent = ({ const consignments = getConsignments() || EMPTY_ARRAY; const config = getConfig(); + const isEveryConsignmentHasShippingOption = hasSelectedShippingOptions(consignments); const shouldDisableSubmit = useMemo(() => { - return isLoading || !!unassignedLineItems.length || !hasSelectedShippingOptions(consignments); + return isLoading || !!unassignedLineItems.length || !isEveryConsignmentHasShippingOption; }, [isLoading, consignments]); if (!config) { @@ -63,8 +65,23 @@ const MultiShippingFormV2: FunctionComponent = ({ } = config; const handleAddShippingDestination = () => { - setIsAddShippingDestination(true); - } + if ( + consignments.length > 0 && + !isAddShippingDestination && + !isEveryConsignmentHasShippingOption + ) { + const errorConsignmentIndex = consignments.findIndex( + (consignment) => !consignment.selectedShippingOption, + ); + + setErrorConsignmentNumber(errorConsignmentIndex + 1); + } else if (isAddShippingDestination) { + setErrorConsignmentNumber(consignments.length + 1); + } else { + setErrorConsignmentNumber(undefined); + setIsAddShippingDestination(true); + } + }; const hasUnassignedItems = shippableItemsCount > 0; @@ -79,6 +96,9 @@ const MultiShippingFormV2: FunctionComponent = ({ ; } + const resetErrorConsignmentNumber = () => { + setErrorConsignmentNumber(undefined); + }; return ( <> @@ -92,6 +112,7 @@ const MultiShippingFormV2: FunctionComponent = ({ isLoading={isLoading} key={consignment.id} onUnhandledError={onUnhandledError} + resetErrorConsignmentNumber={resetErrorConsignmentNumber} shippingQuoteFailedMessage={shippingQuoteFailedMessage} /> ))} @@ -105,11 +126,21 @@ const MultiShippingFormV2: FunctionComponent = ({ setIsAddShippingDestination={setIsAddShippingDestination} />) } - {hasUnassignedItems && + {hasUnassignedItems && } + {Boolean(errorConsignmentNumber) && ( +
+ + + +
+ )} { await checkout.waitForShippingStep(); - checkout.updateCheckout('post', '/checkouts/xxxxxxxxxx-xxxx-xxax-xxxx-xxxxxx/consignments', { - ...cartReadyForMultiShipping, - consignments: [ - { - ...consignment, - }, - ], + checkout.updateCheckout( + 'post', + '/checkouts/xxxxxxxxxx-xxxx-xxax-xxxx-xxxxxx/consignments', + { + ...cartReadyForMultiShipping, + consignments: [ + { + ...consignment, + availableShippingOptions: undefined, + selectedShippingOption: undefined, + }, + ], + }, + ); + // eslint-disable-next-line testing-library/no-unnecessary-act + await act(async () => { + await userEvent.click(screen.getByText(/Ship to multiple addresses/i)); + await userEvent.click(screen.getByText(/Enter a new address/i)); + await userEvent.click(screen.getByText(/789 Test Ave/i)); + await userEvent.click(screen.getByText(/Allocate items/i)); + await userEvent.click(screen.getByText(/Select all items left/i)); + await userEvent.click(screen.getByRole('button', { name: 'Allocate' })); + await userEvent.click( + await screen.findByRole('button', { name: 'Add new destination' }), + ); }); - await userEvent.click(screen.getByText(/Ship to multiple addresses/i)); - await userEvent.click(screen.getByText(/Enter a new address/i)); + checkout.updateCheckout( + 'post', + '/checkouts/xxxxxxxxxx-xxxx-xxax-xxxx-xxxxxx/consignments', + { + ...cartReadyForMultiShipping, + consignments: [ + { + ...consignment, + }, + ], + }, + ); + + expect( + screen.getByText( + "Unfortunately one or more items in your cart can't be shipped to your location. Please choose a different delivery address.", + ), + ).toBeInTheDocument(); + expect( + screen.getByText( + 'Please complete the address, item allocation, and method selection for Destination #1.', + ), + ).toBeInTheDocument(); + + await userEvent.click( + screen.getByText(/checkout test, 130 Pitt St, Sydney, New South Wales, AU, 2000/i), + ); await userEvent.click(screen.getByText(/123 Example St/i)); - await userEvent.click(screen.getByText(/Allocate items/i)); - await userEvent.click(screen.getByText(/Select all items left/i)); - await userEvent.click(screen.getByRole('button', { name: 'Allocate' })); expect(await screen.findAllByRole('radio')).toHaveLength(2); expect(screen.getByLabelText(/Pickup In Store/i)).toBeInTheDocument(); @@ -155,5 +196,10 @@ describe('Multi-shipping V2', () => { .getAttribute('value'); expect(selectedShippingOptionValue2).toBe('option-id-flat-rate'); + expect( + screen.queryByText( + 'Please complete the address, item allocation, and method selection for Destination #1.', + ), + ).not.toBeInTheDocument(); }); }); diff --git a/packages/core/src/app/shipping/shippingOption/MultiShippingOptionsV2.tsx b/packages/core/src/app/shipping/shippingOption/MultiShippingOptionsV2.tsx index 65d573ae70..57c571dc74 100644 --- a/packages/core/src/app/shipping/shippingOption/MultiShippingOptionsV2.tsx +++ b/packages/core/src/app/shipping/shippingOption/MultiShippingOptionsV2.tsx @@ -12,16 +12,21 @@ interface MultiShippingOptionsV2Props { consignment: Consignment; isLoading: boolean; shippingQuoteFailedMessage: string; + resetErrorConsignmentNumber(): void; } export const MultiShippingOptionsV2 = ({ consignment, isLoading, + resetErrorConsignmentNumber, shippingQuoteFailedMessage, }: MultiShippingOptionsV2Props) => { const { checkoutService, checkoutState } = useCheckout(); - const selectShippingOption = checkoutService.selectConsignmentShippingOption; + const selectShippingOption = async (consignmentId: string, shippingOptionId: string) => { + await checkoutService.selectConsignmentShippingOption(consignmentId, shippingOptionId); + resetErrorConsignmentNumber(); + }; const isLoadingOptions = isLoadingSelector(checkoutState, isLoading)(consignment.id); return ( diff --git a/packages/locale/src/translations/en.json b/packages/locale/src/translations/en.json index eae6b0eadb..1b4d26584d 100644 --- a/packages/locale/src/translations/en.json +++ b/packages/locale/src/translations/en.json @@ -510,6 +510,7 @@ "select_shipping_address_text": "Please select a shipping address in order to see shipping quotes", "shipping_address_heading": "Shipping Address", "multishipping_consignment_index_heading": "Destination #{consignmentNumber}", + "multishipping_consignment_not_completed_error": "Please complete the address, item allocation, and method selection for Destination #{consignmentNumber}.", "multishipping_address_heading": "Choose where to ship each item", "multishipping_address_heading_guest": "Please sign in first", "multishipping_guest_intro": "To ship your items to multiple addresses you need to",