diff --git a/packages/template-retail-react-app/app/components/otp-auth/index.jsx b/packages/template-retail-react-app/app/components/otp-auth/index.jsx
index 8a5ec20e65..46c96f29d3 100644
--- a/packages/template-retail-react-app/app/components/otp-auth/index.jsx
+++ b/packages/template-retail-react-app/app/components/otp-auth/index.jsx
@@ -145,6 +145,7 @@ const OtpAuth = ({
resendAttempt: true
})
await handleSendEmailOtp(form.getValues('email'))
+ otpInputs.clear()
} catch (error) {
setResendTimer(0)
await track('/otp-resend-failed', {
diff --git a/packages/template-retail-react-app/app/components/passkey-registration-modal/index.jsx b/packages/template-retail-react-app/app/components/passkey-registration-modal/index.jsx
index 568de0e4c6..aabfa613a9 100644
--- a/packages/template-retail-react-app/app/components/passkey-registration-modal/index.jsx
+++ b/packages/template-retail-react-app/app/components/passkey-registration-modal/index.jsx
@@ -52,6 +52,8 @@ const PasskeyRegistrationModal = ({isOpen, onClose}) => {
const config = getConfig()
const webauthnConfig = config.app.login.passkey
const authorizeWebauthnRegistration = useAuthHelper(AuthHelpers.AuthorizeWebauthnRegistration)
+ const startWebauthnUserRegistration = useAuthHelper(AuthHelpers.StartWebauthnUserRegistration)
+ const finishWebauthnUserRegistration = useAuthHelper(AuthHelpers.FinishWebauthnUserRegistration)
const handleRegisterPasskey = async () => {
setIsLoading(true)
@@ -82,9 +84,109 @@ const PasskeyRegistrationModal = ({isOpen, onClose}) => {
}
}
+ /**
+ * Convert ArrayBuffer to base64url string
+ */
+ const arrayBufferToBase64Url = (buffer) => {
+ const bytes = new Uint8Array(buffer)
+ let binary = ''
+ for (let i = 0; i < bytes.length; i++) {
+ binary += String.fromCharCode(bytes[i])
+ }
+ const base64 = btoa(binary)
+ return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
+ }
+
const handleOtpVerification = async (code) => {
- // TODO: Implement OTP verification
- return {success: true}
+ setIsLoading(true)
+ setError(null)
+
+ try {
+ // Step 1: Start WebAuthn registration
+ const response = await startWebauthnUserRegistration.mutateAsync({
+ user_id: customer.email,
+ pwd_action_token: code,
+ ...(passkeyNickname && {nick_name: passkeyNickname})
+ })
+
+ // Step 2: Convert response to WebAuthn PublicKeyCredentialCreationOptions format
+ const publicKey = window.PublicKeyCredential.parseCreationOptionsFromJSON(response)
+
+ // Step 3: Call navigator.credentials.create()
+ if (!navigator.credentials || !navigator.credentials.create) {
+ throw new Error('WebAuthn API not available in this browser')
+ }
+
+ // navigator.credentials.create() will show a browser/system prompt
+ // This may appear to hang if the user doesn't interact with the prompt
+ let credential
+ try {
+ credential = await navigator.credentials.create({
+ publicKey
+ })
+ } catch (createError) {
+ // Handle user cancellation or other errors from the WebAuthn API
+ if (createError.name === 'NotAllowedError' || createError.name === 'AbortError') {
+ throw new Error('Passkey registration was cancelled or timed out')
+ }
+ throw createError
+ }
+
+ if (!credential) {
+ throw new Error('Failed to create credential: user cancelled or operation failed')
+ }
+
+ // Step 4: Convert credential to JSON format before sending to SLAS
+ // https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential/toJSON
+ let credentialJson
+ try {
+ credentialJson = credential.toJSON()
+ } catch (error) {
+ // Fallback to manual encoding if toJSON() fails
+ // Some passkey providers (e.g., 1Password) may not support the toJSON() method and return an error
+ const clientExtensionResults = credential.getClientExtensionResults?.() || {}
+ credentialJson = {
+ type: credential.type,
+ id: credential.id,
+ rawId: arrayBufferToBase64Url(credential.rawId),
+ response: {
+ attestationObject: arrayBufferToBase64Url(
+ credential.response.attestationObject
+ ),
+ clientDataJSON: arrayBufferToBase64Url(credential.response.clientDataJSON)
+ },
+ ...(Object.keys(clientExtensionResults).length > 0 && {clientExtensionResults})
+ }
+ }
+
+ // Step 5: Finish WebAuthn registration
+ await finishWebauthnUserRegistration.mutateAsync({
+ username: customer.email,
+ credential: credentialJson,
+ pwd_action_token: code
+ })
+
+ // Step 6: Close OTP modal and main modal on success
+ setIsOtpAuthOpen(false)
+ onClose()
+
+ return {success: true}
+ } catch (err) {
+ const errorMessage =
+ err.message ||
+ formatMessage({
+ id: 'passkey_registration.modal.error.registration_failed',
+ defaultMessage: 'Failed to register passkey'
+ })
+
+ // Return error result for OTP component to display
+ return {
+ success: false,
+ error: errorMessage
+ }
+ } finally {
+ setIsLoading(false)
+ }
}
const resetState = () => {
diff --git a/packages/template-retail-react-app/app/components/passkey-registration-modal/index.test.js b/packages/template-retail-react-app/app/components/passkey-registration-modal/index.test.js
index fcdefd1c70..9917b320e5 100644
--- a/packages/template-retail-react-app/app/components/passkey-registration-modal/index.test.js
+++ b/packages/template-retail-react-app/app/components/passkey-registration-modal/index.test.js
@@ -17,7 +17,12 @@ const mockUseAuthHelper = jest.fn()
jest.mock('@salesforce/commerce-sdk-react', () => ({
...jest.requireActual('@salesforce/commerce-sdk-react'),
- useAuthHelper: (param) => mockUseAuthHelper(param)
+ useAuthHelper: (param) => mockUseAuthHelper(param),
+ AuthHelpers: {
+ AuthorizeWebauthnRegistration: 'AuthorizeWebauthnRegistration',
+ StartWebauthnUserRegistration: 'StartWebauthnUserRegistration',
+ FinishWebauthnUserRegistration: 'FinishWebauthnUserRegistration'
+ }
}))
// Mock useCurrentCustomer
@@ -26,14 +31,22 @@ jest.mock('@salesforce/retail-react-app/app/hooks/use-current-customer', () => (
useCurrentCustomer: () => mockUseCurrentCustomer()
}))
-// Mock OtpAuth component
+// Mock OtpAuth component - expose handleOtpVerification for testing
+let otpVerificationHandler = null
jest.mock('@salesforce/retail-react-app/app/components/otp-auth', () => {
const PropTypes = jest.requireActual('prop-types')
- const MockOtpAuth = ({isOpen}) => {
+ const React = jest.requireActual('react')
+ const MockOtpAuth = ({isOpen, handleOtpVerification}) => {
+ React.useEffect(() => {
+ if (handleOtpVerification) {
+ otpVerificationHandler = handleOtpVerification
+ }
+ }, [handleOtpVerification])
return isOpen ?
OTP Auth Modal
: null
}
MockOtpAuth.propTypes = {
- isOpen: PropTypes.bool
+ isOpen: PropTypes.bool,
+ handleOtpVerification: PropTypes.func
}
return MockOtpAuth
})
@@ -52,6 +65,7 @@ describe('PasskeyRegistrationModal', () => {
beforeEach(() => {
jest.clearAllMocks()
+ otpVerificationHandler = null
mockUseCurrentCustomer.mockReturnValue({
data: mockCustomer
})
@@ -59,6 +73,11 @@ describe('PasskeyRegistrationModal', () => {
mutateAsync: mockMutateAsync
})
+ // Mock WebAuthn API
+ global.navigator.credentials = {
+ create: jest.fn()
+ }
+
// Mock product API calls that may be triggered by components in the provider tree
global.server.use(
rest.get('*/products*', (req, res, ctx) => {
@@ -74,7 +93,7 @@ describe('PasskeyRegistrationModal', () => {
})
expect(screen.getByText('Create Passkey')).toBeInTheDocument()
- expect(screen.getByText('Passkey Nickname')).toBeInTheDocument()
+ expect(screen.getByText(/Passkey Nickname/)).toBeInTheDocument()
expect(
screen.getByPlaceholderText("e.g., 'iPhone', 'Personal Laptop'")
).toBeInTheDocument()
@@ -149,7 +168,6 @@ describe('PasskeyRegistrationModal', () => {
expect(mockMutateAsync).toHaveBeenCalledWith({
user_id: 'test@example.com',
mode: 'callback',
- channel_id: 'site-1',
callback_uri: 'https://webhook.site/ee47be40-e9fc-4313-8b56-18e4fe819043'
})
})
@@ -200,4 +218,427 @@ describe('PasskeyRegistrationModal', () => {
expect(registerButton).toBeDisabled()
})
})
+
+ describe('handleOtpVerification', () => {
+ let mockStartWebauthnRegistration
+ let mockFinishWebauthnRegistration
+ let mockAuthorizeWebauthnRegistration
+
+ beforeEach(() => {
+ // Setup separate mocks for each auth helper
+ mockStartWebauthnRegistration = jest.fn()
+ mockFinishWebauthnRegistration = jest.fn()
+ mockAuthorizeWebauthnRegistration = jest.fn()
+
+ mockUseAuthHelper.mockImplementation((helperType) => {
+ if (helperType === 'StartWebauthnUserRegistration') {
+ return {mutateAsync: mockStartWebauthnRegistration}
+ }
+ if (helperType === 'FinishWebauthnUserRegistration') {
+ return {mutateAsync: mockFinishWebauthnRegistration}
+ }
+ if (helperType === 'AuthorizeWebauthnRegistration') {
+ return {mutateAsync: mockAuthorizeWebauthnRegistration}
+ }
+ return {mutateAsync: mockMutateAsync}
+ })
+ })
+
+ test('successfully completes OTP verification and passkey registration flow', async () => {
+ const otpCode = '12345678'
+ const mockChallenge = 'dGVzdC1jaGFsbGVuZ2U='
+ const mockUserId = 'dGVzdC11c2VyLWlk'
+
+ // Mock startWebauthnUserRegistration response
+ const mockStartResponse = {
+ challenge: mockChallenge,
+ rp: {
+ name: 'Test RP',
+ id: 'example.com'
+ },
+ user: {
+ id: mockUserId,
+ name: 'test@example.com',
+ displayName: 'Test User'
+ },
+ pubKeyCredParams: [
+ {
+ type: 'public-key',
+ alg: -7
+ }
+ ],
+ authenticatorSelection: {
+ authenticatorAttachment: 'platform',
+ userVerification: 'required'
+ },
+ timeout: 60000,
+ attestation: 'none'
+ }
+
+ // Mock WebAuthn credential
+ const mockCredential = {
+ type: 'public-key',
+ id: 'test-credential-id',
+ rawId: new ArrayBuffer(8),
+ response: {
+ attestationObject: new ArrayBuffer(16),
+ clientDataJSON: new ArrayBuffer(16)
+ }
+ }
+
+ mockStartWebauthnRegistration.mockResolvedValue(mockStartResponse)
+ global.navigator.credentials.create.mockResolvedValue(mockCredential)
+ mockFinishWebauthnRegistration.mockResolvedValue({})
+
+ const {user} = renderWithProviders(
+ ,
+ {
+ wrapperProps: {appConfig: mockConfig.app}
+ }
+ )
+
+ // Open OTP modal first
+ const registerButton = screen.getByText('Register Passkey')
+ await user.click(registerButton)
+
+ await waitFor(() => {
+ expect(screen.getByTestId('otp-auth-modal')).toBeInTheDocument()
+ })
+
+ // Wait for handler to be set
+ await waitFor(() => {
+ expect(otpVerificationHandler).toBeTruthy()
+ })
+
+ // Call handleOtpVerification
+ const result = await otpVerificationHandler(otpCode)
+
+ // Verify startWebauthnUserRegistration was called correctly
+ expect(mockStartWebauthnRegistration).toHaveBeenCalledWith({
+ user_id: 'test@example.com',
+ pwd_action_token: otpCode
+ })
+
+ // Verify navigator.credentials.create was called
+ expect(global.navigator.credentials.create).toHaveBeenCalled()
+ const publicKeyOptions = global.navigator.credentials.create.mock.calls[0][0].publicKey
+
+ // Verify structure and key properties
+ expect(publicKeyOptions.challenge).toBeDefined()
+ expect(publicKeyOptions.rp).toMatchObject({
+ name: 'Test RP',
+ id: 'example.com'
+ })
+ expect(publicKeyOptions.user.id).toBeDefined()
+ expect(Array.isArray(publicKeyOptions.pubKeyCredParams)).toBe(true)
+ expect(publicKeyOptions.authenticatorSelection).toBeDefined()
+ expect(typeof publicKeyOptions.timeout).toBe('number')
+ expect(publicKeyOptions.attestation).toBe('none')
+
+ // Verify finishWebauthnUserRegistration was called correctly
+ expect(mockFinishWebauthnRegistration).toHaveBeenCalledWith({
+ username: 'test@example.com',
+ credential: expect.objectContaining({
+ type: 'public-key',
+ id: 'test-credential-id'
+ }),
+ pwd_action_token: otpCode
+ })
+
+ // Verify success result
+ expect(result).toEqual({success: true})
+
+ // Verify modals are closed
+ await waitFor(() => {
+ expect(mockOnClose).toHaveBeenCalled()
+ })
+ })
+
+ test('includes nickname in startWebauthnUserRegistration when provided', async () => {
+ const otpCode = '12345678'
+ const nickname = 'My iPhone'
+ const mockStartResponse = {
+ challenge: 'dGVzdA==',
+ rp: {name: 'Test', id: 'example.com'},
+ user: {id: 'dGVzdA==', name: 'test@example.com'},
+ pubKeyCredParams: [],
+ timeout: 60000
+ }
+
+ const mockCredential = {
+ type: 'public-key',
+ id: 'test-id',
+ rawId: new ArrayBuffer(8),
+ response: {
+ attestationObject: new ArrayBuffer(16),
+ clientDataJSON: new ArrayBuffer(16)
+ }
+ }
+
+ mockStartWebauthnRegistration.mockResolvedValue(mockStartResponse)
+ global.navigator.credentials.create.mockResolvedValue(mockCredential)
+ mockFinishWebauthnRegistration.mockResolvedValue({})
+
+ const {user} = renderWithProviders(
+ ,
+ {
+ wrapperProps: {appConfig: mockConfig.app}
+ }
+ )
+
+ // Set nickname
+ const input = screen.getByPlaceholderText("e.g., 'iPhone', 'Personal Laptop'")
+ await user.type(input, nickname)
+
+ // Open OTP modal
+ const registerButton = screen.getByText('Register Passkey')
+ await user.click(registerButton)
+
+ await waitFor(() => {
+ expect(otpVerificationHandler).toBeTruthy()
+ })
+
+ await otpVerificationHandler(otpCode)
+
+ // Verify nickname was included
+ expect(mockStartWebauthnRegistration).toHaveBeenCalledWith({
+ user_id: 'test@example.com',
+ pwd_action_token: otpCode,
+ nick_name: nickname
+ })
+ })
+
+ test('returns error when startWebauthnUserRegistration fails', async () => {
+ const otpCode = '12345678'
+ const errorMessage = 'Failed to start registration'
+
+ mockStartWebauthnRegistration.mockRejectedValue(new Error(errorMessage))
+
+ const {user} = renderWithProviders(
+ ,
+ {
+ wrapperProps: {appConfig: mockConfig.app}
+ }
+ )
+
+ // Open OTP modal
+ const registerButton = screen.getByText('Register Passkey')
+ await user.click(registerButton)
+
+ await waitFor(() => {
+ expect(otpVerificationHandler).toBeTruthy()
+ })
+
+ const result = await otpVerificationHandler(otpCode)
+
+ expect(result).toEqual({
+ success: false,
+ error: errorMessage
+ })
+
+ // Verify modals are not closed on error
+ // mockOnClose was called once when opening OTP modal, but not again after error
+ expect(mockOnClose).toHaveBeenCalledTimes(1)
+ })
+
+ test('returns error when WebAuthn API is not available', async () => {
+ const otpCode = '12345678'
+ const mockStartResponse = {
+ challenge: 'dGVzdA==',
+ rp: {name: 'Test', id: 'example.com'},
+ user: {id: 'dGVzdA==', name: 'test@example.com'},
+ pubKeyCredParams: [],
+ timeout: 60000
+ }
+
+ mockStartWebauthnRegistration.mockResolvedValue(mockStartResponse)
+ // Remove credentials API
+ delete global.navigator.credentials
+
+ const {user} = renderWithProviders(
+ ,
+ {
+ wrapperProps: {appConfig: mockConfig.app}
+ }
+ )
+
+ // Open OTP modal
+ const registerButton = screen.getByText('Register Passkey')
+ await user.click(registerButton)
+
+ await waitFor(() => {
+ expect(otpVerificationHandler).toBeTruthy()
+ })
+
+ const result = await otpVerificationHandler(otpCode)
+
+ expect(result).toEqual({
+ success: false,
+ error: 'WebAuthn API not available in this browser'
+ })
+ })
+
+ test('returns error when user cancels WebAuthn prompt', async () => {
+ const otpCode = '12345678'
+ const mockStartResponse = {
+ challenge: 'dGVzdA==',
+ rp: {name: 'Test', id: 'example.com'},
+ user: {id: 'dGVzdA==', name: 'test@example.com'},
+ pubKeyCredParams: [],
+ timeout: 60000
+ }
+
+ const notAllowedError = new Error('User cancelled')
+ notAllowedError.name = 'NotAllowedError'
+
+ mockStartWebauthnRegistration.mockResolvedValue(mockStartResponse)
+ global.navigator.credentials.create.mockRejectedValue(notAllowedError)
+
+ const {user} = renderWithProviders(
+ ,
+ {
+ wrapperProps: {appConfig: mockConfig.app}
+ }
+ )
+
+ // Open OTP modal
+ const registerButton = screen.getByText('Register Passkey')
+ await user.click(registerButton)
+
+ await waitFor(() => {
+ expect(otpVerificationHandler).toBeTruthy()
+ })
+
+ const result = await otpVerificationHandler(otpCode)
+
+ expect(result).toEqual({
+ success: false,
+ error: 'Passkey registration was cancelled or timed out'
+ })
+ })
+
+ test('returns error when WebAuthn create returns null credential', async () => {
+ const otpCode = '12345678'
+ const mockStartResponse = {
+ challenge: 'dGVzdA==',
+ rp: {name: 'Test', id: 'example.com'},
+ user: {id: 'dGVzdA==', name: 'test@example.com'},
+ pubKeyCredParams: [],
+ timeout: 60000
+ }
+
+ mockStartWebauthnRegistration.mockResolvedValue(mockStartResponse)
+ global.navigator.credentials.create.mockResolvedValue(null)
+
+ const {user} = renderWithProviders(
+ ,
+ {
+ wrapperProps: {appConfig: mockConfig.app}
+ }
+ )
+
+ // Open OTP modal
+ const registerButton = screen.getByText('Register Passkey')
+ await user.click(registerButton)
+
+ await waitFor(() => {
+ expect(otpVerificationHandler).toBeTruthy()
+ })
+
+ const result = await otpVerificationHandler(otpCode)
+
+ expect(result).toEqual({
+ success: false,
+ error: 'Failed to create credential: user cancelled or operation failed'
+ })
+ })
+
+ test('returns error when finishWebauthnUserRegistration fails', async () => {
+ const otpCode = '12345678'
+ const mockStartResponse = {
+ challenge: 'dGVzdA==',
+ rp: {name: 'Test', id: 'example.com'},
+ user: {id: 'dGVzdA==', name: 'test@example.com'},
+ pubKeyCredParams: [],
+ timeout: 60000
+ }
+
+ const mockCredential = {
+ type: 'public-key',
+ id: 'test-id',
+ rawId: new ArrayBuffer(8),
+ response: {
+ attestationObject: new ArrayBuffer(16),
+ clientDataJSON: new ArrayBuffer(16)
+ }
+ }
+
+ const errorMessage = 'Failed to finish registration'
+
+ mockStartWebauthnRegistration.mockResolvedValue(mockStartResponse)
+ global.navigator.credentials.create.mockResolvedValue(mockCredential)
+ mockFinishWebauthnRegistration.mockRejectedValue(new Error(errorMessage))
+
+ const {user} = renderWithProviders(
+ ,
+ {
+ wrapperProps: {appConfig: mockConfig.app}
+ }
+ )
+
+ // Open OTP modal
+ const registerButton = screen.getByText('Register Passkey')
+ await user.click(registerButton)
+
+ await waitFor(() => {
+ expect(otpVerificationHandler).toBeTruthy()
+ })
+
+ const result = await otpVerificationHandler(otpCode)
+
+ expect(result).toEqual({
+ success: false,
+ error: errorMessage
+ })
+ })
+
+ test('handles AbortError from WebAuthn API', async () => {
+ const otpCode = '12345678'
+ const mockStartResponse = {
+ challenge: 'dGVzdA==',
+ rp: {name: 'Test', id: 'example.com'},
+ user: {id: 'dGVzdA==', name: 'test@example.com'},
+ pubKeyCredParams: [],
+ timeout: 60000
+ }
+
+ const abortError = new Error('Operation aborted')
+ abortError.name = 'AbortError'
+
+ mockStartWebauthnRegistration.mockResolvedValue(mockStartResponse)
+ global.navigator.credentials.create.mockRejectedValue(abortError)
+
+ const {user} = renderWithProviders(
+ ,
+ {
+ wrapperProps: {appConfig: mockConfig.app}
+ }
+ )
+
+ // Open OTP modal
+ const registerButton = screen.getByText('Register Passkey')
+ await user.click(registerButton)
+
+ await waitFor(() => {
+ expect(otpVerificationHandler).toBeTruthy()
+ })
+
+ const result = await otpVerificationHandler(otpCode)
+
+ expect(result).toEqual({
+ success: false,
+ error: 'Passkey registration was cancelled or timed out'
+ })
+ })
+ })
})
diff --git a/packages/template-retail-react-app/app/hooks/use-auth-modal.js b/packages/template-retail-react-app/app/hooks/use-auth-modal.js
index b9afaac5b2..64f210ab83 100644
--- a/packages/template-retail-react-app/app/hooks/use-auth-modal.js
+++ b/packages/template-retail-react-app/app/hooks/use-auth-modal.js
@@ -252,7 +252,6 @@ export const AuthModal = ({
) {
Promise.all([
window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),
-
window.PublicKeyCredential.isConditionalMediationAvailable()
]).then((results) => {
if (results.every((r) => r === true)) {
diff --git a/packages/template-retail-react-app/app/pages/registration/index.jsx b/packages/template-retail-react-app/app/pages/registration/index.jsx
index 2c2525d721..20c1bd64ea 100644
--- a/packages/template-retail-react-app/app/pages/registration/index.jsx
+++ b/packages/template-retail-react-app/app/pages/registration/index.jsx
@@ -68,7 +68,6 @@ const Registration = () => {
) {
Promise.all([
window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),
-
window.PublicKeyCredential.isConditionalMediationAvailable()
]).then((results) => {
if (results.every((r) => r === true)) {
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 f5049e1ab5..bbe644565a 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
@@ -2999,6 +2999,12 @@
"value": "Failed to authorize passkey registration"
}
],
+ "passkey_registration.modal.error.registration_failed": [
+ {
+ "type": 0,
+ "value": "Failed to register passkey"
+ }
+ ],
"passkey_registration.modal.label.nickname": [
{
"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 f5049e1ab5..bbe644565a 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
@@ -2999,6 +2999,12 @@
"value": "Failed to authorize passkey registration"
}
],
+ "passkey_registration.modal.error.registration_failed": [
+ {
+ "type": 0,
+ "value": "Failed to register passkey"
+ }
+ ],
"passkey_registration.modal.label.nickname": [
{
"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 9b6ff79828..3fa008d037 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
@@ -6287,6 +6287,20 @@
"value": "]"
}
],
+ "passkey_registration.modal.error.registration_failed": [
+ {
+ "type": 0,
+ "value": "["
+ },
+ {
+ "type": 0,
+ "value": "Ƒȧȧīŀḗḗḓ ŧǿǿ řḗḗɠīşŧḗḗř ƥȧȧşşķḗḗẏ"
+ },
+ {
+ "type": 0,
+ "value": "]"
+ }
+ ],
"passkey_registration.modal.label.nickname": [
{
"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 906ca70d62..488588f871 100644
--- a/packages/template-retail-react-app/translations/en-GB.json
+++ b/packages/template-retail-react-app/translations/en-GB.json
@@ -1240,6 +1240,9 @@
"passkey_registration.modal.error.authorize_failed": {
"defaultMessage": "Failed to authorize passkey registration"
},
+ "passkey_registration.modal.error.registration_failed": {
+ "defaultMessage": "Failed to register passkey"
+ },
"passkey_registration.modal.label.nickname": {
"defaultMessage": "Passkey Nickname (optional)"
},
diff --git a/packages/template-retail-react-app/translations/en-US.json b/packages/template-retail-react-app/translations/en-US.json
index 906ca70d62..488588f871 100644
--- a/packages/template-retail-react-app/translations/en-US.json
+++ b/packages/template-retail-react-app/translations/en-US.json
@@ -1240,6 +1240,9 @@
"passkey_registration.modal.error.authorize_failed": {
"defaultMessage": "Failed to authorize passkey registration"
},
+ "passkey_registration.modal.error.registration_failed": {
+ "defaultMessage": "Failed to register passkey"
+ },
"passkey_registration.modal.label.nickname": {
"defaultMessage": "Passkey Nickname (optional)"
},