Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
735ae0c
One click checkout
syadupathi-sf Dec 30, 2025
67bd66c
changes to fix install, tests and lint - needs to be reviewed
syadupathi-sf Dec 30, 2025
4d63b8f
revert the test change in pwa-kit-runtime
syadupathi-sf Dec 30, 2025
810fd7f
@W-20892497 Show Phone number in Contact Info summary (#3576)
syadupathi-sf Jan 14, 2026
2cab71c
@W-20892592 Remove gift messaging for multi shipment (#3579)
syadupathi-sf Jan 15, 2026
a829ae3
@W-20892530 @W-20892577 Billing Address Validation and Using contact …
syadupathi-sf Jan 16, 2026
6981ec2
Fix SDK tests (#3593)
syadupathi-sf Jan 21, 2026
96fe651
@ W-20540715 Address 1CC feature branch review comments (#3619)
syadupathi-sf Jan 29, 2026
cbf194c
passwordless mode updates
syadupathi-sf Jan 30, 2026
e8428d1
@W-21094171: Fix Resend Code for OTP modal
dannyphan2000 Jan 30, 2026
8dc030b
add translations
dannyphan2000 Jan 30, 2026
2bb888d
fix lint
dannyphan2000 Jan 30, 2026
0cc9223
update component per UX alignment
dannyphan2000 Jan 30, 2026
02b3f94
no actions during verification
dannyphan2000 Jan 30, 2026
a114559
revert to generic error message as per code review comment
syadupathi-sf Jan 30, 2026
5067698
transaltion changes
syadupathi-sf Feb 2, 2026
4a0c69e
fix the rebase issue
syadupathi-sf Feb 2, 2026
3d0fbce
update isomorphic version
syadupathi-sf Feb 3, 2026
64068f5
update isomorhic in dev dependencies
syadupathi-sf Feb 3, 2026
1c177c0
Updating another dev dependency for isomorphic version
syadupathi-sf Feb 3, 2026
1cbbf51
@W-21094171: Fix Resend Code for OTP modal
dannyphan2000 Jan 30, 2026
39e278b
fix package versions
syadupathi-sf Feb 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions packages/commerce-sdk-react/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/commerce-sdk-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"version": "node ./scripts/version.js"
},
"dependencies": {
"commerce-sdk-isomorphic": "5.0.0-preview.1",
"commerce-sdk-isomorphic": "5.0.0",
"js-cookie": "^3.0.1",
"jwt-decode": "^4.0.0"
},
Expand Down
219 changes: 111 additions & 108 deletions packages/template-retail-react-app/app/components/otp-auth/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ const OtpAuth = ({
handleOtpVerification,
onCheckoutAsGuest,
isGuestRegistration = false,
hideCheckoutAsGuestButton = false
hideCheckoutAsGuestButton = false,
resendCooldownDuration = 30
}) => {
const {tokenLength} = getConfig().app.login
const parsedLength = Number(tokenLength)
Expand Down Expand Up @@ -99,6 +100,8 @@ const OtpAuth = ({
otpInputs.clear()
setError('')
form.setValue('otp', '')
// Start resend cooldown when modal opens
setResendTimer(resendCooldownDuration)

// Track OTP modal view activity
track('/otp-authentication', {
Expand All @@ -108,10 +111,10 @@ const OtpAuth = ({

setTimeout(() => otpInputs.inputRefs.current[0]?.focus(), 100)
}
}, [isOpen])
}, [isOpen, resendCooldownDuration])

const handleVerify = async (code = otpInputs.values.join('')) => {
if (code.length !== OTP_LENGTH) return
if (isVerifying || code.length !== OTP_LENGTH) return

setIsVerifying(true)
setError('')
Expand Down Expand Up @@ -147,14 +150,17 @@ const OtpAuth = ({
}

const handleResend = async () => {
setResendTimer(5)
// No action while verifying or during cooldown; button stays visible/enabled
if (isVerifying || resendTimer > 0) return

setResendTimer(resendCooldownDuration)
try {
await track('/otp-resend', {
activity: 'otp_code_resent',
context: 'authentication',
resendAttempt: true
})
await handleSendEmailOtp(form.getValues('email'))
await handleSendEmailOtp(form.getValues('email'), true)
} catch (error) {
setResendTimer(0)
await track('/otp-resend-failed', {
Expand All @@ -167,6 +173,8 @@ const OtpAuth = ({
}

const handleCheckoutAsGuest = async () => {
if (isVerifying) return

// Track checkout as guest selection
await track('/checkout-as-guest', {
activity: 'checkout_as_guest_selected',
Expand All @@ -191,8 +199,6 @@ const OtpAuth = ({
}
}

const isResendDisabled = resendTimer > 0 || isVerifying

return (
<Modal isOpen={isOpen} onClose={onClose} isCentered size="lg" closeOnOverlayClick={false}>
<ModalOverlay />
Expand All @@ -213,7 +219,7 @@ const OtpAuth = ({
<ModalCloseButton disabled={isVerifying} />
<ModalBody pb={6}>
<Stack spacing={12} paddingLeft={4} paddingRight={4} alignItems="center">
<Text fontSize="md" maxWidth="300px" textAlign="center">
<Text fontSize="md" maxWidth={80} textAlign="center">
{isGuestRegistration ? (
<FormattedMessage
defaultMessage="We sent a one-time password (OTP) to your email. To create your account and proceed to checkout, enter the {otpLength}-digit code below."
Expand All @@ -228,116 +234,111 @@ const OtpAuth = ({
)}
</Text>

{/* OTP Input */}
<SimpleGrid columns={OTP_LENGTH} spacing={3}>
{Array.from({length: OTP_LENGTH}).map((_, index) => (
<Input
key={index}
ref={(el) => (otpInputs.inputRefs.current[index] = el)}
value={otpInputs.values[index]}
onChange={(e) => handleInputChange(index, e.target.value)}
onKeyDown={(e) => otpInputs.handleKeyDown(index, e)}
onPaste={otpInputs.handlePaste}
type="text"
inputMode="numeric"
maxLength={1}
textAlign="center"
fontSize="lg"
fontWeight="bold"
size="lg"
width="48px"
height="56px"
borderRadius="md"
borderColor="gray.300"
borderWidth="2px"
disabled={isVerifying}
_focus={{
borderColor: 'blue.500',
boxShadow: '0 0 0 1px var(--chakra-colors-blue-500)'
}}
_hover={{
borderColor: 'gray.400'
}}
/>
))}
</SimpleGrid>
<Stack spacing={6} width="100%" alignItems="center">
{/* OTP Input */}
<SimpleGrid columns={OTP_LENGTH} spacing={3}>
{Array.from({length: OTP_LENGTH}).map((_, index) => (
<Input
key={index}
ref={(el) => (otpInputs.inputRefs.current[index] = el)}
value={otpInputs.values[index]}
onChange={(e) => handleInputChange(index, e.target.value)}
onKeyDown={(e) => otpInputs.handleKeyDown(index, e)}
onPaste={otpInputs.handlePaste}
type="text"
inputMode="numeric"
maxLength={1}
textAlign="center"
fontSize="lg"
fontWeight="bold"
size="lg"
width={12}
height={14}
borderRadius="md"
borderColor={error ? 'red.500' : 'gray.300'}
borderWidth={2}
disabled={isVerifying}
_focus={{
borderColor: error ? 'red.500' : 'blue.500',
boxShadow: error
? '0 0 0 1px var(--chakra-colors-red-500)'
: '0 0 0 1px var(--chakra-colors-blue-500)'
}}
_hover={{
borderColor: error ? 'red.500' : 'gray.400'
}}
/>
))}
</SimpleGrid>

{/* Loading indicator during verification */}
{isVerifying && (
<Text fontSize="sm" color="blue.500">
<FormattedMessage
defaultMessage="Verifying code..."
id="otp.message.verifying"
/>
</Text>
)}
{/* Error message */}
{error && (
<Text fontSize="sm" color="red.500" textAlign="center">
{error}
</Text>
)}

{/* Error message */}
{error && (
<Text fontSize="sm" color="red.500" textAlign="center">
{error}
</Text>
)}
{/* Countdown message */}
{resendTimer > 0 && (
<Text fontSize="sm" color="gray.600" textAlign="center">
<FormattedMessage
defaultMessage="You can request a new code in {timer} {timer, plural, one {second} other {seconds}}."
id="otp.message.resend_cooldown"
values={{timer: resendTimer}}
/>
</Text>
)}

{/* Buttons */}
<HStack spacing={4} width="100%" justifyContent="flex-end">
{!hideCheckoutAsGuestButton && (
<Button
onClick={handleCheckoutAsGuest}
variant="solid"
size="lg"
minWidth={40}
isDisabled={isVerifying}
bg="gray.50"
color="gray.800"
fontWeight="bold"
border="none"
_hover={{
bg: 'gray.100'
}}
_active={{
bg: 'gray.200'
}}
>
{isGuestRegistration ? (
<FormattedMessage
defaultMessage="Cancel"
id="otp.button.cancel_guest_registration"
/>
) : (
<FormattedMessage
defaultMessage="Checkout as a Guest"
id="otp.button.checkout_as_guest"
/>
)}
</Button>
)}

{/* Buttons */}
<HStack spacing={4} width="100%" justifyContent="center">
{!hideCheckoutAsGuestButton && (
<Button
onClick={handleCheckoutAsGuest}
onClick={handleResend}
variant="solid"
size="lg"
minWidth="160px"
isDisabled={isVerifying}
bg="gray.50"
color="gray.800"
fontWeight="bold"
border="none"
_hover={{
bg: 'gray.100'
}}
_active={{
bg: 'gray.200'
}}
colorScheme="blue"
bg="blue.500"
minWidth={40}
_hover={{bg: 'blue.600'}}
>
{isGuestRegistration ? (
<FormattedMessage
defaultMessage="Cancel"
id="otp.button.cancel_guest_registration"
/>
) : (
<FormattedMessage
defaultMessage="Checkout as a Guest"
id="otp.button.checkout_as_guest"
/>
)}
</Button>
)}

<Button
onClick={handleResend}
variant="solid"
size="lg"
colorScheme={isResendDisabled ? 'gray' : 'blue'}
bg={isResendDisabled ? 'gray.300' : 'blue.500'}
minWidth="160px"
isDisabled={isResendDisabled}
_hover={isResendDisabled ? {} : {bg: 'blue.600'}}
_disabled={{bg: 'gray.300', color: 'gray.600'}}
>
{resendTimer > 0 ? (
<FormattedMessage
defaultMessage="Resend code in {timer} seconds..."
id="otp.button.resend_timer"
values={{timer: resendTimer}}
/>
) : (
<FormattedMessage
defaultMessage="Resend Code"
id="otp.button.resend_code"
/>
)}
</Button>
</HStack>
</Button>
</HStack>
</Stack>
</Stack>
</ModalBody>
</ModalContent>
Expand All @@ -353,7 +354,9 @@ OtpAuth.propTypes = {
handleOtpVerification: PropTypes.func.isRequired,
onCheckoutAsGuest: PropTypes.func,
isGuestRegistration: PropTypes.bool,
hideCheckoutAsGuestButton: PropTypes.bool
hideCheckoutAsGuestButton: PropTypes.bool,
/** Resend cooldown (in seconds). Default 30. */
resendCooldownDuration: PropTypes.number
}

export default OtpAuth
Loading
Loading