Skip to content

Commit 0eb14be

Browse files
authored
Merge pull request #3611 from SalesforceCommerceCloud/W-20224220-passkey-in-auth-modal
@W-20224220 Passkey login in auth modal and login page
2 parents 338f103 + 8d46da1 commit 0eb14be

File tree

8 files changed

+897
-13
lines changed

8 files changed

+897
-13
lines changed

packages/template-retail-react-app/app/components/forms/useLoginFields.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ export default function useLoginFields({
2121
placeholder: 'you@email.com',
2222
defaultValue: '',
2323
type: 'email',
24-
autoComplete: 'email',
24+
// Include 'webauthn' for passkey autofill support
25+
autoComplete: 'email webauthn',
2526
rules: {
2627
required: formatMessage({
2728
defaultMessage: 'Please enter your email address.',

packages/template-retail-react-app/app/components/otp-auth/index.test.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,10 @@ describe('OtpAuth', () => {
8282
mockGetUsidWhenReady.mockResolvedValue('mock-usid-12345')
8383
mockGetEncUserIdWhenReady.mockResolvedValue('mock-enc-user-id')
8484
mockUseCurrentCustomer.mockReturnValue({
85-
data: null // Default to guest user
85+
data: {
86+
customerId: 'mock-customer-id',
87+
customerType: 'guest'
88+
}
8689
})
8790

8891
jest.clearAllMocks()
@@ -505,7 +508,12 @@ describe('OtpAuth', () => {
505508

506509
describe('Einstein Tracking - Privacy-Compliant User Identification', () => {
507510
test('uses USID for guest users when DNT is disabled', async () => {
508-
mockUseCurrentCustomer.mockReturnValue({data: null})
511+
mockUseCurrentCustomer.mockReturnValue({
512+
data: {
513+
customerId: 'guest-customer-id',
514+
customerType: 'guest'
515+
}
516+
})
509517

510518
renderWithProviders(
511519
<OtpAuth

packages/template-retail-react-app/app/components/passkey-registration-modal/index.test.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@
66
*/
77
import React from 'react'
88
import {screen, waitFor} from '@testing-library/react'
9-
import PasskeyRegistrationModal from '@salesforce/retail-react-app/app/components/passkey-registration-modal/index'
109
import {renderWithProviders} from '@salesforce/retail-react-app/app/utils/test-utils'
1110
import mockConfig from '@salesforce/retail-react-app/config/mocks/default'
1211
import {rest} from 'msw'
1312

13+
// Unmock the component so we can test it
14+
jest.unmock('@salesforce/retail-react-app/app/components/passkey-registration-modal')
15+
import PasskeyRegistrationModal from '@salesforce/retail-react-app/app/components/passkey-registration-modal/index'
16+
1417
// Mock Commerce SDK hooks
1518
const mockMutateAsync = jest.fn()
1619
const mockUseAuthHelper = jest.fn()
@@ -78,6 +81,23 @@ describe('PasskeyRegistrationModal', () => {
7881
create: jest.fn()
7982
}
8083

84+
// Mock PublicKeyCredential API
85+
global.PublicKeyCredential = {
86+
parseCreationOptionsFromJSON: jest.fn((options) => ({
87+
challenge: new Uint8Array([1, 2, 3]),
88+
rp: {name: 'Test RP', id: 'example.com'},
89+
user: {
90+
id: new Uint8Array([4, 5, 6]),
91+
name: 'test@example.com',
92+
displayName: 'Test User'
93+
},
94+
pubKeyCredParams: [{type: 'public-key', alg: -7}],
95+
...options
96+
})),
97+
isUserVerifyingPlatformAuthenticatorAvailable: jest.fn().mockResolvedValue(true),
98+
isConditionalMediationAvailable: jest.fn().mockResolvedValue(true)
99+
}
100+
81101
// Mock product API calls that may be triggered by components in the provider tree
82102
global.server.use(
83103
rest.get('*/products*', (req, res, ctx) => {
@@ -86,6 +106,10 @@ describe('PasskeyRegistrationModal', () => {
86106
)
87107
})
88108

109+
afterEach(() => {
110+
delete global.PublicKeyCredential
111+
})
112+
89113
describe('Rendering', () => {
90114
test('renders modal when isOpen is true', () => {
91115
renderWithProviders(<PasskeyRegistrationModal isOpen={true} onClose={mockOnClose} />, {

packages/template-retail-react-app/app/hooks/use-auth-modal.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {usePasswordReset} from '@salesforce/retail-react-app/app/hooks/use-passw
4242
import {isServer} from '@salesforce/retail-react-app/app/utils/utils'
4343
import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config'
4444
import {usePasskeyRegistration} from '@salesforce/retail-react-app/app/hooks/use-passkey-registration'
45+
import {usePasskeyLogin} from '@salesforce/retail-react-app/app/hooks/use-passkey-login'
4546
import {absoluteUrl} from '@salesforce/retail-react-app/app/utils/url'
4647
import useMultiSite from '@salesforce/retail-react-app/app/hooks/use-multi-site'
4748

@@ -72,7 +73,7 @@ export const AuthModal = ({
7273
const customerId = useCustomerId()
7374
const {isRegistered, customerType} = useCustomerType()
7475
const prevAuthType = usePrevious(customerType)
75-
76+
const {loginWithPasskey} = usePasskeyLogin()
7677
const customer = useCustomer(
7778
{parameters: {customerId}},
7879
{enabled: !!customerId && isRegistered}
@@ -207,6 +208,10 @@ export const AuthModal = ({
207208
if (isOpen) {
208209
setCurrentView(initialView)
209210
form.reset()
211+
// Prompt user to login without username (discoverable credentials)
212+
loginWithPasskey().catch((error) => {
213+
// TODO W-21056536: Add error message handling
214+
})
210215
}
211216
}, [isOpen])
212217

@@ -381,7 +386,7 @@ AuthModal.propTypes = {
381386
*/
382387
export const useAuthModal = (initialView = LOGIN_VIEW) => {
383388
const {isOpen, onOpen, onClose} = useDisclosure()
384-
const {passwordless = {}, social = {}} = getConfig().app.login || {}
389+
const {passwordless = {}, social = {}, passkey = {}} = getConfig().app.login || {}
385390

386391
return {
387392
initialView,

0 commit comments

Comments
 (0)