diff --git a/components/AccountDropdown/AccountDropdown.js b/components/AccountDropdown/AccountDropdown.js index 780ae67d9f..d56a59daba 100644 --- a/components/AccountDropdown/AccountDropdown.js +++ b/components/AccountDropdown/AccountDropdown.js @@ -10,12 +10,8 @@ import Popover from "@material-ui/core/Popover"; import useViewer from "hooks/viewer/useViewer"; import ViewerInfo from "@reactioncommerce/components/ViewerInfo/v1"; import Link from "components/Link"; -import Modal from "@material-ui/core/Modal"; -import Login from "../Entry/Login"; -import SignUp from "../Entry/SignUp"; -import ChangePassword from "../Entry/ChangePassword"; -import ForgotPassword from "../Entry/ForgotPassword"; -import ResetPassword from "../Entry/ResetPassword"; +import useStores from "hooks/useStores"; +import EntryModal from "../Entry/EntryModal"; import getAccountsHandler from "../../lib/accountsServer.js"; const useStyles = makeStyles((theme) => ({ @@ -25,52 +21,30 @@ const useStyles = makeStyles((theme) => ({ }, marginBottom: { marginBottom: theme.spacing(2) - }, - paper: { - position: "absolute", - width: 380, - backgroundColor: theme.palette.background.paper, - border: "2px solid #000", - boxShadow: theme.shadows[5], - padding: theme.spacing(2, 4, 3), - top: "50%", - left: "50%", - transform: "translate(-50%, -50%)", - display: "flex", - alignItems: "center", - justifyContent: "center" } })); const AccountDropdown = () => { const router = useRouter(); + const { uiStore } = useStores(); + const { setEntryModal } = uiStore; const resetToken = router?.query?.resetToken; const classes = useStyles(); - const [open, setOpen] = React.useState(false); const [anchorElement, setAnchorElement] = useState(null); - const [modalValue, setModalValue] = useState(""); const [viewer, , refetch] = useViewer(); const { accountsClient } = getAccountsHandler(); const isAuthenticated = viewer && viewer._id; - const onClose = () => { - setAnchorElement(null); - }; - useEffect(() => { - if (!resetToken) return; - setOpen(true); - setModalValue("reset-password"); + // Open the modal in case of reset-password link + if (!resetToken) { + return; + } + setEntryModal("reset-password"); }, [resetToken]); - const openModal = (value) => { - setModalValue(value); - setOpen(true); - onClose(); - }; - - const closeModal = () => { - setOpen(false); + const onClose = () => { + setAnchorElement(null); }; const handleSignOut = async () => { @@ -83,25 +57,9 @@ const AccountDropdown = () => { setAnchorElement(event.currentTarget); }; - const getModalComponent = () => { - let comp = Login; - if (modalValue === "signup") { - comp = SignUp; - } else if (modalValue === "change-password") { - comp = ChangePassword; - } else if (modalValue === "forgot-password") { - comp = ForgotPassword; - } else if (modalValue === "reset-password") { - comp = ResetPassword; - } - return React.createElement(comp, { closeModal, openModal, resetToken }); - }; - return ( - -
{getModalComponent()}
-
+ {isAuthenticated ? ( @@ -132,7 +90,7 @@ const AccountDropdown = () => {
-
@@ -143,11 +101,11 @@ const AccountDropdown = () => { ) : (
-
-
diff --git a/components/Entry/Entry.js b/components/Entry/Entry.js index 827816671e..3e102c4b51 100644 --- a/components/Entry/Entry.js +++ b/components/Entry/Entry.js @@ -1,11 +1,11 @@ import React from "react"; import PropTypes from "prop-types"; -import Router from "translations/i18nRouter"; import { withStyles } from "@material-ui/core/styles"; import Typography from "@material-ui/core/Typography"; import Grid from "@material-ui/core/Grid"; import GuestForm from "@reactioncommerce/components/GuestForm/v1"; import Button from "@reactioncommerce/components/Button/v1"; +import useStores from "hooks/useStores"; // flex wrapper jss mixin const flexWrapper = () => ({ @@ -42,7 +42,9 @@ const styles = (theme) => ({ }); const Entry = (props) => { - const { classes, onLoginButtonClick, onRegisterButtonClick, setEmailOnAnonymousCart } = props; + const { classes, setEmailOnAnonymousCart } = props; + const { uiStore } = useStores(); + const { setEntryModal } = uiStore; return ( @@ -50,10 +52,20 @@ const Entry = (props) => { Returning Customer - - @@ -71,12 +83,6 @@ const Entry = (props) => { }; Entry.defaultProps = { - onLoginButtonClick() { - Router.push("/signin"); - }, - onRegisterButtonClick() { - Router.push("/signup"); - }, setEmailOnAnonymousCart() {} }; diff --git a/components/Entry/EntryModal.js b/components/Entry/EntryModal.js new file mode 100644 index 0000000000..67c648980a --- /dev/null +++ b/components/Entry/EntryModal.js @@ -0,0 +1,75 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { makeStyles } from "@material-ui/core/styles"; +import Modal from "@material-ui/core/Modal"; +import useStores from "hooks/useStores"; +import Login from "../Entry/Login"; +import SignUp from "../Entry/SignUp"; +import ChangePassword from "../Entry/ChangePassword"; +import ForgotPassword from "../Entry/ForgotPassword"; +import ResetPassword from "../Entry/ResetPassword"; + +const useStyles = makeStyles((theme) => ({ + paper: { + position: "absolute", + width: 380, + backgroundColor: theme.palette.background.paper, + border: "2px solid #000", + boxShadow: theme.shadows[5], + padding: theme.spacing(2, 4, 3), + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + display: "flex", + alignItems: "center", + justifyContent: "center" + } +})); + +const EntryModal = ({ onClose, resetToken }) => { + const classes = useStyles(); + const { uiStore } = useStores(); + + const { entryModal, setEntryModal } = uiStore; + + const openModal = (value) => { + setEntryModal(value); + }; + + const closeModal = () => { + setEntryModal(null); + onClose(); + }; + + // eslint-disable-next-line react/no-multi-comp + const getModalComponent = () => { + let comp = Login; + if (entryModal === "signup") { + comp = SignUp; + } else if (entryModal === "change-password") { + comp = ChangePassword; + } else if (entryModal === "forgot-password") { + comp = ForgotPassword; + } else if (entryModal === "reset-password") { + comp = ResetPassword; + } + return React.createElement(comp, { closeModal, openModal, resetToken }); + }; + + return ( + +
{getModalComponent()}
+
+ ); +}; + +EntryModal.propTypes = { + onClose: PropTypes.func, + resetToken: PropTypes.string +}; + +EntryModal.defaultProps = { + onClose: () => null +}; + +export default EntryModal; diff --git a/components/Entry/Login.js b/components/Entry/Login.js index eeb82eac01..8ac157d225 100644 --- a/components/Entry/Login.js +++ b/components/Entry/Login.js @@ -11,7 +11,6 @@ import useViewer from "hooks/viewer/useViewer"; import getAccountsHandler from "../../lib/accountsServer.js"; import hashPassword from "../../lib/utils/hashPassword"; - const useStyles = makeStyles((theme) => ({ root: { "display": "flex", @@ -81,8 +80,8 @@ export default function Login(props) { }, password: hashPassword(password) }); - await refetch(); closeModal(); + await refetch(); } catch (err) { setError(err.message); } @@ -98,20 +97,36 @@ export default function Login(props) { Password - -
Forgot Password?
- + tabIndex={0} + > + Forgot Password? + + {!!error &&
{error}
} -
Don't have an account? Sign Up
+ > + Don't have an account? Sign Up + ); } diff --git a/components/Entry/SignUp.js b/components/Entry/SignUp.js index de80f12e9e..0f24c8bee0 100644 --- a/components/Entry/SignUp.js +++ b/components/Entry/SignUp.js @@ -68,8 +68,8 @@ export default function SignUp(props) { try { // Creating user will login also await passwordClient.createUser({ email, password: hashPassword(password) }); - await refetch(); closeModal(); + await refetch(); } catch (err) { setError(err.message); } diff --git a/context/CartContext.js b/context/CartContext.js index 2b499e94d3..157f54eaee 100644 --- a/context/CartContext.js +++ b/context/CartContext.js @@ -11,7 +11,6 @@ export const CartProvider = ({ children }) => { const [anonymousCartId, setAnonymousCartId] = useState(); const [anonymousCartToken, setAnonymousCartToken] = useState(); const [accountCartId, setAccountCartId] = useState(); - const [isReconcilingCarts, setIsReconcilingCarts] = useState(false); const [checkoutPayments, setCheckoutPayments] = useState([]); const setAnonymousCartCredentials = (newAnonymousCartId, newAnonymousCartToken) => { @@ -65,23 +64,23 @@ export const CartProvider = ({ children }) => { }; return ( - {children} diff --git a/context/UIContext.js b/context/UIContext.js index b26955d95f..6f7bfb30ab 100644 --- a/context/UIContext.js +++ b/context/UIContext.js @@ -17,6 +17,7 @@ export const UIProvider = ({ children }) => { const [sortBy, setSortBy] = useState("updatedAt-desc"); const [sortByCurrencyCode, setSortByCurrencyCode] = useState("USD"); // eslint-disable-line no-unused-vars const [openCartTimeout, setOpenCartTimeout] = useState(); + const [entryModal, setEntryModal] = useState(null); const setPDPSelectedVariantId = (variantId, optionId) => { setPdpSelectedVariantId(variantId); @@ -73,31 +74,34 @@ export const UIProvider = ({ children }) => { }; return ( - {children} diff --git a/hooks/cart/useCart.js b/hooks/cart/useCart.js index 0ef19fa76d..422f288f4f 100644 --- a/hooks/cart/useCart.js +++ b/hooks/cart/useCart.js @@ -15,12 +15,9 @@ import { updateCartItemsQuantityMutation, updateFulfillmentOptionsForGroup } from "./mutations.gql"; -import { - accountCartByAccountIdQuery, - anonymousCartByCartIdQuery -} from "./queries.gql"; - +import { accountCartByAccountIdQuery, anonymousCartByCartIdQuery } from "./queries.gql"; +let isReconcilingCarts = false; /** * Hook to get cart information * @@ -48,7 +45,6 @@ export default function useCart() { pollInterval: shouldSkipAccountCartByAccountIdQuery ? 0 : 10000 }); - const [ fetchAnonymousCart, { data: cartDataAnonymous, called: anonymousCartQueryCalled, refetch: refetchAnonymousCart } @@ -73,7 +69,7 @@ export default function useCart() { if (!shouldSkipAnonymousCartByCartIdQuery && anonymousCartQueryCalled) { refetchAnonymousCart(); } - }, [viewer, refetchAccountCart]); + }, [viewer?._id, refetchAccountCart]); const cart = useMemo(() => { if (!shouldSkipAccountCartByAccountIdQuery && cartData) { @@ -113,33 +109,32 @@ export default function useCart() { }; }; - const [addOrCreateCartMutation, { - loading: addOrCreateCartLoading - }] = useMutation(cart && cart._id ? addCartItemsMutation : createCartMutation, { - onCompleted(addOrCreateCartMutationData) { - if (addOrCreateCartMutationData && addOrCreateCartMutationData.createCart && (!viewer || !viewer._id)) { - const { cart: cartPayload, token } = addOrCreateCartMutationData.createCart; - cartStore.setAnonymousCartCredentials(cartPayload._id, token); - } + const [addOrCreateCartMutation, { loading: addOrCreateCartLoading }] = useMutation( + cart && cart._id ? addCartItemsMutation : createCartMutation, + { + onCompleted(addOrCreateCartMutationData) { + if (addOrCreateCartMutationData && addOrCreateCartMutationData.createCart && (!viewer || !viewer._id)) { + const { cart: cartPayload, token } = addOrCreateCartMutationData.createCart; + cartStore.setAnonymousCartCredentials(cartPayload._id, token); + } - const { accountCartId, anonymousCartToken } = cartStore; - if (accountCartId) { - refetchAccountCart(); - } else if (anonymousCartToken) { - refetchAnonymousCart(); + const { accountCartId, anonymousCartToken } = cartStore; + if (accountCartId) { + refetchAccountCart(); + } else if (anonymousCartToken) { + refetchAnonymousCart(); + } } } - }); + ); - const [removeCartItemsMutationFun, { - loading: removeCartItemsLoading - }] = useMutation(removeCartItemsMutation, { + const [removeCartItemsMutationFun, { loading: removeCartItemsLoading }] = useMutation(removeCartItemsMutation, { update(cache, { data: mutationData }) { if (mutationData && mutationData.removeCartItems) { const { cart: cartPayload } = mutationData.removeCartItems; if (cartPayload) { - // Update Apollo cache + // Update Apollo cache cache.writeQuery({ query: cartPayload.account ? accountCartByAccountIdQuery : anonymousCartByCartIdQuery, data: { cart: cartPayload } @@ -149,15 +144,19 @@ export default function useCart() { } }); - const handleRemoveCartItems = useCallback(async (itemIds) => removeCartItemsMutationFun({ - variables: { - input: { - cartId: cartStore.anonymousCartId || cartStore.accountCartId, - cartItemIds: (Array.isArray(itemIds) && itemIds) || [itemIds], - cartToken: cartStore.anonymousCartToken || null - } - } - }), [cartStore.anonymousCartId, cartStore.accountCartId, cartStore.anonymousCartToken]); + const handleRemoveCartItems = useCallback( + async (itemIds) => + removeCartItemsMutationFun({ + variables: { + input: { + cartId: cartStore.anonymousCartId || cartStore.accountCartId, + cartItemIds: (Array.isArray(itemIds) && itemIds) || [itemIds], + cartToken: cartStore.anonymousCartToken || null + } + } + }), + [cartStore.anonymousCartId, cartStore.accountCartId, cartStore.anonymousCartToken] + ); const handleAddItemsToCart = async (data, isCreating) => { const input = { @@ -203,10 +202,9 @@ export default function useCart() { // If we are authenticated, reconcile carts useEffect(() => { - if (cartStore.hasAnonymousCartCredentials && viewer && viewer._id && cartStore.isReconcilingCarts === false) { + if (cartStore.hasAnonymousCartCredentials && viewer && viewer._id && isReconcilingCarts === false) { // Prevent multiple calls to reconcile cart mutations when one is currently in progress - cartStore.setIsReconcilingCarts(true); - + isReconcilingCarts = true; apolloClient.mutate({ mutation: reconcileCartsMutation, update: (cache, { data: mutationData }) => { @@ -231,8 +229,7 @@ export default function useCart() { } } } - - cartStore.setIsReconcilingCarts(false); + isReconcilingCarts = true; }, variables: { input: { @@ -243,7 +240,7 @@ export default function useCart() { } }); } - }, [viewer, cartStore.hasAnonymousCartCredentials, cartStore.isReconcilingCarts, apolloClient]); + }, [viewer?._id, cartStore.hasAnonymousCartCredentials, isReconcilingCarts, apolloClient]); let processedCartData = null; if (cart) { @@ -288,7 +285,9 @@ export default function useCart() { }); // Update fulfillment options for current cart - const { data: { setShippingAddressOnCart } } = response; + const { + data: { setShippingAddressOnCart } + } = response; handleUpdateFulfillmentOptionsForGroup(setShippingAddressOnCart.cart.checkout.fulfillmentGroups[0]._id); return response; @@ -305,7 +304,12 @@ export default function useCart() { const { cart: fetchMoreCart } = fetchMoreResult; // Check for additional items from result - if (fetchMoreCart && fetchMoreCart.items && Array.isArray(fetchMoreCart.items.edges) && fetchMoreCart.items.edges.length) { + if ( + fetchMoreCart && + fetchMoreCart.items && + Array.isArray(fetchMoreCart.items.edges) && + fetchMoreCart.items.edges.length + ) { // Merge previous cart items with next cart items return { ...fetchMoreResult, @@ -314,10 +318,7 @@ export default function useCart() { items: { __typename: previousResult.cart.items.__typename, pageInfo: fetchMoreCart.items.pageInfo, - edges: [ - ...previousResult.cart.items.edges, - ...fetchMoreCart.items.edges - ] + edges: [...previousResult.cart.items.edges, ...fetchMoreCart.items.edges] } } }; diff --git a/pages/[lang]/cart/login.js b/pages/[lang]/cart/login.js index 452c9cd950..06e6ac599c 100644 --- a/pages/[lang]/cart/login.js +++ b/pages/[lang]/cart/login.js @@ -80,11 +80,7 @@ const Login = ({ router }) => { const { locale, t } = useTranslation("common"); // eslint-disable-line no-unused-vars, id-length const shop = useShop(); - const { - cart, - isLoadingCart, - setEmailOnAnonymousCart - } = useCart(); + const { cart, isLoadingCart, setEmailOnAnonymousCart } = useCart(); const hasIdentity = !!((cart && cart.account) || (cart && cart.email)); const pageTitle = `Login | ${shop && shop.name}`; @@ -94,7 +90,7 @@ const Login = ({ router }) => { if (hasIdentity) { Router.push("/cart/checkout"); } - }), [cart, hasIdentity, Router]; // eslint-disable-line no-sequences + }, [cart, hasIdentity, Router]); if (isLoadingCart) { return ( @@ -129,8 +125,8 @@ Login.propTypes = { export async function getStaticProps({ params: { lang } }) { return { props: { - ...await fetchPrimaryShop(lang), - ...await fetchTranslations(lang, ["common"]) + ...(await fetchPrimaryShop(lang)), + ...(await fetchTranslations(lang, ["common"])) } }; }