diff --git a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-payment.jsx b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-payment.jsx index 1aa971fc85..dbe743b1ae 100644 --- a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-payment.jsx +++ b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-payment.jsx @@ -233,11 +233,13 @@ const Payment = ({ }) } + const [showRegistrationNotice, setShowRegistrationNotice] = useState(false) const handleRegistrationSuccess = useCallback( async (newBasketId) => { if (newBasketId) { activeBasketIdRef.current = newBasketId } + setShowRegistrationNotice(true) setShouldSavePaymentMethod(true) try { const values = paymentMethodForm?.getValues?.() @@ -563,7 +565,7 @@ const Payment = ({ isBillingAddress /> )} - {isGuest && ( + {(isGuest || showRegistrationNotice) && ( )} @@ -610,7 +613,8 @@ const Payment = ({ - {selectedBillingAddress && ( + {(selectedBillingAddress || + (effectiveBillingSameAsShipping && selectedShippingAddress)) && ( - + )} - {isGuest && ( + {(isGuest || showRegistrationNotice) && ( )} diff --git a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-address.jsx b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-address.jsx index e9962ad9f2..0f1020d4dc 100644 --- a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-address.jsx +++ b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-address.jsx @@ -24,13 +24,9 @@ import { import {useCurrentCustomer} from '@salesforce/retail-react-app/app/hooks/use-current-customer' import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket' import {useToast} from '@salesforce/retail-react-app/app/hooks/use-toast' -import {Text, Button, Box} from '@salesforce/retail-react-app/app/components/shared/ui' +import {Text} from '@salesforce/retail-react-app/app/components/shared/ui' import {isPickupShipment} from '@salesforce/retail-react-app/app/utils/shipment-utils' import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config' -import {useSelectedStore} from '@salesforce/retail-react-app/app/hooks/use-selected-store' -import useNavigation from '@salesforce/retail-react-app/app/hooks/use-navigation' -import usePickupShipment from '@salesforce/retail-react-app/app/hooks/use-pickup-shipment' -import {STORE_LOCATOR_IS_ENABLED} from '@salesforce/retail-react-app/app/constants' const submitButtonMessage = defineMessage({ defaultMessage: 'Continue to Shipping Method', @@ -68,11 +64,6 @@ export default function ShippingAddress() { const hasMultipleDeliveryShipments = deliveryShipments.length > 1 - const storeLocatorEnabled = getConfig()?.app?.storeLocatorEnabled ?? STORE_LOCATOR_IS_ENABLED - const {selectedStore} = useSelectedStore() - const {navigate} = useNavigation() - const {updatePickupShipment} = usePickupShipment(basket) - // Prepare a shipping methods query we can manually refetch after address updates const shippingMethodsQuery = useShippingMethodsForShipment( { @@ -86,29 +77,6 @@ export default function ShippingAddress() { } ) - const switchToPickup = async () => { - try { - if (!selectedStore?.inventoryId) { - navigate('/store-locator') - return - } - const refreshed = await currentBasketQuery.refetch() - const latestBasketId = refreshed?.data?.basketId || basket.basketId - await updatePickupShipment(latestBasketId, selectedStore) - await currentBasketQuery.refetch() - goToStep(STEPS.PICKUP_ADDRESS) - } catch (_e) { - toast({ - title: formatMessage({ - defaultMessage: - 'We could not switch to Store Pickup. Please try again or choose a different store.', - id: 'shipping_address.error.switch_to_pickup_failed' - }), - status: 'error' - }) - } - } - const submitAndContinue = async (address) => { setIsLoading(true) try { @@ -314,24 +282,12 @@ export default function ShippingAddress() { onBackToSingle={() => setIsMultiShipping(false)} /> ) : ( - <> - {storeLocatorEnabled && ( - - - - )} - - + )} {(hasMultipleDeliveryShipments || isAddressFilled) && ( diff --git a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-user-registration.jsx b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-user-registration.jsx index 537807bc48..be1c55989e 100644 --- a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-user-registration.jsx +++ b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-user-registration.jsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: BSD-3-Clause * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import React, {useRef} from 'react' +import React, {useRef, useState} from 'react' import {FormattedMessage} from 'react-intl' import PropTypes from 'prop-types' import { @@ -13,6 +13,8 @@ import { Stack, Text, Heading, + Badge, + HStack, useDisclosure } from '@salesforce/retail-react-app/app/components/shared/ui' import OtpAuth from '@salesforce/retail-react-app/app/components/otp-auth' @@ -28,7 +30,8 @@ export default function UserRegistration({ isGuestCheckout = false, isDisabled = false, onSavePreferenceChange, - onRegistered + onRegistered, + showNotice = false }) { const {data: basket} = useCurrentBasket() const {isGuest} = useCustomerType() @@ -41,6 +44,7 @@ export default function UserRegistration({ : `${appOrigin}${passwordlessConfigCallback}` const {isOpen: isOtpOpen, onOpen: onOtpOpen, onClose: onOtpClose} = useDisclosure() const otpSentRef = useRef(false) + const [registrationSucceeded, setRegistrationSucceeded] = useState(false) const handleOtpClose = () => { otpSentRef.current = false @@ -85,6 +89,7 @@ export default function UserRegistration({ await onRegistered(basket?.basketId) } handleOtpClose() + setRegistrationSucceeded(true) } catch (_e) { // Let OtpAuth surface errors via its own UI/toast } @@ -96,6 +101,49 @@ export default function UserRegistration({ return null } + // After successful registration (local) or when parent instructs to show, render notice + if (registrationSucceeded || showNotice) { + return ( + + + + + + + + + + + + + + + + ) + } + return ( <> { expect(authorizePasswordlessLogin.mutateAsync).toHaveBeenCalledTimes(1) }) + test('shows account creation notification after successful OTP verification', async () => { + const user = userEvent.setup() + setup() + // Enable registration to trigger OTP + await user.click(screen.getByRole('checkbox', {name: /Create an account/i})) + // Verify OTP (mocked) + const otpButton = await screen.findByTestId('otp-verify') + await user.click(otpButton) + // Notification should appear after registration succeeds + await waitFor(() => { + expect(screen.getByTestId('sf-account-creation-notification')).toBeInTheDocument() + }) + // Optional: assert key content + expect(screen.getByText(/Account Created/i)).toBeInTheDocument() + // Use aria-label to avoid ambiguity with body text containing 'verified' + expect(screen.getByLabelText(/Verified/i)).toBeInTheDocument() + }) + + test('renders account creation notification when showNotice prop is true', async () => { + render( + + + + ) + expect(screen.getByTestId('sf-account-creation-notification')).toBeInTheDocument() + expect(screen.getByText(/Account Created/i)).toBeInTheDocument() + }) + test('calls loginPasswordless with OTP code and register flag', async () => { const user = userEvent.setup() const {loginPasswordless} = setup() diff --git a/packages/template-retail-react-app/app/static/translations/compiled/en-GB.json b/packages/template-retail-react-app/app/static/translations/compiled/en-GB.json index 09c5ebd179..5ca13ec67b 100644 --- a/packages/template-retail-react-app/app/static/translations/compiled/en-GB.json +++ b/packages/template-retail-react-app/app/static/translations/compiled/en-GB.json @@ -161,6 +161,24 @@ "value": "Addresses" } ], + "account_creation_notification.body": [ + { + "type": 0, + "value": "We’ve created and verified your account using the information from your order. Next time you check out, just enter the code we send to log in — no password needed." + } + ], + "account_creation_notification.title": [ + { + "type": 0, + "value": "Account Created" + } + ], + "account_creation_notification.verified": [ + { + "type": 0, + "value": "Verified" + } + ], "account_detail.title.account_details": [ { "type": 0, diff --git a/packages/template-retail-react-app/app/static/translations/compiled/en-US.json b/packages/template-retail-react-app/app/static/translations/compiled/en-US.json index 09c5ebd179..5ca13ec67b 100644 --- a/packages/template-retail-react-app/app/static/translations/compiled/en-US.json +++ b/packages/template-retail-react-app/app/static/translations/compiled/en-US.json @@ -161,6 +161,24 @@ "value": "Addresses" } ], + "account_creation_notification.body": [ + { + "type": 0, + "value": "We’ve created and verified your account using the information from your order. Next time you check out, just enter the code we send to log in — no password needed." + } + ], + "account_creation_notification.title": [ + { + "type": 0, + "value": "Account Created" + } + ], + "account_creation_notification.verified": [ + { + "type": 0, + "value": "Verified" + } + ], "account_detail.title.account_details": [ { "type": 0, diff --git a/packages/template-retail-react-app/app/static/translations/compiled/en-XA.json b/packages/template-retail-react-app/app/static/translations/compiled/en-XA.json index 6253a229eb..0240fc733f 100644 --- a/packages/template-retail-react-app/app/static/translations/compiled/en-XA.json +++ b/packages/template-retail-react-app/app/static/translations/compiled/en-XA.json @@ -377,6 +377,48 @@ "value": "]" } ], + "account_creation_notification.body": [ + { + "type": 0, + "value": "[" + }, + { + "type": 0, + "value": "Ẇḗḗ’ṽḗḗ ƈřḗḗȧȧŧḗḗḓ ȧȧƞḓ ṽḗḗřīƒīḗḗḓ ẏǿǿŭŭř ȧȧƈƈǿǿŭŭƞŧ ŭŭşīƞɠ ŧħḗḗ īƞƒǿǿřḿȧȧŧīǿǿƞ ƒřǿǿḿ ẏǿǿŭŭř ǿǿřḓḗḗř. Ƞḗḗẋŧ ŧīḿḗḗ ẏǿǿŭŭ ƈħḗḗƈķ ǿǿŭŭŧ, ĵŭŭşŧ ḗḗƞŧḗḗř ŧħḗḗ ƈǿǿḓḗḗ ẇḗḗ şḗḗƞḓ ŧǿǿ ŀǿǿɠ īƞ — ƞǿǿ ƥȧȧşşẇǿǿřḓ ƞḗḗḗḗḓḗḗḓ." + }, + { + "type": 0, + "value": "]" + } + ], + "account_creation_notification.title": [ + { + "type": 0, + "value": "[" + }, + { + "type": 0, + "value": "Ȧƈƈǿǿŭŭƞŧ Ƈřḗḗȧȧŧḗḗḓ" + }, + { + "type": 0, + "value": "]" + } + ], + "account_creation_notification.verified": [ + { + "type": 0, + "value": "[" + }, + { + "type": 0, + "value": "Ṽḗḗřīƒīḗḗḓ" + }, + { + "type": 0, + "value": "]" + } + ], "account_detail.title.account_details": [ { "type": 0, diff --git a/packages/template-retail-react-app/translations/en-GB.json b/packages/template-retail-react-app/translations/en-GB.json index 059e413a15..6830915e50 100644 --- a/packages/template-retail-react-app/translations/en-GB.json +++ b/packages/template-retail-react-app/translations/en-GB.json @@ -80,6 +80,15 @@ "account_addresses.title.addresses": { "defaultMessage": "Addresses" }, + "account_creation_notification.body": { + "defaultMessage": "We’ve created and verified your account using the information from your order. Next time you check out, just enter the code we send to log in — no password needed." + }, + "account_creation_notification.title": { + "defaultMessage": "Account Created" + }, + "account_creation_notification.verified": { + "defaultMessage": "Verified" + }, "account_detail.title.account_details": { "defaultMessage": "Account Details" }, diff --git a/packages/template-retail-react-app/translations/en-US.json b/packages/template-retail-react-app/translations/en-US.json index 059e413a15..6830915e50 100644 --- a/packages/template-retail-react-app/translations/en-US.json +++ b/packages/template-retail-react-app/translations/en-US.json @@ -80,6 +80,15 @@ "account_addresses.title.addresses": { "defaultMessage": "Addresses" }, + "account_creation_notification.body": { + "defaultMessage": "We’ve created and verified your account using the information from your order. Next time you check out, just enter the code we send to log in — no password needed." + }, + "account_creation_notification.title": { + "defaultMessage": "Account Created" + }, + "account_creation_notification.verified": { + "defaultMessage": "Verified" + }, "account_detail.title.account_details": { "defaultMessage": "Account Details" },