From 24c7a7bb93956f3db89d307b6f46c0819fa6fbc7 Mon Sep 17 00:00:00 2001 From: yunakim714 Date: Mon, 27 Jan 2025 11:44:02 -0500 Subject: [PATCH 01/21] add locatedfrom session item: --- .../app/components/_app-config/index.jsx | 2 +- .../app/pages/checkout/partials/contact-info.jsx | 2 ++ .../template-retail-react-app/app/pages/login/index.jsx | 8 ++++++-- packages/template-retail-react-app/app/ssr.js | 2 +- packages/template-retail-react-app/config/default.js | 4 ++-- packages/template-retail-react-app/package.json | 2 +- 6 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/template-retail-react-app/app/components/_app-config/index.jsx b/packages/template-retail-react-app/app/components/_app-config/index.jsx index 4ce4afd1c7..8ccf8b9e8f 100644 --- a/packages/template-retail-react-app/app/components/_app-config/index.jsx +++ b/packages/template-retail-react-app/app/components/_app-config/index.jsx @@ -70,7 +70,7 @@ const AppConfig = ({children, locals = {}}) => { headers={headers} // Uncomment 'enablePWAKitPrivateClient' to use SLAS private client login flows. // Make sure to also enable useSLASPrivateClient in ssr.js when enabling this setting. - // enablePWAKitPrivateClient={true} + enablePWAKitPrivateClient={true} logger={createLogger({packageName: 'commerce-sdk-react'})} > diff --git a/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx b/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx index 749542293c..cabe3e31ce 100644 --- a/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx +++ b/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx @@ -80,6 +80,8 @@ const ContactInfo = ({isSocialEnabled = false, isPasswordlessEnabled = false, id const handlePasswordlessLogin = async (email) => { try { + // Save the path where the user logged in + setSessionJSONItem('returnToPage', window.location.pathname) await authorizePasswordlessLogin.mutateAsync({userid: email}) setAuthModalView(EMAIL_VIEW) authModal.onOpen() diff --git a/packages/template-retail-react-app/app/pages/login/index.jsx b/packages/template-retail-react-app/app/pages/login/index.jsx index f32d27247a..80e321e139 100644 --- a/packages/template-retail-react-app/app/pages/login/index.jsx +++ b/packages/template-retail-react-app/app/pages/login/index.jsx @@ -75,6 +75,7 @@ const Login = ({initialView = LOGIN_VIEW}) => { const [currentView, setCurrentView] = useState(initialView) const [passwordlessLoginEmail, setPasswordlessLoginEmail] = useState('') const [loginType, setLoginType] = useState(LOGIN_TYPES.PASSWORD) + const locatedFrom = getSessionJSONItem('returnToPage') const handleMergeBasket = () => { const hasBasketItem = baskets?.baskets?.[0]?.productItems?.length > 0 @@ -109,6 +110,8 @@ const Login = ({initialView = LOGIN_VIEW}) => { const handlePasswordlessLogin = async (email) => { try { + // Save the path where the user logged in + setSessionJSONItem('returnToPage', window.location.pathname) await authorizePasswordlessLogin.mutateAsync({userid: email}) setCurrentView(EMAIL_VIEW) } catch (error) { @@ -169,9 +172,10 @@ const Login = ({initialView = LOGIN_VIEW}) => { // If customer is registered push to account page and merge the basket useEffect(() => { if (isRegistered) { + clearSessionJSONItem('returnToPage') handleMergeBasket() - if (location?.state?.directedFrom) { - navigate(location.state.directedFrom) + if (locatedFrom) { + navigate(locatedFrom) } else { navigate('/account') } diff --git a/packages/template-retail-react-app/app/ssr.js b/packages/template-retail-react-app/app/ssr.js index f152b457ac..a3f964ca8f 100644 --- a/packages/template-retail-react-app/app/ssr.js +++ b/packages/template-retail-react-app/app/ssr.js @@ -58,7 +58,7 @@ const options = { // Set this to false if using a SLAS public client // When setting this to true, make sure to also set the PWA_KIT_SLAS_CLIENT_SECRET // environment variable as this endpoint will return HTTP 501 if it is not set - useSLASPrivateClient: false, + useSLASPrivateClient: true, applySLASPrivateClientToEndpoints: /oauth2\/(token|passwordless|password\/(login|token|reset|action))/, diff --git a/packages/template-retail-react-app/config/default.js b/packages/template-retail-react-app/config/default.js index 7fbec2011e..55ca4e67a0 100644 --- a/packages/template-retail-react-app/config/default.js +++ b/packages/template-retail-react-app/config/default.js @@ -17,7 +17,7 @@ module.exports = { }, login: { passwordless: { - enabled: false, + enabled: true, callbackURI: process.env.PASSWORDLESS_LOGIN_CALLBACK_URI || '/passwordless-login-callback' }, @@ -39,7 +39,7 @@ module.exports = { commerceAPI: { proxyPath: `/mobify/proxy/api`, parameters: { - clientId: 'c9c45bfd-0ed3-4aa2-9971-40f88962b836', + clientId: '80255e22-8504-45e3-b1a3-749fd4475bb7', organizationId: 'f_ecom_zzrf_001', shortCode: '8o7m175y', siteId: 'RefArchGlobal' diff --git a/packages/template-retail-react-app/package.json b/packages/template-retail-react-app/package.json index d957eaf1c2..b363cfa6d8 100644 --- a/packages/template-retail-react-app/package.json +++ b/packages/template-retail-react-app/package.json @@ -1,5 +1,5 @@ { - "name": "@salesforce/retail-react-app", + "name": "wasatch-mrt", "version": "5.1.0-dev", "license": "See license in LICENSE", "author": "cc-pwa-kit@salesforce.com", From fb7c92ff0d5addfe2239c58ea30b212c0c9f5fa5 Mon Sep 17 00:00:00 2001 From: yunakim714 Date: Mon, 27 Jan 2025 11:50:37 -0500 Subject: [PATCH 02/21] lint --- .../app/pages/checkout/partials/contact-info.jsx | 1 + .../template-retail-react-app/app/pages/login/index.jsx | 7 ++++++- packages/template-retail-react-app/app/utils/jwt-utils.js | 4 +++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx b/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx index cabe3e31ce..66318a7d21 100644 --- a/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx +++ b/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx @@ -49,6 +49,7 @@ import { PASSWORDLESS_ERROR_MESSAGES, USER_NOT_FOUND_ERROR } from '@salesforce/retail-react-app/app/constants' +import {setSessionJSONItem} from '@salesforce/retail-react-app/app/utils/utils' const ContactInfo = ({isSocialEnabled = false, isPasswordlessEnabled = false, idps = []}) => { const {formatMessage} = useIntl() diff --git a/packages/template-retail-react-app/app/pages/login/index.jsx b/packages/template-retail-react-app/app/pages/login/index.jsx index 80e321e139..81dbfd73e4 100644 --- a/packages/template-retail-react-app/app/pages/login/index.jsx +++ b/packages/template-retail-react-app/app/pages/login/index.jsx @@ -37,7 +37,12 @@ import { USER_NOT_FOUND_ERROR } from '@salesforce/retail-react-app/app/constants' import {usePrevious} from '@salesforce/retail-react-app/app/hooks/use-previous' -import {isServer} from '@salesforce/retail-react-app/app/utils/utils' +import { + isServer, + getSessionJSONItem, + setSessionJSONItem, + clearSessionJSONItem +} from '@salesforce/retail-react-app/app/utils/utils' import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config' const LOGIN_ERROR_MESSAGE = defineMessage({ diff --git a/packages/template-retail-react-app/app/utils/jwt-utils.js b/packages/template-retail-react-app/app/utils/jwt-utils.js index 2f73d5c99a..eaf159fa0e 100644 --- a/packages/template-retail-react-app/app/utils/jwt-utils.js +++ b/packages/template-retail-react-app/app/utils/jwt-utils.js @@ -26,7 +26,9 @@ export const createRemoteJWKSet = (tenantId) => { const shortCode = appConfig.commerceAPI.parameters.shortCode const configTenantId = appConfig.commerceAPI.parameters.organizationId.replace(/^f_ecom_/, '') if (tenantId !== configTenantId) { - throw new Error(`The tenant ID in your PWA Kit configuration ("${configTenantId}") does not match the tenant ID in the SLAS callback token ("${tenantId}").`) + throw new Error( + `The tenant ID in your PWA Kit configuration ("${configTenantId}") does not match the tenant ID in the SLAS callback token ("${tenantId}").` + ) } const JWKS_URI = `${appOrigin}/${shortCode}/${tenantId}/oauth2/jwks` return joseCreateRemoteJWKSet(new URL(JWKS_URI)) From 02fc981ae0c76620a7f1f779a86a83ff89b592eb Mon Sep 17 00:00:00 2001 From: yunakim714 Date: Mon, 27 Jan 2025 12:45:23 -0500 Subject: [PATCH 03/21] use local storage --- .../app/components/_app-config/index.jsx | 2 +- packages/template-retail-react-app/app/ssr.js | 2 +- packages/template-retail-react-app/config/default.js | 4 ++-- packages/template-retail-react-app/package.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/template-retail-react-app/app/components/_app-config/index.jsx b/packages/template-retail-react-app/app/components/_app-config/index.jsx index 8ccf8b9e8f..4ce4afd1c7 100644 --- a/packages/template-retail-react-app/app/components/_app-config/index.jsx +++ b/packages/template-retail-react-app/app/components/_app-config/index.jsx @@ -70,7 +70,7 @@ const AppConfig = ({children, locals = {}}) => { headers={headers} // Uncomment 'enablePWAKitPrivateClient' to use SLAS private client login flows. // Make sure to also enable useSLASPrivateClient in ssr.js when enabling this setting. - enablePWAKitPrivateClient={true} + // enablePWAKitPrivateClient={true} logger={createLogger({packageName: 'commerce-sdk-react'})} > diff --git a/packages/template-retail-react-app/app/ssr.js b/packages/template-retail-react-app/app/ssr.js index a3f964ca8f..f152b457ac 100644 --- a/packages/template-retail-react-app/app/ssr.js +++ b/packages/template-retail-react-app/app/ssr.js @@ -58,7 +58,7 @@ const options = { // Set this to false if using a SLAS public client // When setting this to true, make sure to also set the PWA_KIT_SLAS_CLIENT_SECRET // environment variable as this endpoint will return HTTP 501 if it is not set - useSLASPrivateClient: true, + useSLASPrivateClient: false, applySLASPrivateClientToEndpoints: /oauth2\/(token|passwordless|password\/(login|token|reset|action))/, diff --git a/packages/template-retail-react-app/config/default.js b/packages/template-retail-react-app/config/default.js index 55ca4e67a0..7fbec2011e 100644 --- a/packages/template-retail-react-app/config/default.js +++ b/packages/template-retail-react-app/config/default.js @@ -17,7 +17,7 @@ module.exports = { }, login: { passwordless: { - enabled: true, + enabled: false, callbackURI: process.env.PASSWORDLESS_LOGIN_CALLBACK_URI || '/passwordless-login-callback' }, @@ -39,7 +39,7 @@ module.exports = { commerceAPI: { proxyPath: `/mobify/proxy/api`, parameters: { - clientId: '80255e22-8504-45e3-b1a3-749fd4475bb7', + clientId: 'c9c45bfd-0ed3-4aa2-9971-40f88962b836', organizationId: 'f_ecom_zzrf_001', shortCode: '8o7m175y', siteId: 'RefArchGlobal' diff --git a/packages/template-retail-react-app/package.json b/packages/template-retail-react-app/package.json index b363cfa6d8..d957eaf1c2 100644 --- a/packages/template-retail-react-app/package.json +++ b/packages/template-retail-react-app/package.json @@ -1,5 +1,5 @@ { - "name": "wasatch-mrt", + "name": "@salesforce/retail-react-app", "version": "5.1.0-dev", "license": "See license in LICENSE", "author": "cc-pwa-kit@salesforce.com", From 272405a66fdd63c302602d7ce061aef5244d8395 Mon Sep 17 00:00:00 2001 From: yunakim714 Date: Mon, 27 Jan 2025 12:49:11 -0500 Subject: [PATCH 04/21] use localstorage --- .../template-retail-react-app/app/hooks/use-auth-modal.js | 2 ++ .../app/pages/checkout/partials/contact-info.jsx | 2 +- .../template-retail-react-app/app/pages/login/index.jsx | 7 ++++--- 3 files changed, 7 insertions(+), 4 deletions(-) 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 4d09cf4946..a6757d3c0a 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 @@ -105,6 +105,8 @@ export const AuthModal = ({ const handlePasswordlessLogin = async (email) => { try { + // Save the path where the user logged in + window.localStorage.setItem('returnToPage', window.location.pathname) await authorizePasswordlessLogin.mutateAsync({userid: email}) setCurrentView(EMAIL_VIEW) } catch (error) { diff --git a/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx b/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx index 66318a7d21..6cc1663ff7 100644 --- a/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx +++ b/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx @@ -82,7 +82,7 @@ const ContactInfo = ({isSocialEnabled = false, isPasswordlessEnabled = false, id const handlePasswordlessLogin = async (email) => { try { // Save the path where the user logged in - setSessionJSONItem('returnToPage', window.location.pathname) + window.localStorage.setItem('returnToPage', window.location.pathname) await authorizePasswordlessLogin.mutateAsync({userid: email}) setAuthModalView(EMAIL_VIEW) authModal.onOpen() diff --git a/packages/template-retail-react-app/app/pages/login/index.jsx b/packages/template-retail-react-app/app/pages/login/index.jsx index 81dbfd73e4..e227549e78 100644 --- a/packages/template-retail-react-app/app/pages/login/index.jsx +++ b/packages/template-retail-react-app/app/pages/login/index.jsx @@ -80,7 +80,7 @@ const Login = ({initialView = LOGIN_VIEW}) => { const [currentView, setCurrentView] = useState(initialView) const [passwordlessLoginEmail, setPasswordlessLoginEmail] = useState('') const [loginType, setLoginType] = useState(LOGIN_TYPES.PASSWORD) - const locatedFrom = getSessionJSONItem('returnToPage') + let locatedFrom const handleMergeBasket = () => { const hasBasketItem = baskets?.baskets?.[0]?.productItems?.length > 0 @@ -116,7 +116,7 @@ const Login = ({initialView = LOGIN_VIEW}) => { const handlePasswordlessLogin = async (email) => { try { // Save the path where the user logged in - setSessionJSONItem('returnToPage', window.location.pathname) + window.localStorage.setItem('returnToPage', window.location.pathname) await authorizePasswordlessLogin.mutateAsync({userid: email}) setCurrentView(EMAIL_VIEW) } catch (error) { @@ -158,6 +158,8 @@ const Login = ({initialView = LOGIN_VIEW}) => { useEffect(() => { if (path === PASSWORDLESS_LOGIN_LANDING_PATH && isSuccessCustomerBaskets) { const token = queryParams.get('token') + locatedFrom = window.localStorage.getItem('returnToPage') + window.localStorage.removeItem('returnToPage') const passwordlessLogin = async () => { try { @@ -177,7 +179,6 @@ const Login = ({initialView = LOGIN_VIEW}) => { // If customer is registered push to account page and merge the basket useEffect(() => { if (isRegistered) { - clearSessionJSONItem('returnToPage') handleMergeBasket() if (locatedFrom) { navigate(locatedFrom) From 10a22c39000e82262f22c750fd46af62062031ff Mon Sep 17 00:00:00 2001 From: yunakim714 Date: Mon, 27 Jan 2025 13:04:29 -0500 Subject: [PATCH 05/21] cleanup --- .../app/pages/checkout/partials/contact-info.jsx | 1 - .../app/pages/login/index.jsx | 10 ++-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx b/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx index 6cc1663ff7..5767a0a2b8 100644 --- a/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx +++ b/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx @@ -49,7 +49,6 @@ import { PASSWORDLESS_ERROR_MESSAGES, USER_NOT_FOUND_ERROR } from '@salesforce/retail-react-app/app/constants' -import {setSessionJSONItem} from '@salesforce/retail-react-app/app/utils/utils' const ContactInfo = ({isSocialEnabled = false, isPasswordlessEnabled = false, idps = []}) => { const {formatMessage} = useIntl() diff --git a/packages/template-retail-react-app/app/pages/login/index.jsx b/packages/template-retail-react-app/app/pages/login/index.jsx index e227549e78..ea3b202dfe 100644 --- a/packages/template-retail-react-app/app/pages/login/index.jsx +++ b/packages/template-retail-react-app/app/pages/login/index.jsx @@ -37,12 +37,7 @@ import { USER_NOT_FOUND_ERROR } from '@salesforce/retail-react-app/app/constants' import {usePrevious} from '@salesforce/retail-react-app/app/hooks/use-previous' -import { - isServer, - getSessionJSONItem, - setSessionJSONItem, - clearSessionJSONItem -} from '@salesforce/retail-react-app/app/utils/utils' +import {isServer} from '@salesforce/retail-react-app/app/utils/utils' import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config' const LOGIN_ERROR_MESSAGE = defineMessage({ @@ -158,8 +153,6 @@ const Login = ({initialView = LOGIN_VIEW}) => { useEffect(() => { if (path === PASSWORDLESS_LOGIN_LANDING_PATH && isSuccessCustomerBaskets) { const token = queryParams.get('token') - locatedFrom = window.localStorage.getItem('returnToPage') - window.localStorage.removeItem('returnToPage') const passwordlessLogin = async () => { try { @@ -180,6 +173,7 @@ const Login = ({initialView = LOGIN_VIEW}) => { useEffect(() => { if (isRegistered) { handleMergeBasket() + locatedFrom = window.localStorage.getItem('returnToPage') if (locatedFrom) { navigate(locatedFrom) } else { From c2a694165a0b5580f9eca6460f5ac88b50e01a85 Mon Sep 17 00:00:00 2001 From: yunakim714 Date: Mon, 27 Jan 2025 13:08:14 -0500 Subject: [PATCH 06/21] login page s hould redirect to account --- packages/template-retail-react-app/app/pages/login/index.jsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/template-retail-react-app/app/pages/login/index.jsx b/packages/template-retail-react-app/app/pages/login/index.jsx index ea3b202dfe..df794de09e 100644 --- a/packages/template-retail-react-app/app/pages/login/index.jsx +++ b/packages/template-retail-react-app/app/pages/login/index.jsx @@ -75,7 +75,6 @@ const Login = ({initialView = LOGIN_VIEW}) => { const [currentView, setCurrentView] = useState(initialView) const [passwordlessLoginEmail, setPasswordlessLoginEmail] = useState('') const [loginType, setLoginType] = useState(LOGIN_TYPES.PASSWORD) - let locatedFrom const handleMergeBasket = () => { const hasBasketItem = baskets?.baskets?.[0]?.productItems?.length > 0 @@ -110,8 +109,6 @@ const Login = ({initialView = LOGIN_VIEW}) => { const handlePasswordlessLogin = async (email) => { try { - // Save the path where the user logged in - window.localStorage.setItem('returnToPage', window.location.pathname) await authorizePasswordlessLogin.mutateAsync({userid: email}) setCurrentView(EMAIL_VIEW) } catch (error) { @@ -173,7 +170,7 @@ const Login = ({initialView = LOGIN_VIEW}) => { useEffect(() => { if (isRegistered) { handleMergeBasket() - locatedFrom = window.localStorage.getItem('returnToPage') + const locatedFrom = window.localStorage.getItem('returnToPage') if (locatedFrom) { navigate(locatedFrom) } else { From 4b64139dcd975320afac686f6a6b58728a98faf3 Mon Sep 17 00:00:00 2001 From: yunakim714 Date: Mon, 27 Jan 2025 13:10:43 -0500 Subject: [PATCH 07/21] remove item after reading --- packages/template-retail-react-app/app/pages/login/index.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/template-retail-react-app/app/pages/login/index.jsx b/packages/template-retail-react-app/app/pages/login/index.jsx index df794de09e..b9a46dd3d4 100644 --- a/packages/template-retail-react-app/app/pages/login/index.jsx +++ b/packages/template-retail-react-app/app/pages/login/index.jsx @@ -171,6 +171,7 @@ const Login = ({initialView = LOGIN_VIEW}) => { if (isRegistered) { handleMergeBasket() const locatedFrom = window.localStorage.getItem('returnToPage') + window.localStorage.removeItem('returnToPage') if (locatedFrom) { navigate(locatedFrom) } else { From 1d20200a4af7724c7d29f910626a3207f672d7e8 Mon Sep 17 00:00:00 2001 From: yunakim714 Date: Tue, 28 Jan 2025 10:33:13 -0500 Subject: [PATCH 08/21] check that setitem is being called when logging in with passwordless --- .../app/pages/checkout/partials/contact-info.test.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.test.js b/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.test.js index aac8c5933d..9a8815c199 100644 --- a/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.test.js +++ b/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.test.js @@ -105,7 +105,14 @@ describe('passwordless and social disabled', () => { describe('passwordless enabled', () => { let currentBasket = JSON.parse(JSON.stringify(scapiBasketWithItem)) + + beforeAll(() => { + jest.spyOn(window.localStorage, 'setItem') + }) + beforeEach(() => { + window.localStorage.setItem.mockClear() + global.server.use( rest.put('*/baskets/:basketId/customer', (req, res, ctx) => { currentBasket.customerInfo.email = validEmail @@ -114,6 +121,10 @@ describe('passwordless enabled', () => { ) }) + afterAll(() => { + window.localStorage.setItem.mockRestore() + }) + test('renders component', async () => { const {getByRole} = renderWithProviders() expect(getByRole('button', {name: 'Checkout as Guest'})).toBeInTheDocument() @@ -166,6 +177,7 @@ describe('passwordless enabled', () => { const withinForm = within(screen.getByTestId('sf-form-resend-passwordless-email')) expect(withinForm.getByText(/Check Your Email/i)).toBeInTheDocument() expect(withinForm.getByText(validEmail)).toBeInTheDocument() + expect(window.localStorage.setItem).toHaveBeenCalled() }) // resend the email From a45ed602d06000aec0ca37e8a3596e451ff2c586 Mon Sep 17 00:00:00 2001 From: yunakim714 Date: Tue, 28 Jan 2025 11:02:03 -0500 Subject: [PATCH 09/21] test passwordless login in useauthmodal hook file --- .../app/hooks/use-auth-modal.test.js | 86 +++++++++++++++++-- 1 file changed, 78 insertions(+), 8 deletions(-) diff --git a/packages/template-retail-react-app/app/hooks/use-auth-modal.test.js b/packages/template-retail-react-app/app/hooks/use-auth-modal.test.js index a13580c2d4..f9d9931942 100644 --- a/packages/template-retail-react-app/app/hooks/use-auth-modal.test.js +++ b/packages/template-retail-react-app/app/hooks/use-auth-modal.test.js @@ -19,6 +19,7 @@ import Account from '@salesforce/retail-react-app/app/pages/account' import {rest} from 'msw' import {mockedRegisteredCustomer} from '@salesforce/retail-react-app/app/mocks/mock-data' import * as ReactHookForm from 'react-hook-form' +import {AuthHelpers} from '@salesforce/commerce-sdk-react' jest.setTimeout(60000) @@ -47,6 +48,21 @@ const mockRegisteredCustomer = { login: 'customer@test.com' } +const mockAuthHelperFunctions = { + [AuthHelpers.AuthorizePasswordless]: {mutateAsync: jest.fn()}, + [AuthHelpers.Register]: {mutateAsync: jest.fn()} +} + +jest.mock('@salesforce/commerce-sdk-react', () => { + const originalModule = jest.requireActual('@salesforce/commerce-sdk-react') + return { + ...originalModule, + useAuthHelper: jest + .fn() + .mockImplementation((helperType) => mockAuthHelperFunctions[helperType]) + } +}) + let authModal = undefined const MockedComponent = (props) => { const {initialView, isPasswordlessEnabled = false} = props @@ -155,17 +171,71 @@ test('Renders check email modal on email mode', async () => { mockUseForm.mockRestore() }) -test('Renders passwordless login when enabled', async () => { - const user = userEvent.setup() +describe('Passwordless enabled', () => { + beforeAll(() => { + jest.spyOn(window.localStorage, 'setItem') + }) - renderWithProviders() + beforeEach(() => { + jest.resetModules() + window.localStorage.setItem.mockClear() + }) - // open the modal - const trigger = screen.getByText(/open modal/i) - await user.click(trigger) + afterAll(() => { + window.localStorage.setItem.mockRestore() + }) - await waitFor(() => { - expect(screen.getByText(/continue securely/i)).toBeInTheDocument() + test('Renders passwordless login when enabled', async () => { + const user = userEvent.setup() + + renderWithProviders() + + // open the modal + const trigger = screen.getByText(/open modal/i) + await user.click(trigger) + + await waitFor(() => { + expect(screen.getByText(/continue securely/i)).toBeInTheDocument() + }) + }) + + test('Allows passwordless login', async () => { + const {user} = renderWithProviders() + const validEmail = 'test@salesforce.com' + + // open the modal + const trigger = screen.getByText(/open modal/i) + await user.click(trigger) + + await waitFor(() => { + expect(screen.getByText(/continue securely/i)).toBeInTheDocument() + }) + + // enter a valid email address + await user.type(screen.getByLabelText('Email'), validEmail) + + // initiate passwordless login + const passwordlessLoginButton = screen.getByText(/continue securely/i) + // Click the button twice as the isPasswordlessLoginClicked state doesn't change after the first click + await user.click(passwordlessLoginButton) + await user.click(passwordlessLoginButton) + expect( + mockAuthHelperFunctions[AuthHelpers.AuthorizePasswordless].mutateAsync + ).toHaveBeenCalledWith({userid: validEmail}) + + // check that check email modal is open + await waitFor(() => { + const withinForm = within(screen.getByTestId('sf-form-resend-passwordless-email')) + expect(withinForm.getByText(/Check Your Email/i)).toBeInTheDocument() + expect(withinForm.getByText(validEmail)).toBeInTheDocument() + expect(window.localStorage.setItem).toHaveBeenCalled() + }) + + // resend the email + user.click(screen.getByText(/Resend Link/i)) + expect( + mockAuthHelperFunctions[AuthHelpers.AuthorizePasswordless].mutateAsync + ).toHaveBeenCalledWith({userid: validEmail}) }) }) From 54436ac4ae57325161f84281620c956717bf50ed Mon Sep 17 00:00:00 2001 From: yunakim714 Date: Tue, 28 Jan 2025 11:16:25 -0500 Subject: [PATCH 10/21] add localstorage tests in login page --- .../app/pages/login/index.test.js | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/packages/template-retail-react-app/app/pages/login/index.test.js b/packages/template-retail-react-app/app/pages/login/index.test.js index 66268ef08d..b2d8df5888 100644 --- a/packages/template-retail-react-app/app/pages/login/index.test.js +++ b/packages/template-retail-react-app/app/pages/login/index.test.js @@ -19,6 +19,8 @@ import Registration from '@salesforce/retail-react-app/app/pages/registration' import ResetPassword from '@salesforce/retail-react-app/app/pages/reset-password' import mockConfig from '@salesforce/retail-react-app/config/mocks/default' import {mockedRegisteredCustomer} from '@salesforce/retail-react-app/app/mocks/mock-data' +import * as sdk from '@salesforce/commerce-sdk-react' + const mockMergedBasket = { basketId: 'a10ff320829cb0eef93ca5310a', currency: 'USD', @@ -77,6 +79,11 @@ afterEach(() => { }) describe('Logging in tests', function () { + beforeAll(() => { + jest.spyOn(window.localStorage, 'getItem') + jest.spyOn(window.localStorage, 'removeItem') + }) + beforeEach(() => { global.server.use( rest.post('*/oauth2/token', (req, res, ctx) => @@ -97,6 +104,12 @@ describe('Logging in tests', function () { }) ) }) + + afterAll(() => { + window.localStorage.getItem.mockRestore() + window.localStorage.removeItem.mockRestore() + }) + test('Allows customer to sign in to their account', async () => { const {user} = renderWithProviders(, { wrapperProps: { @@ -134,6 +147,28 @@ describe('Logging in tests', function () { expect(screen.getByText(/My Profile/i)).toBeInTheDocument() }) }) + + test('If customer is registered, check the returnToPage local storage item', async () => { + jest.doMock('@salesforce/commerce-sdk-react', () => ({ + ...jest.requireActual('@salesforce/commerce-sdk-react'), + useCustomerType: jest.fn(() => { + return {isRegistered: true} + }) + })) + + renderWithProviders(, { + wrapperProps: { + siteAlias: 'uk', + locale: {id: 'en-GB'}, + appConfig: mockConfig.app, + bypassAuth: false + } + }) + await waitFor(() => { + expect(window.localStorage.getItem).toHaveBeenCalled() + expect(window.localStorage.removeItem).toHaveBeenCalled() + }) + }) }) describe('Error while logging in', function () { From 84ab1684a2e653826fa5309f8bd3c6f73be0e1c6 Mon Sep 17 00:00:00 2001 From: yunakim714 Date: Wed, 29 Jan 2025 11:20:46 -0500 Subject: [PATCH 11/21] redirect via query param on magic link --- packages/commerce-sdk-react/src/auth/index.ts | 2 +- .../app/hooks/use-auth-modal.js | 15 ++++++-- .../app/hooks/use-auth-modal.test.js | 24 +++++-------- .../pages/checkout/partials/contact-info.jsx | 16 +++++++-- .../checkout/partials/contact-info.test.js | 26 +++++++------- .../app/pages/login/index.jsx | 10 +++--- .../app/pages/login/index.test.js | 34 ------------------- packages/template-retail-react-app/app/ssr.js | 9 +++-- .../app/utils/jwt-utils.js | 2 +- 9 files changed, 61 insertions(+), 77 deletions(-) diff --git a/packages/commerce-sdk-react/src/auth/index.ts b/packages/commerce-sdk-react/src/auth/index.ts index 1df2824d05..89926f526b 100644 --- a/packages/commerce-sdk-react/src/auth/index.ts +++ b/packages/commerce-sdk-react/src/auth/index.ts @@ -1112,7 +1112,7 @@ class Auth { */ async authorizePasswordless(parameters: AuthorizePasswordlessParams) { const userid = parameters.userid - const callbackURI = this.passwordlessLoginCallbackURI + const callbackURI = parameters.callbackURI || this.passwordlessLoginCallbackURI const usid = this.get('usid') const mode = callbackURI ? 'callback' : 'sms' 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 a6757d3c0a..4086848ab0 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 @@ -44,6 +44,8 @@ import {usePrevious} from '@salesforce/retail-react-app/app/hooks/use-previous' import {usePasswordReset} from '@salesforce/retail-react-app/app/hooks/use-password-reset' import {isServer} from '@salesforce/retail-react-app/app/utils/utils' import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config' +import {isAbsoluteURL} from '@salesforce/retail-react-app/app/page-designer/utils' +import {useAppOrigin} from '@salesforce/retail-react-app/app/hooks/use-app-origin' export const LOGIN_VIEW = 'login' export const REGISTER_VIEW = 'register' @@ -84,11 +86,13 @@ export const AuthModal = ({ const toast = useToast() const login = useAuthHelper(AuthHelpers.LoginRegisteredUserB2C) const register = useAuthHelper(AuthHelpers.Register) + const appOrigin = useAppOrigin() const [loginType, setLoginType] = useState(LOGIN_TYPES.PASSWORD) const [passwordlessLoginEmail, setPasswordlessLoginEmail] = useState(initialEmail) const {getPasswordResetToken} = usePasswordReset() const authorizePasswordlessLogin = useAuthHelper(AuthHelpers.AuthorizePasswordless) + const passwordlessConfigCallback = getConfig().app.login?.passwordless?.callbackURI const {data: baskets} = useCustomerBaskets( {parameters: {customerId}}, @@ -105,9 +109,14 @@ export const AuthModal = ({ const handlePasswordlessLogin = async (email) => { try { - // Save the path where the user logged in - window.localStorage.setItem('returnToPage', window.location.pathname) - await authorizePasswordlessLogin.mutateAsync({userid: email}) + const callbackURL = isAbsoluteURL(passwordlessConfigCallback) + ? passwordlessConfigCallback + : `${appOrigin}${passwordlessConfigCallback}` + const redirectPath = window.location.pathname + await authorizePasswordlessLogin.mutateAsync({ + userid: email, + callbackURI: `${callbackURL}?redirectUrl=${redirectPath}` + }) setCurrentView(EMAIL_VIEW) } catch (error) { const message = USER_NOT_FOUND_ERROR.test(error.message) diff --git a/packages/template-retail-react-app/app/hooks/use-auth-modal.test.js b/packages/template-retail-react-app/app/hooks/use-auth-modal.test.js index f9d9931942..8c5a8775e4 100644 --- a/packages/template-retail-react-app/app/hooks/use-auth-modal.test.js +++ b/packages/template-retail-react-app/app/hooks/use-auth-modal.test.js @@ -172,19 +172,6 @@ test('Renders check email modal on email mode', async () => { }) describe('Passwordless enabled', () => { - beforeAll(() => { - jest.spyOn(window.localStorage, 'setItem') - }) - - beforeEach(() => { - jest.resetModules() - window.localStorage.setItem.mockClear() - }) - - afterAll(() => { - window.localStorage.setItem.mockRestore() - }) - test('Renders passwordless login when enabled', async () => { const user = userEvent.setup() @@ -221,21 +208,26 @@ describe('Passwordless enabled', () => { await user.click(passwordlessLoginButton) expect( mockAuthHelperFunctions[AuthHelpers.AuthorizePasswordless].mutateAsync - ).toHaveBeenCalledWith({userid: validEmail}) + ).toHaveBeenCalledWith({ + userid: validEmail, + callbackURI: 'https://webhook.site/27761b71-50c1-4097-a600-21a3b89a546c?redirectUrl=/' + }) // check that check email modal is open await waitFor(() => { const withinForm = within(screen.getByTestId('sf-form-resend-passwordless-email')) expect(withinForm.getByText(/Check Your Email/i)).toBeInTheDocument() expect(withinForm.getByText(validEmail)).toBeInTheDocument() - expect(window.localStorage.setItem).toHaveBeenCalled() }) // resend the email user.click(screen.getByText(/Resend Link/i)) expect( mockAuthHelperFunctions[AuthHelpers.AuthorizePasswordless].mutateAsync - ).toHaveBeenCalledWith({userid: validEmail}) + ).toHaveBeenCalledWith({ + userid: validEmail, + callbackURI: 'https://webhook.site/27761b71-50c1-4097-a600-21a3b89a546c?redirectUrl=/' + }) }) }) diff --git a/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx b/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx index 5767a0a2b8..22f054654b 100644 --- a/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx +++ b/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx @@ -41,7 +41,10 @@ import { import useNavigation from '@salesforce/retail-react-app/app/hooks/use-navigation' 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 {isAbsoluteURL} from '@salesforce/retail-react-app/app/page-designer/utils' +import {useAppOrigin} from '@salesforce/retail-react-app/app/hooks/use-app-origin' import {AuthHelpers, useAuthHelper, useShopperBasketsMutation} from '@salesforce/commerce-sdk-react' +import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config' import { API_ERROR_MESSAGE, FEATURE_UNAVAILABLE_ERROR_MESSAGE, @@ -55,6 +58,7 @@ const ContactInfo = ({isSocialEnabled = false, isPasswordlessEnabled = false, id const navigate = useNavigation() const {data: customer} = useCurrentCustomer() const {data: basket} = useCurrentBasket() + const appOrigin = useAppOrigin() const login = useAuthHelper(AuthHelpers.LoginRegisteredUserB2C) const logout = useAuthHelper(AuthHelpers.Logout) const authorizePasswordlessLogin = useAuthHelper(AuthHelpers.AuthorizePasswordless) @@ -77,12 +81,18 @@ const ContactInfo = ({isSocialEnabled = false, isPasswordlessEnabled = false, id const [authModalView, setAuthModalView] = useState(PASSWORD_VIEW) const authModal = useAuthModal(authModalView) const [isPasswordlessLoginClicked, setIsPasswordlessLoginClicked] = useState(false) + const passwordlessConfigCallback = getConfig().app.login?.passwordless?.callbackURI const handlePasswordlessLogin = async (email) => { try { - // Save the path where the user logged in - window.localStorage.setItem('returnToPage', window.location.pathname) - await authorizePasswordlessLogin.mutateAsync({userid: email}) + const callbackURL = isAbsoluteURL(passwordlessConfigCallback) + ? passwordlessConfigCallback + : `${appOrigin}${passwordlessConfigCallback}` + const redirectPath = window.location.pathname + await authorizePasswordlessLogin.mutateAsync({ + userid: email, + callbackURI: `${callbackURL}?redirectUrl=${redirectPath}` + }) setAuthModalView(EMAIL_VIEW) authModal.onOpen() } catch (error) { diff --git a/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.test.js b/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.test.js index 9a8815c199..34333b73b5 100644 --- a/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.test.js +++ b/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.test.js @@ -106,13 +106,7 @@ describe('passwordless and social disabled', () => { describe('passwordless enabled', () => { let currentBasket = JSON.parse(JSON.stringify(scapiBasketWithItem)) - beforeAll(() => { - jest.spyOn(window.localStorage, 'setItem') - }) - beforeEach(() => { - window.localStorage.setItem.mockClear() - global.server.use( rest.put('*/baskets/:basketId/customer', (req, res, ctx) => { currentBasket.customerInfo.email = validEmail @@ -121,10 +115,6 @@ describe('passwordless enabled', () => { ) }) - afterAll(() => { - window.localStorage.setItem.mockRestore() - }) - test('renders component', async () => { const {getByRole} = renderWithProviders() expect(getByRole('button', {name: 'Checkout as Guest'})).toBeInTheDocument() @@ -158,6 +148,9 @@ describe('passwordless enabled', () => { }) test('allows passwordless login', async () => { + jest.spyOn(window, 'location', 'get').mockReturnValue({ + pathname: '/checkout' + }) const {user} = renderWithProviders() // enter a valid email address @@ -170,21 +163,28 @@ describe('passwordless enabled', () => { await user.click(passwordlessLoginButton) expect( mockAuthHelperFunctions[AuthHelpers.AuthorizePasswordless].mutateAsync - ).toHaveBeenCalledWith({userid: validEmail}) + ).toHaveBeenCalledWith({ + userid: validEmail, + callbackURI: + 'https://webhook.site/27761b71-50c1-4097-a600-21a3b89a546c?redirectUrl=/checkout' + }) // check that check email modal is open await waitFor(() => { const withinForm = within(screen.getByTestId('sf-form-resend-passwordless-email')) expect(withinForm.getByText(/Check Your Email/i)).toBeInTheDocument() expect(withinForm.getByText(validEmail)).toBeInTheDocument() - expect(window.localStorage.setItem).toHaveBeenCalled() }) // resend the email user.click(screen.getByText(/Resend Link/i)) expect( mockAuthHelperFunctions[AuthHelpers.AuthorizePasswordless].mutateAsync - ).toHaveBeenCalledWith({userid: validEmail}) + ).toHaveBeenCalledWith({ + userid: validEmail, + callbackURI: + 'https://webhook.site/27761b71-50c1-4097-a600-21a3b89a546c?redirectUrl=/checkout' + }) }) test('allows login using password', async () => { diff --git a/packages/template-retail-react-app/app/pages/login/index.jsx b/packages/template-retail-react-app/app/pages/login/index.jsx index b9a46dd3d4..86273087db 100644 --- a/packages/template-retail-react-app/app/pages/login/index.jsx +++ b/packages/template-retail-react-app/app/pages/login/index.jsx @@ -75,6 +75,7 @@ const Login = ({initialView = LOGIN_VIEW}) => { const [currentView, setCurrentView] = useState(initialView) const [passwordlessLoginEmail, setPasswordlessLoginEmail] = useState('') const [loginType, setLoginType] = useState(LOGIN_TYPES.PASSWORD) + const [redirectPath, setRedirectPath] = useState('') const handleMergeBasket = () => { const hasBasketItem = baskets?.baskets?.[0]?.productItems?.length > 0 @@ -150,6 +151,8 @@ const Login = ({initialView = LOGIN_VIEW}) => { useEffect(() => { if (path === PASSWORDLESS_LOGIN_LANDING_PATH && isSuccessCustomerBaskets) { const token = queryParams.get('token') + const redirect_url = queryParams.get('redirect_url') + setRedirectPath(redirect_url) const passwordlessLogin = async () => { try { @@ -170,10 +173,9 @@ const Login = ({initialView = LOGIN_VIEW}) => { useEffect(() => { if (isRegistered) { handleMergeBasket() - const locatedFrom = window.localStorage.getItem('returnToPage') - window.localStorage.removeItem('returnToPage') - if (locatedFrom) { - navigate(locatedFrom) + if (redirectPath) { + navigate(redirectPath) + setRedirectPath('') } else { navigate('/account') } diff --git a/packages/template-retail-react-app/app/pages/login/index.test.js b/packages/template-retail-react-app/app/pages/login/index.test.js index b2d8df5888..d3aeca5a48 100644 --- a/packages/template-retail-react-app/app/pages/login/index.test.js +++ b/packages/template-retail-react-app/app/pages/login/index.test.js @@ -19,7 +19,6 @@ import Registration from '@salesforce/retail-react-app/app/pages/registration' import ResetPassword from '@salesforce/retail-react-app/app/pages/reset-password' import mockConfig from '@salesforce/retail-react-app/config/mocks/default' import {mockedRegisteredCustomer} from '@salesforce/retail-react-app/app/mocks/mock-data' -import * as sdk from '@salesforce/commerce-sdk-react' const mockMergedBasket = { basketId: 'a10ff320829cb0eef93ca5310a', @@ -75,15 +74,9 @@ beforeEach(() => { }) afterEach(() => { jest.resetModules() - localStorage.clear() }) describe('Logging in tests', function () { - beforeAll(() => { - jest.spyOn(window.localStorage, 'getItem') - jest.spyOn(window.localStorage, 'removeItem') - }) - beforeEach(() => { global.server.use( rest.post('*/oauth2/token', (req, res, ctx) => @@ -105,11 +98,6 @@ describe('Logging in tests', function () { ) }) - afterAll(() => { - window.localStorage.getItem.mockRestore() - window.localStorage.removeItem.mockRestore() - }) - test('Allows customer to sign in to their account', async () => { const {user} = renderWithProviders(, { wrapperProps: { @@ -147,28 +135,6 @@ describe('Logging in tests', function () { expect(screen.getByText(/My Profile/i)).toBeInTheDocument() }) }) - - test('If customer is registered, check the returnToPage local storage item', async () => { - jest.doMock('@salesforce/commerce-sdk-react', () => ({ - ...jest.requireActual('@salesforce/commerce-sdk-react'), - useCustomerType: jest.fn(() => { - return {isRegistered: true} - }) - })) - - renderWithProviders(, { - wrapperProps: { - siteAlias: 'uk', - locale: {id: 'en-GB'}, - appConfig: mockConfig.app, - bypassAuth: false - } - }) - await waitFor(() => { - expect(window.localStorage.getItem).toHaveBeenCalled() - expect(window.localStorage.removeItem).toHaveBeenCalled() - }) - }) }) describe('Error while logging in', function () { diff --git a/packages/template-retail-react-app/app/ssr.js b/packages/template-retail-react-app/app/ssr.js index f152b457ac..8639210081 100644 --- a/packages/template-retail-react-app/app/ssr.js +++ b/packages/template-retail-react-app/app/ssr.js @@ -80,7 +80,7 @@ const passwordlessLoginCallback = // Reusable function to handle sending a magic link email. // By default, this implementation uses Marketing Cloud. -async function sendMagicLinkEmail(req, res, landingPath, emailTemplate) { +async function sendMagicLinkEmail(req, res, landingPath, emailTemplate, redirectUrl) { // Extract the base URL from the request const base = req.protocol + '://' + req.get('host') @@ -93,6 +93,9 @@ async function sendMagicLinkEmail(req, res, landingPath, emailTemplate) { // Add email query parameter for reset password flow magicLink += `&email=${email_id}` } + if (landingPath === PASSWORDLESS_LOGIN_LANDING_PATH) { + magicLink += `&redirect_url=${redirectUrl}` + } // Call the emailLink function to send an email with the magic link using Marketing Cloud const emailLinkResponse = await emailLink(email_id, emailTemplate, magicLink) @@ -147,12 +150,14 @@ const {handler} = runtime.createHandler(options, (app) => { // https://developer.salesforce.com/docs/commerce/commerce-api/guide/slas-passwordless-login.html#receive-the-callback app.post(passwordlessLoginCallback, (req, res) => { const slasCallbackToken = req.headers['x-slas-callback-token'] + const redirectUrl = req.query.redirectUrl validateSlasCallbackToken(slasCallbackToken).then(() => { sendMagicLinkEmail( req, res, PASSWORDLESS_LOGIN_LANDING_PATH, - process.env.MARKETING_CLOUD_PASSWORDLESS_LOGIN_TEMPLATE + process.env.MARKETING_CLOUD_PASSWORDLESS_LOGIN_TEMPLATE, + redirectUrl ) }) }) diff --git a/packages/template-retail-react-app/app/utils/jwt-utils.js b/packages/template-retail-react-app/app/utils/jwt-utils.js index eaf159fa0e..b57cc8eb5e 100644 --- a/packages/template-retail-react-app/app/utils/jwt-utils.js +++ b/packages/template-retail-react-app/app/utils/jwt-utils.js @@ -79,7 +79,7 @@ export async function jwksCaching(req, res, options) { // JWKS rotate every 30 days. For now, cache response for 14 days so that // fetches only need to happen twice a month - res.set('Cache-Control', 'public, max-age=1209600') + res.set('Cache-Control', 'public, max-age=1209600, stale-while-revalidate=86400') return res.json(await response.json()) } catch (error) { From 7051eecab4265db098a3958beb045de84532b8ef Mon Sep 17 00:00:00 2001 From: yunakim714 Date: Wed, 29 Jan 2025 11:49:25 -0500 Subject: [PATCH 12/21] cleanup --- packages/template-retail-react-app/app/pages/login/index.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/template-retail-react-app/app/pages/login/index.test.js b/packages/template-retail-react-app/app/pages/login/index.test.js index d3aeca5a48..8a5ea7e269 100644 --- a/packages/template-retail-react-app/app/pages/login/index.test.js +++ b/packages/template-retail-react-app/app/pages/login/index.test.js @@ -74,6 +74,7 @@ beforeEach(() => { }) afterEach(() => { jest.resetModules() + localStorage.clear() }) describe('Logging in tests', function () { From 4e9945f82c6dea1817597b973223b8d75f8a4afe Mon Sep 17 00:00:00 2001 From: yunakim714 Date: Wed, 29 Jan 2025 11:56:45 -0500 Subject: [PATCH 13/21] encode magic link --- packages/template-retail-react-app/app/pages/login/index.jsx | 2 +- .../app/pages/reset-password/reset-password-landing.jsx | 2 +- packages/template-retail-react-app/app/ssr.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/template-retail-react-app/app/pages/login/index.jsx b/packages/template-retail-react-app/app/pages/login/index.jsx index 86273087db..6195657c8e 100644 --- a/packages/template-retail-react-app/app/pages/login/index.jsx +++ b/packages/template-retail-react-app/app/pages/login/index.jsx @@ -53,7 +53,7 @@ const Login = ({initialView = LOGIN_VIEW}) => { const navigate = useNavigation() const form = useForm() const location = useLocation() - const queryParams = new URLSearchParams(location.search) + const queryParams = decodeURIComponent(new URLSearchParams(location.search)) const {path} = useRouteMatch() const einstein = useEinstein() const {isRegistered, customerType} = useCustomerType() diff --git a/packages/template-retail-react-app/app/pages/reset-password/reset-password-landing.jsx b/packages/template-retail-react-app/app/pages/reset-password/reset-password-landing.jsx index 97b8a35361..1e9b6b3593 100644 --- a/packages/template-retail-react-app/app/pages/reset-password/reset-password-landing.jsx +++ b/packages/template-retail-react-app/app/pages/reset-password/reset-password-landing.jsx @@ -34,7 +34,7 @@ const ResetPasswordLanding = () => { const {formatMessage} = useIntl() const {search} = useLocation() const navigate = useNavigation() - const queryParams = new URLSearchParams(search) + const queryParams = decodeURIComponent(new URLSearchParams(search)) const email = queryParams.get('email') const token = queryParams.get('token') const fields = useUpdatePasswordFields({form}) diff --git a/packages/template-retail-react-app/app/ssr.js b/packages/template-retail-react-app/app/ssr.js index 8639210081..9d32a7e6b0 100644 --- a/packages/template-retail-react-app/app/ssr.js +++ b/packages/template-retail-react-app/app/ssr.js @@ -98,7 +98,7 @@ async function sendMagicLinkEmail(req, res, landingPath, emailTemplate, redirect } // Call the emailLink function to send an email with the magic link using Marketing Cloud - const emailLinkResponse = await emailLink(email_id, emailTemplate, magicLink) + const emailLinkResponse = await emailLink(email_id, emailTemplate, encodeURI(magicLink)) // Send the response res.send(emailLinkResponse) From 5b9decaec1e7a38f0d5b2040b4d2cb51cb99444f Mon Sep 17 00:00:00 2001 From: yunakim714 Date: Wed, 29 Jan 2025 12:23:41 -0500 Subject: [PATCH 14/21] decode specific queryparams --- .../template-retail-react-app/app/pages/login/index.jsx | 6 +++--- .../app/pages/reset-password/reset-password-landing.jsx | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/template-retail-react-app/app/pages/login/index.jsx b/packages/template-retail-react-app/app/pages/login/index.jsx index 6195657c8e..484a54a3be 100644 --- a/packages/template-retail-react-app/app/pages/login/index.jsx +++ b/packages/template-retail-react-app/app/pages/login/index.jsx @@ -53,7 +53,7 @@ const Login = ({initialView = LOGIN_VIEW}) => { const navigate = useNavigation() const form = useForm() const location = useLocation() - const queryParams = decodeURIComponent(new URLSearchParams(location.search)) + const queryParams = new URLSearchParams(location.search) const {path} = useRouteMatch() const einstein = useEinstein() const {isRegistered, customerType} = useCustomerType() @@ -150,8 +150,8 @@ const Login = ({initialView = LOGIN_VIEW}) => { // customer baskets to be loaded to guarantee proper basket merging. useEffect(() => { if (path === PASSWORDLESS_LOGIN_LANDING_PATH && isSuccessCustomerBaskets) { - const token = queryParams.get('token') - const redirect_url = queryParams.get('redirect_url') + const token = decodeURIComponent(queryParams.get('token')) + const redirect_url = decodeURIComponent(queryParams.get('redirect_url')) setRedirectPath(redirect_url) const passwordlessLogin = async () => { diff --git a/packages/template-retail-react-app/app/pages/reset-password/reset-password-landing.jsx b/packages/template-retail-react-app/app/pages/reset-password/reset-password-landing.jsx index 1e9b6b3593..956bb3a1e2 100644 --- a/packages/template-retail-react-app/app/pages/reset-password/reset-password-landing.jsx +++ b/packages/template-retail-react-app/app/pages/reset-password/reset-password-landing.jsx @@ -34,9 +34,9 @@ const ResetPasswordLanding = () => { const {formatMessage} = useIntl() const {search} = useLocation() const navigate = useNavigation() - const queryParams = decodeURIComponent(new URLSearchParams(search)) - const email = queryParams.get('email') - const token = queryParams.get('token') + const queryParams = new URLSearchParams(search) + const email = decodeURIComponent(queryParams.get('email')) + const token = decodeURIComponent(queryParams.get('token')) const fields = useUpdatePasswordFields({form}) const password = form.watch('password') const {resetPassword} = usePasswordReset() From b40e815d6e2641ce70ee33fff65a05105aaeaee4 Mon Sep 17 00:00:00 2001 From: yunakim714 Date: Thu, 30 Jan 2025 12:09:46 -0500 Subject: [PATCH 15/21] fix login page redirect --- .../app/hooks/use-auth-modal.js | 8 ++++---- .../app/pages/checkout/partials/contact-info.jsx | 8 ++++---- .../app/pages/login/index.jsx | 14 ++++++-------- 3 files changed, 14 insertions(+), 16 deletions(-) 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 4086848ab0..77a9ccc569 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 @@ -93,6 +93,9 @@ export const AuthModal = ({ const {getPasswordResetToken} = usePasswordReset() const authorizePasswordlessLogin = useAuthHelper(AuthHelpers.AuthorizePasswordless) const passwordlessConfigCallback = getConfig().app.login?.passwordless?.callbackURI + const callbackURL = isAbsoluteURL(passwordlessConfigCallback) + ? passwordlessConfigCallback + : `${appOrigin}${passwordlessConfigCallback}` const {data: baskets} = useCustomerBaskets( {parameters: {customerId}}, @@ -109,10 +112,7 @@ export const AuthModal = ({ const handlePasswordlessLogin = async (email) => { try { - const callbackURL = isAbsoluteURL(passwordlessConfigCallback) - ? passwordlessConfigCallback - : `${appOrigin}${passwordlessConfigCallback}` - const redirectPath = window.location.pathname + const redirectPath = window.location.pathname + window.location.search await authorizePasswordlessLogin.mutateAsync({ userid: email, callbackURI: `${callbackURL}?redirectUrl=${redirectPath}` diff --git a/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx b/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx index 22f054654b..ef0cdc8fd1 100644 --- a/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx +++ b/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx @@ -82,13 +82,13 @@ const ContactInfo = ({isSocialEnabled = false, isPasswordlessEnabled = false, id const authModal = useAuthModal(authModalView) const [isPasswordlessLoginClicked, setIsPasswordlessLoginClicked] = useState(false) const passwordlessConfigCallback = getConfig().app.login?.passwordless?.callbackURI + const callbackURL = isAbsoluteURL(passwordlessConfigCallback) + ? passwordlessConfigCallback + : `${appOrigin}${passwordlessConfigCallback}` const handlePasswordlessLogin = async (email) => { try { - const callbackURL = isAbsoluteURL(passwordlessConfigCallback) - ? passwordlessConfigCallback - : `${appOrigin}${passwordlessConfigCallback}` - const redirectPath = window.location.pathname + const redirectPath = window.location.pathname + window.location.search await authorizePasswordlessLogin.mutateAsync({ userid: email, callbackURI: `${callbackURL}?redirectUrl=${redirectPath}` diff --git a/packages/template-retail-react-app/app/pages/login/index.jsx b/packages/template-retail-react-app/app/pages/login/index.jsx index 484a54a3be..3555a29d75 100644 --- a/packages/template-retail-react-app/app/pages/login/index.jsx +++ b/packages/template-retail-react-app/app/pages/login/index.jsx @@ -151,8 +151,9 @@ const Login = ({initialView = LOGIN_VIEW}) => { useEffect(() => { if (path === PASSWORDLESS_LOGIN_LANDING_PATH && isSuccessCustomerBaskets) { const token = decodeURIComponent(queryParams.get('token')) - const redirect_url = decodeURIComponent(queryParams.get('redirect_url')) - setRedirectPath(redirect_url) + if (queryParams.get('redirect_url')) { + setRedirectPath(decodeURIComponent(queryParams.get('redirect_url'))) + } const passwordlessLogin = async () => { try { @@ -173,12 +174,9 @@ const Login = ({initialView = LOGIN_VIEW}) => { useEffect(() => { if (isRegistered) { handleMergeBasket() - if (redirectPath) { - navigate(redirectPath) - setRedirectPath('') - } else { - navigate('/account') - } + const redirectTo = redirectPath ? redirectPath : '/account' + navigate(redirectTo) + setRedirectPath('') } }, [isRegistered]) From 3dc0d43b4746842eb8f8ce813c9fcc68b6c6f06a Mon Sep 17 00:00:00 2001 From: yunakim714 Date: Thu, 30 Jan 2025 12:10:20 -0500 Subject: [PATCH 16/21] only add redirect_uri if it is passed --- packages/template-retail-react-app/app/ssr.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/template-retail-react-app/app/ssr.js b/packages/template-retail-react-app/app/ssr.js index 9d32a7e6b0..aa3434ab35 100644 --- a/packages/template-retail-react-app/app/ssr.js +++ b/packages/template-retail-react-app/app/ssr.js @@ -93,7 +93,7 @@ async function sendMagicLinkEmail(req, res, landingPath, emailTemplate, redirect // Add email query parameter for reset password flow magicLink += `&email=${email_id}` } - if (landingPath === PASSWORDLESS_LOGIN_LANDING_PATH) { + if (landingPath === PASSWORDLESS_LOGIN_LANDING_PATH && redirectUrl) { magicLink += `&redirect_url=${redirectUrl}` } From b831c1795a18e8bd6511f16142de425a7806f23c Mon Sep 17 00:00:00 2001 From: yunakim714 Date: Thu, 30 Jan 2025 12:14:50 -0500 Subject: [PATCH 17/21] encode each query param separately --- packages/template-retail-react-app/app/ssr.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/template-retail-react-app/app/ssr.js b/packages/template-retail-react-app/app/ssr.js index aa3434ab35..88e39f6580 100644 --- a/packages/template-retail-react-app/app/ssr.js +++ b/packages/template-retail-react-app/app/ssr.js @@ -88,17 +88,17 @@ async function sendMagicLinkEmail(req, res, landingPath, emailTemplate, redirect const {email_id, token} = req.body // Construct the magic link URL - let magicLink = `${base}${landingPath}?token=${token}` + let magicLink = `${base}${landingPath}?token=${encodeURIComponent(token)}` if (landingPath === RESET_PASSWORD_LANDING_PATH) { // Add email query parameter for reset password flow - magicLink += `&email=${email_id}` + magicLink += `&email=${encodeURIComponent(email_id)}` } if (landingPath === PASSWORDLESS_LOGIN_LANDING_PATH && redirectUrl) { - magicLink += `&redirect_url=${redirectUrl}` + magicLink += `&redirect_url=${encodeURIComponent(redirectUrl)}` } // Call the emailLink function to send an email with the magic link using Marketing Cloud - const emailLinkResponse = await emailLink(email_id, emailTemplate, encodeURI(magicLink)) + const emailLinkResponse = await emailLink(email_id, emailTemplate, magicLink) // Send the response res.send(emailLinkResponse) From 39876bd1d2cb4e5eacfb3da8a2780634a78308e4 Mon Sep 17 00:00:00 2001 From: yunakim714 Date: Thu, 30 Jan 2025 13:32:17 -0500 Subject: [PATCH 18/21] add passwordless landing tests --- .../app/pages/login/index.jsx | 2 +- .../pages/login/passwordless-landing.test.js | 129 ++++++++++++++++++ 2 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 packages/template-retail-react-app/app/pages/login/passwordless-landing.test.js diff --git a/packages/template-retail-react-app/app/pages/login/index.jsx b/packages/template-retail-react-app/app/pages/login/index.jsx index 3555a29d75..b9dfdb9460 100644 --- a/packages/template-retail-react-app/app/pages/login/index.jsx +++ b/packages/template-retail-react-app/app/pages/login/index.jsx @@ -153,7 +153,7 @@ const Login = ({initialView = LOGIN_VIEW}) => { const token = decodeURIComponent(queryParams.get('token')) if (queryParams.get('redirect_url')) { setRedirectPath(decodeURIComponent(queryParams.get('redirect_url'))) - } + } const passwordlessLogin = async () => { try { diff --git a/packages/template-retail-react-app/app/pages/login/passwordless-landing.test.js b/packages/template-retail-react-app/app/pages/login/passwordless-landing.test.js new file mode 100644 index 0000000000..91bc72a820 --- /dev/null +++ b/packages/template-retail-react-app/app/pages/login/passwordless-landing.test.js @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2021, salesforce.com, inc. + * All rights reserved. + * 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 from 'react' +import {screen, waitFor} from '@testing-library/react' +import {rest} from 'msw' +import { + renderWithProviders, + createPathWithDefaults, + guestToken +} from '@salesforce/retail-react-app/app/utils/test-utils' +import Login from '.' +import {BrowserRouter as Router, Route} from 'react-router-dom' +import Account from '@salesforce/retail-react-app/app/pages/account' +import Registration from '@salesforce/retail-react-app/app/pages/registration' +import ResetPassword from '@salesforce/retail-react-app/app/pages/reset-password' +import mockConfig from '@salesforce/retail-react-app/config/mocks/default' +import {mockedRegisteredCustomer} from '@salesforce/retail-react-app/app/mocks/mock-data' +import {AuthHelpers, useCustomerType} from '@salesforce/commerce-sdk-react' +import {useLocation} from 'react-router-dom' + +const mockMergedBasket = { + basketId: 'a10ff320829cb0eef93ca5310a', + currency: 'USD', + customerInfo: { + customerId: 'registeredCustomerId', + email: 'customer@test.com' + } +} + +const mockAuthHelperFunctions = { + [AuthHelpers.LoginPasswordlessUser]: {mutateAsync: jest.fn()} +} + +const MockedComponent = () => { + const match = { + params: {pageName: 'profile'} + } + return ( + + + + + + + + + + + + + ) +} + +jest.mock('react-router', () => { + return { + ...jest.requireActual('react-router'), + useRouteMatch: () => {return {path: '/passwordless-login-landing'}} + } +}) + +jest.mock('react-router-dom', () => { + return { + ...jest.requireActual('react-router-dom'), + useLocation: jest.fn() + } +}) + +jest.mock('@salesforce/commerce-sdk-react', () => { + const originalModule = jest.requireActual('@salesforce/commerce-sdk-react') + return { + ...originalModule, + useAuthHelper: jest + .fn() + .mockImplementation((helperType) => mockAuthHelperFunctions[helperType]), + useCustomerBaskets: () => {return {data: mockMergedBasket, isSuccess: true}}, + useCustomerType: jest.fn(() => {return {isRegistered: false, customerType: 'guest'}}) + } +}) + +// Set up and clean up +beforeEach(() => { + global.server.use( + rest.post('*/customers', (req, res, ctx) => { + return res(ctx.delay(0), ctx.status(200), ctx.json(mockedRegisteredCustomer)) + }), + rest.get('*/customers/:customerId', (req, res, ctx) => { + const {customerId} = req.params + if (customerId === 'customerId') { + return res( + ctx.delay(0), + ctx.status(200), + ctx.json({ + authType: 'guest', + customerId: 'customerid' + }) + ) + } + return res(ctx.delay(0), ctx.status(200), ctx.json(mockedRegisteredCustomer)) + }) + ) +}) +afterEach(() => { + jest.resetAllMocks() +}) + +describe('Passwordless landing tests', function () { + + test('On passwordless landing, make sure token in magic link is passed as param', async () => { + const token = '12345678' + useLocation.mockReturnValue({search: `?token=${token}&redirect_url=/womens-tops`}) + renderWithProviders(, { + wrapperProps: { + siteAlias: 'uk', + locale: {id: 'en-GB'}, + appConfig: mockConfig.app, + isGuest: true + } + }) + expect( + mockAuthHelperFunctions[AuthHelpers.LoginPasswordlessUser].mutateAsync + ).toHaveBeenCalledWith({ + pwdlessLoginToken: token + }) + }) +}) \ No newline at end of file From b7b7d621b04b9aab075f49286c435d290dce7130 Mon Sep 17 00:00:00 2001 From: yunakim714 Date: Fri, 31 Jan 2025 16:43:38 -0500 Subject: [PATCH 19/21] reset redirect path before navigating --- packages/template-retail-react-app/app/pages/login/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/template-retail-react-app/app/pages/login/index.jsx b/packages/template-retail-react-app/app/pages/login/index.jsx index b9dfdb9460..0683c1136e 100644 --- a/packages/template-retail-react-app/app/pages/login/index.jsx +++ b/packages/template-retail-react-app/app/pages/login/index.jsx @@ -175,8 +175,8 @@ const Login = ({initialView = LOGIN_VIEW}) => { if (isRegistered) { handleMergeBasket() const redirectTo = redirectPath ? redirectPath : '/account' - navigate(redirectTo) setRedirectPath('') + navigate(redirectTo) } }, [isRegistered]) From e23bdce81efce8d4367a477e57bb5b3ea400312f Mon Sep 17 00:00:00 2001 From: yunakim714 Date: Mon, 3 Feb 2025 11:16:33 -0500 Subject: [PATCH 20/21] reset redirectpath if no queryparam --- packages/template-retail-react-app/app/pages/login/index.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/template-retail-react-app/app/pages/login/index.jsx b/packages/template-retail-react-app/app/pages/login/index.jsx index 0683c1136e..54b37ee0ef 100644 --- a/packages/template-retail-react-app/app/pages/login/index.jsx +++ b/packages/template-retail-react-app/app/pages/login/index.jsx @@ -153,6 +153,8 @@ const Login = ({initialView = LOGIN_VIEW}) => { const token = decodeURIComponent(queryParams.get('token')) if (queryParams.get('redirect_url')) { setRedirectPath(decodeURIComponent(queryParams.get('redirect_url'))) + } else { + setRedirectPath('') } const passwordlessLogin = async () => { @@ -175,7 +177,6 @@ const Login = ({initialView = LOGIN_VIEW}) => { if (isRegistered) { handleMergeBasket() const redirectTo = redirectPath ? redirectPath : '/account' - setRedirectPath('') navigate(redirectTo) } }, [isRegistered]) From 73f64827ad7d405cafaea65e26bc34490c2d5dfc Mon Sep 17 00:00:00 2001 From: Jinsu Ha <91205717+hajinsuha1@users.noreply.github.com> Date: Mon, 3 Feb 2025 11:57:07 -0500 Subject: [PATCH 21/21] Passwordless redirect unit test (#2235) * add unit tests for passwordless landing --- .../app/pages/login/index.jsx | 2 +- .../pages/login/passwordless-landing.test.js | 83 ++++++++++++------- 2 files changed, 55 insertions(+), 30 deletions(-) diff --git a/packages/template-retail-react-app/app/pages/login/index.jsx b/packages/template-retail-react-app/app/pages/login/index.jsx index 54b37ee0ef..e2c3b4a103 100644 --- a/packages/template-retail-react-app/app/pages/login/index.jsx +++ b/packages/template-retail-react-app/app/pages/login/index.jsx @@ -179,7 +179,7 @@ const Login = ({initialView = LOGIN_VIEW}) => { const redirectTo = redirectPath ? redirectPath : '/account' navigate(redirectTo) } - }, [isRegistered]) + }, [isRegistered, redirectPath]) /**************** Einstein ****************/ useEffect(() => { diff --git a/packages/template-retail-react-app/app/pages/login/passwordless-landing.test.js b/packages/template-retail-react-app/app/pages/login/passwordless-landing.test.js index 91bc72a820..34867fafa3 100644 --- a/packages/template-retail-react-app/app/pages/login/passwordless-landing.test.js +++ b/packages/template-retail-react-app/app/pages/login/passwordless-landing.test.js @@ -5,22 +5,18 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ import React from 'react' -import {screen, waitFor} from '@testing-library/react' +import {waitFor} from '@testing-library/react' import {rest} from 'msw' import { renderWithProviders, - createPathWithDefaults, - guestToken + createPathWithDefaults } from '@salesforce/retail-react-app/app/utils/test-utils' import Login from '.' import {BrowserRouter as Router, Route} from 'react-router-dom' import Account from '@salesforce/retail-react-app/app/pages/account' -import Registration from '@salesforce/retail-react-app/app/pages/registration' -import ResetPassword from '@salesforce/retail-react-app/app/pages/reset-password' import mockConfig from '@salesforce/retail-react-app/config/mocks/default' import {mockedRegisteredCustomer} from '@salesforce/retail-react-app/app/mocks/mock-data' -import {AuthHelpers, useCustomerType} from '@salesforce/commerce-sdk-react' -import {useLocation} from 'react-router-dom' +import {AuthHelpers} from '@salesforce/commerce-sdk-react' const mockMergedBasket = { basketId: 'a10ff320829cb0eef93ca5310a', @@ -42,12 +38,6 @@ const MockedComponent = () => { return ( - - - - - - @@ -58,14 +48,9 @@ const MockedComponent = () => { jest.mock('react-router', () => { return { ...jest.requireActual('react-router'), - useRouteMatch: () => {return {path: '/passwordless-login-landing'}} - } -}) - -jest.mock('react-router-dom', () => { - return { - ...jest.requireActual('react-router-dom'), - useLocation: jest.fn() + useRouteMatch: () => { + return {path: '/passwordless-login-landing'} + } } }) @@ -76,8 +61,12 @@ jest.mock('@salesforce/commerce-sdk-react', () => { useAuthHelper: jest .fn() .mockImplementation((helperType) => mockAuthHelperFunctions[helperType]), - useCustomerBaskets: () => {return {data: mockMergedBasket, isSuccess: true}}, - useCustomerType: jest.fn(() => {return {isRegistered: false, customerType: 'guest'}}) + useCustomerBaskets: () => { + return {data: mockMergedBasket, isSuccess: true} + }, + useCustomerType: jest.fn(() => { + return {isRegistered: true, customerType: 'guest'} + }) } }) @@ -104,26 +93,62 @@ beforeEach(() => { ) }) afterEach(() => { - jest.resetAllMocks() + jest.resetModules() }) describe('Passwordless landing tests', function () { + test('redirects to account page when redirect url is not passed', async () => { + const token = '12345678' + window.history.pushState( + {}, + 'Passwordless Login Landing', + createPathWithDefaults(`/passwordless-login-landing?token=${token}`) + ) + renderWithProviders(, { + wrapperProps: { + siteAlias: 'uk', + locale: {id: 'en-GB'}, + appConfig: mockConfig.app + } + }) - test('On passwordless landing, make sure token in magic link is passed as param', async () => { + expect( + mockAuthHelperFunctions[AuthHelpers.LoginPasswordlessUser].mutateAsync + ).toHaveBeenCalledWith({ + pwdlessLoginToken: token + }) + + await waitFor(() => { + expect(window.location.pathname).toBe('/uk/en-GB/account') + }) + }) + + test('redirects to redirectUrl when passed as param', async () => { const token = '12345678' - useLocation.mockReturnValue({search: `?token=${token}&redirect_url=/womens-tops`}) + const redirectUrl = '/womens-tops' + window.history.pushState( + {}, + 'Passwordless Login Landing', + createPathWithDefaults( + `/passwordless-login-landing?token=${token}&redirect_url=${redirectUrl}` + ) + ) renderWithProviders(, { wrapperProps: { siteAlias: 'uk', locale: {id: 'en-GB'}, - appConfig: mockConfig.app, - isGuest: true + appConfig: mockConfig.app } }) + expect( mockAuthHelperFunctions[AuthHelpers.LoginPasswordlessUser].mutateAsync ).toHaveBeenCalledWith({ pwdlessLoginToken: token }) + + await waitFor(() => { + expect(window.location.pathname).toBe('/uk/en-GB/womens-tops') + }) }) -}) \ No newline at end of file +})