Skip to content

Commit 7cebb20

Browse files
@W-20480665 Account Verification Notification (#3501)
* W-20480665 Account Verification Notification * remove pick up in store link from checkout page
1 parent 610cffe commit 7cebb20

File tree

9 files changed

+203
-58
lines changed

9 files changed

+203
-58
lines changed

packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-payment.jsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -233,11 +233,13 @@ const Payment = ({
233233
})
234234
}
235235

236+
const [showRegistrationNotice, setShowRegistrationNotice] = useState(false)
236237
const handleRegistrationSuccess = useCallback(
237238
async (newBasketId) => {
238239
if (newBasketId) {
239240
activeBasketIdRef.current = newBasketId
240241
}
242+
setShowRegistrationNotice(true)
241243
setShouldSavePaymentMethod(true)
242244
try {
243245
const values = paymentMethodForm?.getValues?.()
@@ -563,7 +565,7 @@ const Payment = ({
563565
isBillingAddress
564566
/>
565567
)}
566-
{isGuest && (
568+
{(isGuest || showRegistrationNotice) && (
567569
<UserRegistration
568570
enableUserRegistration={enableUserRegistration}
569571
setEnableUserRegistration={onUserRegistrationToggle}
@@ -578,6 +580,7 @@ const Payment = ({
578580
}
579581
onSavePreferenceChange={onSavePreferenceChange}
580582
onRegistered={handleRegistrationSuccess}
583+
showNotice={showRegistrationNotice}
581584
/>
582585
)}
583586
</Stack>
@@ -610,26 +613,30 @@ const Payment = ({
610613

611614
<Divider borderColor="gray.100" />
612615

613-
{selectedBillingAddress && (
616+
{(selectedBillingAddress ||
617+
(effectiveBillingSameAsShipping && selectedShippingAddress)) && (
614618
<Stack spacing={2}>
615619
<Heading as="h3" fontSize="md">
616620
<FormattedMessage
617621
defaultMessage="Billing Address"
618622
id="checkout_payment.heading.billing_address"
619623
/>
620624
</Heading>
621-
<AddressDisplay address={selectedBillingAddress} />
625+
<AddressDisplay
626+
address={selectedBillingAddress || selectedShippingAddress}
627+
/>
622628
</Stack>
623629
)}
624630

625-
{isGuest && (
631+
{(isGuest || showRegistrationNotice) && (
626632
<UserRegistration
627633
enableUserRegistration={enableUserRegistration}
628634
setEnableUserRegistration={setEnableUserRegistration}
629635
isGuestCheckout={registeredUserChoseGuest}
630636
isDisabled={!appliedPayment && !paymentMethodForm.formState.isValid}
631637
onSavePreferenceChange={onSavePreferenceChange}
632638
onRegistered={handleRegistrationSuccess}
639+
showNotice={showRegistrationNotice}
633640
/>
634641
)}
635642
</Stack>

packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-address.jsx

Lines changed: 7 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,9 @@ import {
2424
import {useCurrentCustomer} from '@salesforce/retail-react-app/app/hooks/use-current-customer'
2525
import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket'
2626
import {useToast} from '@salesforce/retail-react-app/app/hooks/use-toast'
27-
import {Text, Button, Box} from '@salesforce/retail-react-app/app/components/shared/ui'
27+
import {Text} from '@salesforce/retail-react-app/app/components/shared/ui'
2828
import {isPickupShipment} from '@salesforce/retail-react-app/app/utils/shipment-utils'
2929
import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config'
30-
import {useSelectedStore} from '@salesforce/retail-react-app/app/hooks/use-selected-store'
31-
import useNavigation from '@salesforce/retail-react-app/app/hooks/use-navigation'
32-
import usePickupShipment from '@salesforce/retail-react-app/app/hooks/use-pickup-shipment'
33-
import {STORE_LOCATOR_IS_ENABLED} from '@salesforce/retail-react-app/app/constants'
3430

3531
const submitButtonMessage = defineMessage({
3632
defaultMessage: 'Continue to Shipping Method',
@@ -68,11 +64,6 @@ export default function ShippingAddress() {
6864

6965
const hasMultipleDeliveryShipments = deliveryShipments.length > 1
7066

71-
const storeLocatorEnabled = getConfig()?.app?.storeLocatorEnabled ?? STORE_LOCATOR_IS_ENABLED
72-
const {selectedStore} = useSelectedStore()
73-
const {navigate} = useNavigation()
74-
const {updatePickupShipment} = usePickupShipment(basket)
75-
7667
// Prepare a shipping methods query we can manually refetch after address updates
7768
const shippingMethodsQuery = useShippingMethodsForShipment(
7869
{
@@ -86,29 +77,6 @@ export default function ShippingAddress() {
8677
}
8778
)
8879

89-
const switchToPickup = async () => {
90-
try {
91-
if (!selectedStore?.inventoryId) {
92-
navigate('/store-locator')
93-
return
94-
}
95-
const refreshed = await currentBasketQuery.refetch()
96-
const latestBasketId = refreshed?.data?.basketId || basket.basketId
97-
await updatePickupShipment(latestBasketId, selectedStore)
98-
await currentBasketQuery.refetch()
99-
goToStep(STEPS.PICKUP_ADDRESS)
100-
} catch (_e) {
101-
toast({
102-
title: formatMessage({
103-
defaultMessage:
104-
'We could not switch to Store Pickup. Please try again or choose a different store.',
105-
id: 'shipping_address.error.switch_to_pickup_failed'
106-
}),
107-
status: 'error'
108-
})
109-
}
110-
}
111-
11280
const submitAndContinue = async (address) => {
11381
setIsLoading(true)
11482
try {
@@ -314,24 +282,12 @@ export default function ShippingAddress() {
314282
onBackToSingle={() => setIsMultiShipping(false)}
315283
/>
316284
) : (
317-
<>
318-
{storeLocatorEnabled && (
319-
<Box mb={3}>
320-
<Button variant="link" onClick={switchToPickup}>
321-
{formatMessage({
322-
defaultMessage: 'Pick up in store',
323-
id: 'shipping_address.action.pickup_in_store'
324-
})}
325-
</Button>
326-
</Box>
327-
)}
328-
<ShippingAddressSelection
329-
selectedAddress={selectedShippingAddress}
330-
submitButtonLabel={submitButtonMessage}
331-
onSubmit={submitAndContinue}
332-
formTitleAriaLabel={shippingAddressAriaLabel}
333-
/>
334-
</>
285+
<ShippingAddressSelection
286+
selectedAddress={selectedShippingAddress}
287+
submitButtonLabel={submitButtonMessage}
288+
onSubmit={submitAndContinue}
289+
formTitleAriaLabel={shippingAddressAriaLabel}
290+
/>
335291
)}
336292
</ToggleCardEdit>
337293
{(hasMultipleDeliveryShipments || isAddressFilled) && (

packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-user-registration.jsx

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* SPDX-License-Identifier: BSD-3-Clause
55
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
66
*/
7-
import React, {useRef} from 'react'
7+
import React, {useRef, useState} from 'react'
88
import {FormattedMessage} from 'react-intl'
99
import PropTypes from 'prop-types'
1010
import {
@@ -13,6 +13,8 @@ import {
1313
Stack,
1414
Text,
1515
Heading,
16+
Badge,
17+
HStack,
1618
useDisclosure
1719
} from '@salesforce/retail-react-app/app/components/shared/ui'
1820
import OtpAuth from '@salesforce/retail-react-app/app/components/otp-auth'
@@ -28,7 +30,8 @@ export default function UserRegistration({
2830
isGuestCheckout = false,
2931
isDisabled = false,
3032
onSavePreferenceChange,
31-
onRegistered
33+
onRegistered,
34+
showNotice = false
3235
}) {
3336
const {data: basket} = useCurrentBasket()
3437
const {isGuest} = useCustomerType()
@@ -41,6 +44,7 @@ export default function UserRegistration({
4144
: `${appOrigin}${passwordlessConfigCallback}`
4245
const {isOpen: isOtpOpen, onOpen: onOtpOpen, onClose: onOtpClose} = useDisclosure()
4346
const otpSentRef = useRef(false)
47+
const [registrationSucceeded, setRegistrationSucceeded] = useState(false)
4448

4549
const handleOtpClose = () => {
4650
otpSentRef.current = false
@@ -85,6 +89,7 @@ export default function UserRegistration({
8589
await onRegistered(basket?.basketId)
8690
}
8791
handleOtpClose()
92+
setRegistrationSucceeded(true)
8893
} catch (_e) {
8994
// Let OtpAuth surface errors via its own UI/toast
9095
}
@@ -96,6 +101,49 @@ export default function UserRegistration({
96101
return null
97102
}
98103

104+
// After successful registration (local) or when parent instructs to show, render notice
105+
if (registrationSucceeded || showNotice) {
106+
return (
107+
<Box
108+
border="1px solid"
109+
borderColor="gray.200"
110+
rounded="md"
111+
p={4}
112+
data-testid="sf-account-creation-notification"
113+
>
114+
<HStack justify="space-between" align="start" mb={2}>
115+
<Heading fontSize="lg" lineHeight="30px">
116+
<FormattedMessage
117+
defaultMessage="Account Created"
118+
id="account_creation_notification.title"
119+
/>
120+
</Heading>
121+
<Badge
122+
colorScheme="green"
123+
fontSize="0.9em"
124+
px={3}
125+
py={1}
126+
rounded="md"
127+
aria-label="Verified"
128+
>
129+
<FormattedMessage
130+
defaultMessage="Verified"
131+
id="account_creation_notification.verified"
132+
/>
133+
</Badge>
134+
</HStack>
135+
<Stack spacing={2}>
136+
<Text color="gray.700">
137+
<FormattedMessage
138+
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."
139+
id="account_creation_notification.body"
140+
/>
141+
</Text>
142+
</Stack>
143+
</Box>
144+
)
145+
}
146+
99147
return (
100148
<>
101149
<Box
@@ -175,5 +223,7 @@ UserRegistration.propTypes = {
175223
isDisabled: PropTypes.bool,
176224
/** Callback to set save-for-future preference */
177225
onSavePreferenceChange: PropTypes.func,
178-
onRegistered: PropTypes.func
226+
onRegistered: PropTypes.func,
227+
/** When true, forces the success notice to show (e.g., after component would normally unmount) */
228+
showNotice: PropTypes.bool
179229
}

packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-user-registration.test.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,42 @@ describe('UserRegistration', () => {
332332
expect(authorizePasswordlessLogin.mutateAsync).toHaveBeenCalledTimes(1)
333333
})
334334

335+
test('shows account creation notification after successful OTP verification', async () => {
336+
const user = userEvent.setup()
337+
setup()
338+
// Enable registration to trigger OTP
339+
await user.click(screen.getByRole('checkbox', {name: /Create an account/i}))
340+
// Verify OTP (mocked)
341+
const otpButton = await screen.findByTestId('otp-verify')
342+
await user.click(otpButton)
343+
// Notification should appear after registration succeeds
344+
await waitFor(() => {
345+
expect(screen.getByTestId('sf-account-creation-notification')).toBeInTheDocument()
346+
})
347+
// Optional: assert key content
348+
expect(screen.getByText(/Account Created/i)).toBeInTheDocument()
349+
// Use aria-label to avoid ambiguity with body text containing 'verified'
350+
expect(screen.getByLabelText(/Verified/i)).toBeInTheDocument()
351+
})
352+
353+
test('renders account creation notification when showNotice prop is true', async () => {
354+
render(
355+
<IntlProvider locale="en-GB">
356+
<UserRegistration
357+
enableUserRegistration={false}
358+
setEnableUserRegistration={jest.fn()}
359+
isGuestCheckout={false}
360+
isDisabled={false}
361+
onSavePreferenceChange={jest.fn()}
362+
onRegistered={jest.fn()}
363+
showNotice
364+
/>
365+
</IntlProvider>
366+
)
367+
expect(screen.getByTestId('sf-account-creation-notification')).toBeInTheDocument()
368+
expect(screen.getByText(/Account Created/i)).toBeInTheDocument()
369+
})
370+
335371
test('calls loginPasswordless with OTP code and register flag', async () => {
336372
const user = userEvent.setup()
337373
const {loginPasswordless} = setup()

packages/template-retail-react-app/app/static/translations/compiled/en-GB.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,24 @@
161161
"value": "Addresses"
162162
}
163163
],
164+
"account_creation_notification.body": [
165+
{
166+
"type": 0,
167+
"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."
168+
}
169+
],
170+
"account_creation_notification.title": [
171+
{
172+
"type": 0,
173+
"value": "Account Created"
174+
}
175+
],
176+
"account_creation_notification.verified": [
177+
{
178+
"type": 0,
179+
"value": "Verified"
180+
}
181+
],
164182
"account_detail.title.account_details": [
165183
{
166184
"type": 0,

packages/template-retail-react-app/app/static/translations/compiled/en-US.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,24 @@
161161
"value": "Addresses"
162162
}
163163
],
164+
"account_creation_notification.body": [
165+
{
166+
"type": 0,
167+
"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."
168+
}
169+
],
170+
"account_creation_notification.title": [
171+
{
172+
"type": 0,
173+
"value": "Account Created"
174+
}
175+
],
176+
"account_creation_notification.verified": [
177+
{
178+
"type": 0,
179+
"value": "Verified"
180+
}
181+
],
164182
"account_detail.title.account_details": [
165183
{
166184
"type": 0,

packages/template-retail-react-app/app/static/translations/compiled/en-XA.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,48 @@
377377
"value": "]"
378378
}
379379
],
380+
"account_creation_notification.body": [
381+
{
382+
"type": 0,
383+
"value": "["
384+
},
385+
{
386+
"type": 0,
387+
"value": "Ẇḗḗ’ṽḗḗ ƈřḗḗȧȧŧḗḗḓ ȧȧƞḓ ṽḗḗřīƒīḗḗḓ ẏǿǿŭŭř ȧȧƈƈǿǿŭŭƞŧ ŭŭşīƞɠ ŧħḗḗ īƞƒǿǿřḿȧȧŧīǿǿƞ ƒřǿǿḿ ẏǿǿŭŭř ǿǿřḓḗḗř. Ƞḗḗẋŧ ŧīḿḗḗ ẏǿǿŭŭ ƈħḗḗƈķ ǿǿŭŭŧ, ĵŭŭşŧ ḗḗƞŧḗḗř ŧħḗḗ ƈǿǿḓḗḗ ẇḗḗ şḗḗƞḓ ŧǿǿ ŀǿǿɠ īƞ — ƞǿǿ ƥȧȧşşẇǿǿřḓ ƞḗḗḗḗḓḗḗḓ."
388+
},
389+
{
390+
"type": 0,
391+
"value": "]"
392+
}
393+
],
394+
"account_creation_notification.title": [
395+
{
396+
"type": 0,
397+
"value": "["
398+
},
399+
{
400+
"type": 0,
401+
"value": "Ȧƈƈǿǿŭŭƞŧ Ƈřḗḗȧȧŧḗḗḓ"
402+
},
403+
{
404+
"type": 0,
405+
"value": "]"
406+
}
407+
],
408+
"account_creation_notification.verified": [
409+
{
410+
"type": 0,
411+
"value": "["
412+
},
413+
{
414+
"type": 0,
415+
"value": "Ṽḗḗřīƒīḗḗḓ"
416+
},
417+
{
418+
"type": 0,
419+
"value": "]"
420+
}
421+
],
380422
"account_detail.title.account_details": [
381423
{
382424
"type": 0,

0 commit comments

Comments
 (0)