Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature passwordless social login #2079

Merged
merged 74 commits into from
Feb 12, 2025
Merged
Changes from 1 commit
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
fee6914
Add social login
bredmond-sf Sep 25, 2024
0a93e1b
Merge branch 'develop' of github.com:SalesforceCommerceCloud/pwa-kit …
bredmond-sf Sep 25, 2024
2e4bede
Remove new default.js key
bredmond-sf Sep 25, 2024
a383f52
Tweak test
bredmond-sf Sep 26, 2024
114363e
Merge branch 'feature-passwordless-social-login' of github.com:Salesf…
bredmond-sf Sep 26, 2024
76b9cd0
Fix bordercolor
bredmond-sf Sep 26, 2024
da6ac02
Tweak icon
bredmond-sf Sep 26, 2024
f44e927
Merge pull request #2027 from SalesforceCommerceCloud/W-16544327-soci…
bredmond-sf Sep 26, 2024
cf49a18
Merge branch 'feature-passwordless-social-login' of github.com:Salesf…
bredmond-sf Sep 27, 2024
89f9a30
Merge branch 'develop' of github.com:SalesforceCommerceCloud/pwa-kit …
bredmond-sf Oct 1, 2024
82be946
Merge branch 'develop' of github.com:SalesforceCommerceCloud/pwa-kit …
bredmond-sf Oct 2, 2024
7a2dedd
Merge branch 'develop' of github.com:SalesforceCommerceCloud/pwa-kit …
bredmond-sf Oct 3, 2024
7f01ff7
Add wrappers for social login helpers in `commerce-sdk-react` (#2049)
yunakim714 Oct 9, 2024
59a1ddc
Merge branch 'develop' into feature-passwordless-social-login
hajinsuha1 Oct 15, 2024
34c450c
Merge branch 'develop' into feature-passwordless-social-login
hajinsuha1 Oct 15, 2024
27ec267
Merge branch 'develop' into feature-passwordless-social-login
hajinsuha1 Oct 22, 2024
f116aca
Add wrappers for passwordless login helpers in `commerce-sdk-react` (…
yunakim714 Oct 24, 2024
8a3798e
Social Login Redirect Page (#2068)
yunakim714 Oct 24, 2024
415b673
Merge branch 'develop' into feature-passwordless-social-login
hajinsuha1 Oct 29, 2024
7d80a01
Merge branch 'develop' into feature-passwordless-social-login
hajinsuha1 Oct 31, 2024
ef0fbe2
@W-16617186 Passwordless Login UI Buttons (#2032)
hajinsuha1 Oct 31, 2024
372abcf
@W-16795956 - Implement "Check Email" page (#2110)
yunakim714 Nov 11, 2024
8bc9f69
@W-16909794 - Add passwordless/social login UI buttons to Checkout pa…
yunakim714 Nov 13, 2024
c02cf88
Merge branch 'develop' into feature-passwordless-social-login
hajinsuha1 Nov 21, 2024
888776d
Merge branch 'develop' into feature-passwordless-social-login
yunakim714 Dec 4, 2024
25b3661
@W-16889880 - Complete Social Login Integration: Connect Backend API …
yunakim714 Dec 4, 2024
efe9c14
@W-17263304 Passwordless login with marketing cloud (#2131)
bredmond-sf Dec 30, 2024
1c687fd
@W-17458080 - Use env vars to configure redirect and callback uris fo…
yunakim714 Jan 14, 2025
4a60961
add back button
jeremy-jung1 Jan 16, 2025
306107e
Merge branch 'develop' into feature-passwordless-social-login
jeremy-jung1 Jan 16, 2025
fd37b12
changelog
jeremy-jung1 Jan 16, 2025
3ec8af6
linting
jeremy-jung1 Jan 16, 2025
868bfb3
Add test
jeremy-jung1 Jan 17, 2025
0d5e884
translations
jeremy-jung1 Jan 17, 2025
9c8006a
Merge branch 'develop' into feature-passwordless-social-login
jeremy-jung1 Jan 17, 2025
76664e1
Change text
jeremy-jung1 Jan 17, 2025
a2133ec
Update index.test.js
jeremy-jung1 Jan 17, 2025
9aecbd6
Add condition
jeremy-jung1 Jan 17, 2025
5e2b100
linting
jeremy-jung1 Jan 17, 2025
751e58c
Update spacing
jeremy-jung1 Jan 17, 2025
0c08ccf
Remove changelog for later and spacing
jeremy-jung1 Jan 21, 2025
37e78d1
Account for spacing above and below stack
jeremy-jung1 Jan 21, 2025
c83473d
Merge pull request #2208 from SalesforceCommerceCloud/W-17526051-back…
jeremy-jung1 Jan 21, 2025
fb61a7b
@W-17458039 - Handle error states for social/passwordless login and r…
yunakim714 Jan 22, 2025
1b9aeb5
@W-17271709 Passwordless Login in Checkout (#2178)
hajinsuha1 Jan 22, 2025
b70a97c
@W-17386338 - Secure SSR Endpoints by Verifying SLAS Callback Request…
yunakim714 Jan 23, 2025
86964df
Merge branch 'develop' into feature-passwordless-social-login
yunakim714 Jan 28, 2025
a17d1b1
Merge branch 'develop' into feature-passwordless-social-login
yunakim714 Jan 30, 2025
960d851
@W-17550783 - [Passwordless login] Redirect customer to page prior to…
yunakim714 Feb 3, 2025
d84a358
Merge branch 'develop' into feature-passwordless-social-login
yunakim714 Feb 5, 2025
5a47508
cleanup
yunakim714 Feb 5, 2025
18cd6eb
Merge branch 'feature-passwordless-social-login' of github.com:Salesf…
yunakim714 Feb 5, 2025
1cf11d6
change jose registry to npmjs
yunakim714 Feb 5, 2025
67b6fb5
update changelog
yunakim714 Feb 5, 2025
f025826
update pwa-kit-create-app changelog'
yunakim714 Feb 5, 2025
48da858
fix unit tests
yunakim714 Feb 6, 2025
9b2c5bb
resolve absolute paths issue with babel
yunakim714 Feb 6, 2025
65572b7
fix retail app unit tests
yunakim714 Feb 6, 2025
2d2f5f4
resolve flaky checkout test
yunakim714 Feb 6, 2025
bf0cb44
add 1s timeout
yunakim714 Feb 6, 2025
c41787e
apply babel config to all files in template retail app
yunakim714 Feb 6, 2025
908c20f
cleanup
yunakim714 Feb 6, 2025
445d4ef
lint:
yunakim714 Feb 6, 2025
458f911
fix social login not using private client and refactor applySLASPriva…
hajinsuha1 Feb 6, 2025
ccc8a1f
[Social Login] Remove initial fetch to improve performance (#2243)
yunakim714 Feb 10, 2025
09ef3df
bump commerce sdk isomorphic version
yunakim714 Feb 10, 2025
bf22e96
Merge branch 'develop' into feature-passwordless-social-login
vmarta Feb 10, 2025
72a160e
Fix linting error
vmarta Feb 10, 2025
33b7c88
Fix CI test for Checkout Confirmation
vmarta Feb 11, 2025
e173869
Bump up the bundle size limit
vmarta Feb 11, 2025
f5db134
Update Social Login E2E test (#2248)
yunakim714 Feb 11, 2025
83295ac
[Social Login] Update default value of `applySLASPrivateClientToEndpo…
vmarta Feb 12, 2025
32f9f69
Update CHANGELOG.md
vmarta Feb 12, 2025
4e15295
Merge branch 'develop' into feature-passwordless-social-login
vmarta Feb 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
@W-16795956 - Implement "Check Email" page (#2110)
Implement a Check Your Email page that will be used in the passwordless login flow

---------

Signed-off-by: Yuna Kim <84923642+yunakim714@users.noreply.github.com>
  • Loading branch information
yunakim714 authored Nov 11, 2024
commit 372abcfc7960262f4e44e9287c3a08d4982c1e9f
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* 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 PropTypes from 'prop-types'
import {FormattedMessage} from 'react-intl'
import {Button, Stack, Text} from '@salesforce/retail-react-app/app/components/shared/ui'
import {BrandLogo} from '@salesforce/retail-react-app/app/components/icons'

const PasswordlessEmailConfirmation = ({form, submitForm, email = ''}) => {
return (
<form
onSubmit={form.handleSubmit(submitForm)}
data-testid="sf-form-resend-passwordless-email"
>
<Stack justify="center" align="center" spacing={6}>
<BrandLogo width="60px" height="auto" />
<Text align="center" fontSize="xl" fontWeight="semibold">
<FormattedMessage
defaultMessage="Check Your Email"
id="auth_modal.check_email.title.check_your_email"
/>
</Text>
<Text align="center" fontSize="md">
<FormattedMessage
defaultMessage="We just sent a login link to <b>{email}</b>"
id="auth_modal.check_email.description.just_sent"
values={{
email: email,
b: (chunks) => <b>{chunks}</b>
}}
/>
</Text>
<Stack spacing={6} pt={4}>
<Text align="center" fontSize="sm">
<FormattedMessage
defaultMessage="The link may take a few minutes to arrive, check your spam folder if you're having trouble finding it"
id="auth_modal.check_email.description.check_spam_folder"
/>
</Text>

<Button type="submit">
<FormattedMessage
defaultMessage="Resend Link"
id="auth_modal.check_email.button.resend_link"
/>
</Button>
</Stack>
</Stack>
</form>
)
}

PasswordlessEmailConfirmation.propTypes = {
form: PropTypes.object,
submitForm: PropTypes.func,
email: PropTypes.string
}

export default PasswordlessEmailConfirmation
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright (c) 2024, Salesforce, 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} from '@testing-library/react'
import {renderWithProviders} from '@salesforce/retail-react-app/app/utils/test-utils'
import PasswordlessEmailConfirmation from '@salesforce/retail-react-app/app/components/email-confirmation/index'
import {useForm} from 'react-hook-form'

const WrapperComponent = ({...props}) => {
const form = useForm()
return <PasswordlessEmailConfirmation form={form} {...props} />
}

test('renders PasswordlessEmailConfirmation component with passed email', () => {
const email = 'test@salesforce.com'
renderWithProviders(<WrapperComponent email={email} />)
expect(screen.getByText(email)).toBeInTheDocument()
})
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@ import {noop} from '@salesforce/retail-react-app/app/utils/utils'
const LoginForm = ({
submitForm,
handleForgotPasswordClick,
handlePasswordlessLoginClick,
clickCreateAccount = noop,
form,
isPasswordlessEnabled = false,
@@ -52,6 +53,7 @@ const LoginForm = ({
<PasswordlessLogin
form={form}
handleForgotPasswordClick={handleForgotPasswordClick}
handlePasswordlessLoginClick={handlePasswordlessLoginClick}
isSocialEnabled={isSocialEnabled}
idps={idps}
/>
@@ -88,6 +90,7 @@ LoginForm.propTypes = {
submitForm: PropTypes.func,
handleForgotPasswordClick: PropTypes.func,
clickCreateAccount: PropTypes.func,
handlePasswordlessLoginClick: PropTypes.func,
form: PropTypes.object,
isPasswordlessEnabled: PropTypes.bool,
isSocialEnabled: PropTypes.bool,
Original file line number Diff line number Diff line change
@@ -10,12 +10,13 @@ import PropTypes from 'prop-types'
import {FormattedMessage} from 'react-intl'
import {Button, Divider, Stack, Text} from '@salesforce/retail-react-app/app/components/shared/ui'
import LoginFields from '@salesforce/retail-react-app/app/components/forms/login-fields'
import StandardLogin from '../standard-login/index'
import StandardLogin from '@salesforce/retail-react-app/app/components/standard-login'
import SocialLogin from '@salesforce/retail-react-app/app/components/social-login'

const PasswordlessLogin = ({
form,
handleForgotPasswordClick,
handlePasswordlessLoginClick,
isSocialEnabled = false,
idps = []
}) => {
@@ -44,9 +45,7 @@ const PasswordlessLogin = ({
/>
<Button
type="submit"
onClick={() => {
form.clearErrors('global')
}}
onClick={handlePasswordlessLoginClick}
isLoading={form.formState.isSubmitting}
>
<FormattedMessage
@@ -93,6 +92,7 @@ const PasswordlessLogin = ({
PasswordlessLogin.propTypes = {
form: PropTypes.object,
handleForgotPasswordClick: PropTypes.func,
handlePasswordlessLoginClick: PropTypes.func,
isSocialEnabled: PropTypes.bool,
idps: PropTypes.arrayOf[PropTypes.string]
}
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ import PropTypes from 'prop-types'
import {FormattedMessage} from 'react-intl'
import {Button, Divider, Stack, Text} from '@salesforce/retail-react-app/app/components/shared/ui'
import LoginFields from '@salesforce/retail-react-app/app/components/forms/login-fields'
import SocialLogin from '../social-login/index'
import SocialLogin from '@salesforce/retail-react-app/app/components/social-login'

const StandardLogin = ({
form,
81 changes: 53 additions & 28 deletions packages/template-retail-react-app/app/hooks/use-auth-modal.js
Original file line number Diff line number Diff line change
@@ -35,6 +35,7 @@ import {BrandLogo} from '@salesforce/retail-react-app/app/components/icons'
import LoginForm from '@salesforce/retail-react-app/app/components/login'
import ResetPasswordForm from '@salesforce/retail-react-app/app/components/reset-password'
import RegisterForm from '@salesforce/retail-react-app/app/components/register'
import PasswordlessEmailConfirmation from '@salesforce/retail-react-app/app/components/email-confirmation/index'
import {noop} from '@salesforce/retail-react-app/app/utils/utils'
import {API_ERROR_MESSAGE} from '@salesforce/retail-react-app/app/constants'
import useNavigation from '@salesforce/retail-react-app/app/hooks/use-navigation'
@@ -44,6 +45,7 @@ import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config'
const LOGIN_VIEW = 'login'
const REGISTER_VIEW = 'register'
const PASSWORD_VIEW = 'password'
const EMAIL_VIEW = 'email'

const LOGIN_ERROR = defineMessage({
defaultMessage: "Something's not right with your email or password. Try again.",
@@ -79,6 +81,8 @@ export const AuthModal = ({
const toast = useToast()
const login = useAuthHelper(AuthHelpers.LoginRegisteredUserB2C)
const register = useAuthHelper(AuthHelpers.Register)
const [passwordlessLoginEmail, setPasswordlessLoginEmail] = useState('')
const [loginType, setLoginType] = useState('password')

const getResetPasswordToken = useShopperCustomersMutation(
ShopperCustomersMutations.GetResetPasswordToken
@@ -99,34 +103,42 @@ export const AuthModal = ({

return {
login: async (data) => {
try {
await login.mutateAsync({
username: data.email,
password: data.password
})
const hasBasketItem = baskets?.baskets?.[0]?.productItems?.length > 0
// we only want to merge basket when the user is logged in as a recurring user
// only recurring users trigger the login mutation, new user triggers register mutation
// this logic needs to stay in this block because this is the only place that tells if a user is a recurring user
// if you change logic here, also change it in login page
const shouldMergeBasket = hasBasketItem && prevAuthType === 'guest'
if (shouldMergeBasket) {
mergeBasket.mutate({
headers: {
// This is not required since the request has no body
// but CommerceAPI throws a '419 - Unsupported Media Type' error if this header is removed.
'Content-Type': 'application/json'
},
parameters: {
createDestinationBasket: true
}
if (loginType === 'password') {
try {
await login.mutateAsync({
username: data.email,
password: data.password
})
const hasBasketItem = baskets?.baskets?.[0]?.productItems?.length > 0
// we only want to merge basket when the user is logged in as a recurring user
// only recurring users trigger the login mutation, new user triggers register mutation
// this logic needs to stay in this block because this is the only place that tells if a user is a recurring user
// if you change logic here, also change it in login page
const shouldMergeBasket = hasBasketItem && prevAuthType === 'guest'
if (shouldMergeBasket) {
mergeBasket.mutate({
headers: {
// This is not required since the request has no body
// but CommerceAPI throws a '419 - Unsupported Media Type' error if this header is removed.
'Content-Type': 'application/json'
},
parameters: {
createDestinationBasket: true
}
})
}
} catch (error) {
const message = /Unauthorized/i.test(error.message)
? formatMessage(LOGIN_ERROR)
: formatMessage(API_ERROR_MESSAGE)
form.setError('global', {type: 'manual', message})
}
} catch (error) {
const message = /Unauthorized/i.test(error.message)
? formatMessage(LOGIN_ERROR)
: formatMessage(API_ERROR_MESSAGE)
form.setError('global', {type: 'manual', message})
} else if (loginType === 'passwordless') {
setCurrentView(EMAIL_VIEW)
setPasswordlessLoginEmail(data.email)
// Handle passwordless login logic here
} else if (loginType === 'social') {
// Handle social login logic here
}
},
register: async (data) => {
@@ -162,6 +174,9 @@ export const AuthModal = ({
message: formatMessage(API_ERROR_MESSAGE)
})
}
},
email: async (data) => {
// Handle resend passwordless email logic here
}
}[currentView](data)
}
@@ -239,6 +254,7 @@ export const AuthModal = ({
const onBackToSignInClick = () =>
initialView === PASSWORD_VIEW ? onClose() : setCurrentView(LOGIN_VIEW)

// TODO: Remove this to a separate component when fixing password reset flow
const PasswordResetSuccess = () => (
<Stack justify="center" align="center" spacing={6}>
<BrandLogo width="60px" height="auto" />
@@ -270,6 +286,7 @@ export const AuthModal = ({
</Stack>
</Stack>
)

return (
<Modal
size="sm"
@@ -294,6 +311,7 @@ export const AuthModal = ({
form={form}
submitForm={submitForm}
clickCreateAccount={() => setCurrentView(REGISTER_VIEW)}
handlePasswordlessLoginClick={() => setLoginType('passwordless')}
handleForgotPasswordClick={() => setCurrentView(PASSWORD_VIEW)}
isPasswordlessEnabled={isPasswordlessEnabled}
isSocialEnabled={isSocialEnabled}
@@ -317,14 +335,21 @@ export const AuthModal = ({
{form.formState.isSubmitSuccessful && currentView === PASSWORD_VIEW && (
<PasswordResetSuccess />
)}
{form.formState.isSubmitSuccessful && currentView === EMAIL_VIEW && (
<PasswordlessEmailConfirmation
form={form}
submitForm={submitForm}
email={passwordlessLoginEmail}
/>
)}
</ModalBody>
</ModalContent>
</Modal>
)
}

AuthModal.propTypes = {
initialView: PropTypes.oneOf([LOGIN_VIEW, REGISTER_VIEW, PASSWORD_VIEW]),
initialView: PropTypes.oneOf([LOGIN_VIEW, REGISTER_VIEW, PASSWORD_VIEW, EMAIL_VIEW]),
isOpen: PropTypes.bool.isRequired,
onOpen: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
@@ -337,7 +362,7 @@ AuthModal.propTypes = {

/**
*
* @param {('register'|'login'|'password')} initialView - the initial view for the modal
* @param {('register'|'login'|'password'|'email')} initialView - the initial view for the modal
* @returns {Object} - Object props to be spread on to the AuthModal component
*/
export const useAuthModal = (initialView = LOGIN_VIEW) => {
Original file line number Diff line number Diff line change
@@ -121,6 +121,20 @@ test('Renders login modal by default', async () => {
})
})

test('Renders check email modal on email mode', async () => {
const user = userEvent.setup()

renderWithProviders(<MockedComponent initialView="email" />)

// open the modal
const trigger = screen.getByText(/open modal/i)
await user.click(trigger)

await waitFor(() => {
expect(screen.getByText(/check your email/i)).toBeInTheDocument()
})
})

// TODO: Fix flaky/broken test
// eslint-disable-next-line jest/no-disabled-tests
test.skip('Renders error when given incorrect log in credentials', async () => {
Loading
Loading