Skip to content

Commit

Permalink
[PWA-1512]: Check if email is associated with account in guest checko…
Browse files Browse the repository at this point in the history
…ut (magento#3529)

* PWA-1512 Add dismissAction option to toast

* PWA-1521 Add isEmailAvailable query to operations

* PWA-1512 Add email verification and toast features

* PWA-1512 Add translations

* Hide errors from getEmailAvailable query

* Use "key" prop instead of conditional rendering

* Add unit tests

* Update snapshots

* Remove unnecessary snapshot test

* Add Cypress coverage

* Rollback cypress config changes

* Fix failing cypress tests

* Update propTypes

* Add onpaste handler

* Remove console.log

* Fix unit tests

Co-authored-by: Eric Oeur <[email protected]>
  • Loading branch information
pedrochiossi and ericeoeur committed Dec 8, 2021
1 parent 3c2d50d commit 06524f9
Show file tree
Hide file tree
Showing 30 changed files with 639 additions and 31 deletions.
5 changes: 5 additions & 0 deletions packages/peregrine/lib/Toasts/useToasts.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ export const useToasts = () => {
* This property is optional when creating toasts.
* @property {String} [actionText] Text to display as a call to action.
* This property is optional when creating toasts.
* @property {Bool} [hasDismissAction] Indicates whether the toast should have a
* dismiss action with the same behavior as the dismiss icon.
* This property is optional when creating toasts.
* @property {String} [dismissActionText] Text to display as a call to dissmisAction.
* This property is optional when creating toasts.
* @property {Function} [onAction] Callback invoked when a user clicks the action
* text.
* This property is optional when creating toasts.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ Object {
},
"handleCancel": [Function],
"handleSubmit": [Function],
"handleToastAction": [Function],
"handleValidateEmail": [Function],
"initialValues": Object {
"city": "Manhattan",
"country": "US",
Expand All @@ -74,5 +76,6 @@ Object {
},
"isSaving": false,
"isUpdate": true,
"showSignInToast": false,
}
`;
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import React from 'react';
import { act } from 'react-test-renderer';
import { useMutation } from '@apollo/client';
import { useLazyQuery, useMutation } from '@apollo/client';

import createTestInstance from '../../../../../util/createTestInstance';
import { useGuestForm } from '../useGuestForm';

jest.mock('@apollo/client', () => ({
useMutation: jest
.fn()
.mockReturnValue([jest.fn(), { called: false, loading: false }])
.mockReturnValue([jest.fn(), { called: false, loading: false }]),
useLazyQuery: jest
.fn()
.mockReturnValue([
jest.fn(),
{ data: null, called: false, loading: false, error: null }
])
}));

jest.mock('../../../../../context/cart', () => {
Expand Down Expand Up @@ -221,3 +227,71 @@ test('should call onSuccess on mutation success', () => {

expect(onSuccess).toHaveBeenCalled();
});

test('handleValidateEmail calls isEmailAvailable query if email is valid', () => {
const validadeEmailQuery = jest.fn();
useLazyQuery.mockReturnValueOnce([validadeEmailQuery, { data: null }]);

const tree = createTestInstance(
<Component mutations={{}} shippingData={shippingData} />
);

const { root } = tree;
const { talonProps } = root.findByType('i').props;
const { handleValidateEmail } = talonProps;

handleValidateEmail();
expect(validadeEmailQuery).not.toHaveBeenCalled();
handleValidateEmail('testmail.com');
expect(validadeEmailQuery).not.toHaveBeenCalled();
handleValidateEmail('[email protected]');
expect(validadeEmailQuery).toHaveBeenCalled();
});

test('sets signIn toast visible when email is not available', () => {
const validadeEmailQuery = jest.fn();
useLazyQuery.mockReturnValueOnce([
validadeEmailQuery,
{
data: {
isEmailAvailable: {
is_email_available: false
}
}
}
]);

const tree = createTestInstance(
<Component mutations={{}} shippingData={shippingData} />
);

const { root } = tree;
const { talonProps } = root.findByType('i').props;
const { showSignInToast } = talonProps;

expect(showSignInToast).toBe(true);
});

test('handleToastAction fires up defined toast callback action', () => {
const removeToast = jest.fn();
const setGuestSignInUsername = jest.fn();
const toggleSignInContent = jest.fn();

const tree = createTestInstance(
<Component
mutations={{}}
shippingData={shippingData}
toggleSignInContent={toggleSignInContent}
setGuestSignInUsername={setGuestSignInUsername}
/>
);

const { root } = tree;
const { talonProps } = root.findByType('i').props;
const { handleToastAction } = talonProps;

handleToastAction(removeToast, '[email protected]');
expect(setGuestSignInUsername).toHaveBeenCalledWith('[email protected]');
expect(toggleSignInContent).toHaveBeenCalled();
expect(removeToast).toHaveBeenCalled();
});
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ export const SET_GUEST_SHIPPING_MUTATION = gql`
${AvailablePaymentMethodsFragment}
`;

export const GET_EMAIL_AVAILABLE_QUERY = gql`
query IsEmailAvailable($email: String!) {
isEmailAvailable(email: $email) {
is_email_available
}
}
`;

export default {
setGuestShippingMutation: SET_GUEST_SHIPPING_MUTATION
setGuestShippingMutation: SET_GUEST_SHIPPING_MUTATION,
getEmailAvailableQuery: GET_EMAIL_AVAILABLE_QUERY
};
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import { useCallback, useMemo } from 'react';
import { useMutation } from '@apollo/client';
import { useCallback, useMemo, useState, useEffect } from 'react';
import { useMutation, useLazyQuery } from '@apollo/client';
import DEFAULT_OPERATIONS from './guestForm.gql';
import mergeOperations from '@magento/peregrine/lib/util/shallowMerge';

import { useCartContext } from '../../../../context/cart';

export const useGuestForm = props => {
const { afterSubmit, onCancel, onSuccess, shippingData } = props;
const {
afterSubmit,
onCancel,
onSuccess,
shippingData,
toggleSignInContent,
setGuestSignInUsername
} = props;
const [showSignInToast, setShowSignInToast] = useState(false);

const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations);
const { setGuestShippingMutation } = operations;
const { setGuestShippingMutation, getEmailAvailableQuery } = operations;

const [{ cartId }] = useCartContext();

Expand All @@ -22,6 +30,10 @@ export const useGuestForm = props => {
}
);

const [runQuery, { data }] = useLazyQuery(getEmailAvailableQuery, {
fetchPolicy: 'cache-and-network'
});

const { country } = shippingData;
const { code: countryCode } = country;

Expand Down Expand Up @@ -66,17 +78,45 @@ export const useGuestForm = props => {
onCancel();
}, [onCancel]);

const handleValidateEmail = useCallback(
email => {
setShowSignInToast(false);
if (email && email.includes('@')) {
runQuery({ variables: { email } });
}
},
[runQuery]
);

const handleToastAction = useCallback(
(removeToast, email) => {
setGuestSignInUsername(email);
toggleSignInContent();
removeToast();
},
[setGuestSignInUsername, toggleSignInContent]
);

const errors = useMemo(
() => new Map([['setGuestShippingMutation', error]]),
[error]
);

useEffect(() => {
if (data) {
setShowSignInToast(!data.isEmailAvailable.is_email_available);
}
}, [data]);

return {
errors,
handleCancel,
handleSubmit,
handleValidateEmail,
handleToastAction,
initialValues,
isSaving: loading,
isUpdate
isUpdate,
showSignInToast
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Object {
"name": "gooseton",
},
"error": undefined,
"guestSignInUsername": "",
"handlePlaceOrder": [Function],
"handleReviewOrder": [Function],
"hasError": false,
Expand All @@ -27,6 +28,7 @@ Object {
"scrollShippingInformationIntoView": [Function],
"scrollShippingMethodIntoView": [Function],
"setCheckoutStep": [Function],
"setGuestSignInUsername": [Function],
"setIsUpdating": [Function],
"setPaymentInformationDone": [Function],
"setShippingInformationDone": [Function],
Expand Down
4 changes: 4 additions & 0 deletions packages/peregrine/lib/talons/CheckoutPage/useCheckoutPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ export const useCheckoutPage = (props = {}) => {
const [checkoutStep, setCheckoutStep] = useState(
CHECKOUT_STEP.SHIPPING_ADDRESS
);
const [guestSignInUsername, setGuestSignInUsername] = useState('');

const [{ isSignedIn }] = useUserContext();
const [{ cartId }, { createCart, removeCart }] = useCartContext();

Expand Down Expand Up @@ -284,6 +286,7 @@ export const useCheckoutPage = (props = {}) => {
checkoutStep,
customer,
error: checkoutError,
guestSignInUsername,
handlePlaceOrder,
hasError: !!checkoutError,
isCartEmpty: !(checkoutData && checkoutData.cart.total_quantity),
Expand All @@ -297,6 +300,7 @@ export const useCheckoutPage = (props = {}) => {
null,
placeOrderLoading,
setCheckoutStep,
setGuestSignInUsername,
setIsUpdating,
setShippingInformationDone,
setShippingMethodDone,
Expand Down
5 changes: 4 additions & 1 deletion packages/venia-ui/i18n/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@
"checkoutPage.step4": "Saved Credit Card Information Successfully",
"checkoutPage.stockStatusMessage": "An item in your cart is currently out-of-stock and must be removed in order to Checkout. Please return to your cart to remove the item.",
"checkoutPage.subscribe": "Subscribe to news and updates",
"checkoutPage.suggestSignInConfirmMessage": "Yes, sign in",
"checkoutPage.suggestSignInDeclineMessage": "No, thanks",
"checkoutPage.suggestSignInMessage'": "The email you provided is associated with an existing Venia account. Would you like to sign into this account?",
"checkoutPage.thankYou": "Thank you for your order!",
"checkoutPage.titleCheckout": "Checkout",
"checkoutPage.titleReceipt": "Receipt",
Expand Down Expand Up @@ -455,4 +458,4 @@
"wishlist.itemCountOpen": "Showing {currentCount} of {count} items in this list",
"wishlist.itemCountClosed": "You have {count} {count, plural, one {item} other {items}} in this list",
"wishlist.loadMore": "Load More"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import SignIn from '@magento/venia-ui/lib/components/SignIn';
import defaultClasses from './guestSignIn.module.css';

const GuestSignIn = props => {
const { isActive, toggleActiveContent } = props;
const { isActive, toggleActiveContent, initialValues } = props;

const talonProps = useGuestSignIn({ toggleActiveContent });
const {
Expand All @@ -32,6 +32,7 @@ const GuestSignIn = props => {
classes={{ modal_active: undefined, root: classes.signInRoot }}
showCreateAccount={toggleCreateAccountView}
showForgotPassword={toggleForgotPasswordView}
initialValues={initialValues}
/>
);
} else if (view === 'FORGOT_PASSWORD') {
Expand Down Expand Up @@ -85,5 +86,8 @@ GuestSignIn.propTypes = {
createAccountRoot: string
}),
isActive: bool.isRequired,
toggleActiveContent: func.isRequired
toggleActiveContent: func.isRequired,
initialValues: shape({
email: string.isRequired
})
};
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@ Array [
className="input"
>
<input
autoComplete="off"
className="input"
id="email"
name="email"
onBlur={[Function]}
onChange={[Function]}
onPaste={[Function]}
value=""
/>
</span>
Expand Down Expand Up @@ -475,11 +477,13 @@ Array [
className="input"
>
<input
autoComplete="off"
className="input"
id="email"
name="email"
onBlur={[Function]}
onChange={[Function]}
onPaste={[Function]}
value=""
/>
</span>
Expand Down Expand Up @@ -910,11 +914,13 @@ Array [
className="input"
>
<input
autoComplete="off"
className="input"
id="email"
name="email"
onBlur={[Function]}
onChange={[Function]}
onPaste={[Function]}
value="[email protected]"
/>
</span>
Expand Down Expand Up @@ -1363,11 +1369,13 @@ Array [
className="input"
>
<input
autoComplete="off"
className="input"
id="email"
name="email"
onBlur={[Function]}
onChange={[Function]}
onPaste={[Function]}
value="[email protected]"
/>
</span>
Expand Down
Loading

0 comments on commit 06524f9

Please sign in to comment.