From 9ac0acae6765705cbecaf5999d395d18685bd40d Mon Sep 17 00:00:00 2001 From: Paul Hachmang Date: Wed, 14 Feb 2024 14:08:09 +0100 Subject: [PATCH 01/13] Assign the customer cart directly so there wont be a query made with the guest cart --- .../hooks/useMergeCustomerCart.ts | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/packages/magento-cart/hooks/useMergeCustomerCart.ts b/packages/magento-cart/hooks/useMergeCustomerCart.ts index 7d704dd4db..88da952c0b 100644 --- a/packages/magento-cart/hooks/useMergeCustomerCart.ts +++ b/packages/magento-cart/hooks/useMergeCustomerCart.ts @@ -1,6 +1,6 @@ -import { useMutation } from '@graphcommerce/graphql' +import { useApolloClient } from '@graphcommerce/graphql' import { useCustomerQuery } from '@graphcommerce/magento-customer' -import { useEffect } from 'react' +import { useMemo } from 'react' import { CustomerCartDocument } from './CustomerCart.gql' import { UseMergeCustomerCartDocument } from './UseMergeCustomerCart.gql' import { useAssignCurrentCartId } from './useAssignCurrentCartId' @@ -11,33 +11,31 @@ import { useCurrentCartId } from './useCurrentCartId' * - Merge the guest cart into the customer cart */ export function useMergeCustomerCart() { - const { currentCartId } = useCurrentCartId() + const { currentCartId: sourceCartId } = useCurrentCartId() + const client = useApolloClient() const assignCurrentCartId = useAssignCurrentCartId() - const [merge] = useMutation(UseMergeCustomerCartDocument, { errorPolicy: 'all' }) const destinationCartId = useCustomerQuery(CustomerCartDocument, { fetchPolicy: 'network-only' }) ?.data?.customerCart.id - useEffect(() => { - // If we don't have a customer cart, we're done - // If the vistor cart is the same as the customer cart, we're done - if (!destinationCartId || currentCartId === destinationCartId) return + useMemo(() => { + if (!destinationCartId || sourceCartId === destinationCartId) return - // If the visitor has a guest cart, try merging it into the customer cart - if (currentCartId) { + if (sourceCartId) { // eslint-disable-next-line @typescript-eslint/no-floating-promises - merge({ variables: { sourceCartId: currentCartId, destinationCartId } }) - // We're not handling exceptions here: - // If the merge returns an error, we'll use the customer cart without merging the guest cart. + client + .mutate({ + mutation: UseMergeCustomerCartDocument, + variables: { sourceCartId, destinationCartId }, + }) .catch((e) => { + // We're not handling exceptions here: + // If the merge returns an error, we'll use the customer cart without merging the guest cart. console.error('Error merging carts', e) }) - .finally(() => { - // Assign the customer cart as the new cart id - assignCurrentCartId(destinationCartId) - }) - } else { - assignCurrentCartId(destinationCartId) } - }, [assignCurrentCartId, destinationCartId, merge, currentCartId]) + + // Assign the customer cart as the new cart id + assignCurrentCartId(destinationCartId) + }, [assignCurrentCartId, client, sourceCartId, destinationCartId]) } From 27d4109244e259123387d72539414eb86d16ae0f Mon Sep 17 00:00:00 2001 From: Paul Hachmang Date: Wed, 14 Feb 2024 14:11:18 +0100 Subject: [PATCH 02/13] Formatting --- packages/magento-cart/hooks/useMergeCustomerCart.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/magento-cart/hooks/useMergeCustomerCart.ts b/packages/magento-cart/hooks/useMergeCustomerCart.ts index 88da952c0b..9c5709f6f7 100644 --- a/packages/magento-cart/hooks/useMergeCustomerCart.ts +++ b/packages/magento-cart/hooks/useMergeCustomerCart.ts @@ -11,7 +11,7 @@ import { useCurrentCartId } from './useCurrentCartId' * - Merge the guest cart into the customer cart */ export function useMergeCustomerCart() { - const { currentCartId: sourceCartId } = useCurrentCartId() + const { currentCartId } = useCurrentCartId() const client = useApolloClient() const assignCurrentCartId = useAssignCurrentCartId() @@ -19,14 +19,16 @@ export function useMergeCustomerCart() { ?.data?.customerCart.id useMemo(() => { - if (!destinationCartId || sourceCartId === destinationCartId) return + // If we don't have a customer cart, we're done + // If the vistor cart is the same as the customer cart, we're done + if (!destinationCartId || currentCartId === destinationCartId) return - if (sourceCartId) { + if (currentCartId) { // eslint-disable-next-line @typescript-eslint/no-floating-promises client .mutate({ mutation: UseMergeCustomerCartDocument, - variables: { sourceCartId, destinationCartId }, + variables: { sourceCartId: currentCartId, destinationCartId }, }) .catch((e) => { // We're not handling exceptions here: @@ -37,5 +39,5 @@ export function useMergeCustomerCart() { // Assign the customer cart as the new cart id assignCurrentCartId(destinationCartId) - }, [assignCurrentCartId, client, sourceCartId, destinationCartId]) + }, [assignCurrentCartId, client, currentCartId, destinationCartId]) } From acb8a9c3671607759ec46fa9702c9b58447cd3b9 Mon Sep 17 00:00:00 2001 From: Paul Hachmang Date: Wed, 14 Feb 2024 14:11:51 +0100 Subject: [PATCH 03/13] More cleanup --- packages/magento-cart/hooks/useMergeCustomerCart.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/magento-cart/hooks/useMergeCustomerCart.ts b/packages/magento-cart/hooks/useMergeCustomerCart.ts index 9c5709f6f7..97677f9a7c 100644 --- a/packages/magento-cart/hooks/useMergeCustomerCart.ts +++ b/packages/magento-cart/hooks/useMergeCustomerCart.ts @@ -30,9 +30,9 @@ export function useMergeCustomerCart() { mutation: UseMergeCustomerCartDocument, variables: { sourceCartId: currentCartId, destinationCartId }, }) + // We're not handling exceptions here: + // If the merge returns an error, we'll use the customer cart without merging the guest cart. .catch((e) => { - // We're not handling exceptions here: - // If the merge returns an error, we'll use the customer cart without merging the guest cart. console.error('Error merging carts', e) }) } From b1454ccd98998b7fb6e3f4a7cad8093f1a636239 Mon Sep 17 00:00:00 2001 From: Paul Hachmang Date: Wed, 14 Feb 2024 15:16:31 +0100 Subject: [PATCH 04/13] Replace merge carts with a plugin on useSignInForm --- .../hooks/useMergeCustomerCart.ts | 41 +---------------- .../plugins/useSignInFormMergeCart.ts | 46 +++++++++++++++++++ .../components/SignInForm/SignInForm.tsx | 24 +--------- .../magento-customer/hooks/useSignInForm.ts | 44 ++++++++++++++++++ 4 files changed, 94 insertions(+), 61 deletions(-) create mode 100644 packages/magento-cart/plugins/useSignInFormMergeCart.ts create mode 100644 packages/magento-customer/hooks/useSignInForm.ts diff --git a/packages/magento-cart/hooks/useMergeCustomerCart.ts b/packages/magento-cart/hooks/useMergeCustomerCart.ts index 97677f9a7c..d2d7399ca6 100644 --- a/packages/magento-cart/hooks/useMergeCustomerCart.ts +++ b/packages/magento-cart/hooks/useMergeCustomerCart.ts @@ -1,43 +1,6 @@ -import { useApolloClient } from '@graphcommerce/graphql' -import { useCustomerQuery } from '@graphcommerce/magento-customer' -import { useMemo } from 'react' -import { CustomerCartDocument } from './CustomerCart.gql' -import { UseMergeCustomerCartDocument } from './UseMergeCustomerCart.gql' -import { useAssignCurrentCartId } from './useAssignCurrentCartId' -import { useCurrentCartId } from './useCurrentCartId' - /** - * - Automatically assign the customer cart as the current cart - * - Merge the guest cart into the customer cart + * @deprecated Is replaced by the useSignInFormMergeCart plugin. */ export function useMergeCustomerCart() { - const { currentCartId } = useCurrentCartId() - const client = useApolloClient() - const assignCurrentCartId = useAssignCurrentCartId() - - const destinationCartId = useCustomerQuery(CustomerCartDocument, { fetchPolicy: 'network-only' }) - ?.data?.customerCart.id - - useMemo(() => { - // If we don't have a customer cart, we're done - // If the vistor cart is the same as the customer cart, we're done - if (!destinationCartId || currentCartId === destinationCartId) return - - if (currentCartId) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - client - .mutate({ - mutation: UseMergeCustomerCartDocument, - variables: { sourceCartId: currentCartId, destinationCartId }, - }) - // We're not handling exceptions here: - // If the merge returns an error, we'll use the customer cart without merging the guest cart. - .catch((e) => { - console.error('Error merging carts', e) - }) - } - - // Assign the customer cart as the new cart id - assignCurrentCartId(destinationCartId) - }, [assignCurrentCartId, client, currentCartId, destinationCartId]) + return null } diff --git a/packages/magento-cart/plugins/useSignInFormMergeCart.ts b/packages/magento-cart/plugins/useSignInFormMergeCart.ts new file mode 100644 index 0000000000..10bdcbbca6 --- /dev/null +++ b/packages/magento-cart/plugins/useSignInFormMergeCart.ts @@ -0,0 +1,46 @@ +import { useApolloClient } from '@graphcommerce/graphql' +import type { useSignInForm } from '@graphcommerce/magento-customer/hooks/useSignInForm' +import type { MethodPlugin } from '@graphcommerce/next-config' +import { useCurrentCartId, useAssignCurrentCartId } from '../hooks' +import { CustomerCartDocument } from '../hooks/CustomerCart.gql' +import { UseMergeCustomerCartDocument } from '../hooks/UseMergeCustomerCart.gql' + +export const func = 'useSignInForm' +export const exported = '@graphcommerce/magento-customer/hooks/useSignInForm' + +const useSignInFormMergeCart: MethodPlugin = (useSignInForm, options) => { + const { currentCartId } = useCurrentCartId() + const client = useApolloClient() + const assignCurrentCartId = useAssignCurrentCartId() + + return useSignInForm({ + ...options, + onComplete: async (data, variables) => { + await options.onComplete?.(data, variables) + + const destinationCartId = ( + await client.query({ query: CustomerCartDocument, fetchPolicy: 'network-only' }) + ).data.customerCart.id + + // If we don't have a customer cart, we're done + // If the vistor cart is the same as the customer cart, we're done + if (!destinationCartId || currentCartId === destinationCartId) return + + // Assign the customer cart as the new cart id + assignCurrentCartId(destinationCartId) + + if (currentCartId) { + await client + .mutate({ + mutation: UseMergeCustomerCartDocument, + variables: { sourceCartId: currentCartId, destinationCartId }, + }) + // We're not handling exceptions here: + // If the merge returns an error, we'll use the customer cart without merging the guest cart. + .catch((e) => console.error('Error merging carts', e)) + } + }, + }) +} + +export const plugin = useSignInFormMergeCart diff --git a/packages/magento-customer/components/SignInForm/SignInForm.tsx b/packages/magento-customer/components/SignInForm/SignInForm.tsx index 919ae27be7..a02ecc7e85 100644 --- a/packages/magento-customer/components/SignInForm/SignInForm.tsx +++ b/packages/magento-customer/components/SignInForm/SignInForm.tsx @@ -1,37 +1,17 @@ import { PasswordElement } from '@graphcommerce/ecommerce-ui' -import { useApolloClient } from '@graphcommerce/graphql' import { graphqlErrorByCategory } from '@graphcommerce/magento-graphql' import { Button, FormRow, FormActions } from '@graphcommerce/next-ui' -import { useFormGqlMutation } from '@graphcommerce/react-hook-form' import { Trans } from '@lingui/react' import { Box, FormControl, Link, SxProps, Theme } from '@mui/material' -import { CustomerDocument } from '../../hooks' +import { useSignInForm } from '../../hooks/useSignInForm' import { ApolloCustomerErrorAlert } from '../ApolloCustomerError/ApolloCustomerErrorAlert' -import { SignInDocument } from './SignIn.gql' -import { signOut } from '../SignOutForm/signOut' export type SignInFormProps = { email: string; sx?: SxProps } export function SignInForm(props: SignInFormProps) { const { email, sx } = props - const client = useApolloClient() - const form = useFormGqlMutation( - SignInDocument, - { - defaultValues: { email }, - onBeforeSubmit: async (values) => { - const oldEmail = client.cache.readQuery({ query: CustomerDocument })?.customer?.email - /** - * We are logging in because the session expired, but we're logging in with a different - * email address, we need to reset the store. - */ - if (oldEmail && oldEmail !== email) signOut(client) - return { ...values, email } - }, - }, - { errorPolicy: 'all' }, - ) + const form = useSignInForm({ email }) const { handleSubmit, required, formState, error, control } = form const [remainingError, authError] = graphqlErrorByCategory({ diff --git a/packages/magento-customer/hooks/useSignInForm.ts b/packages/magento-customer/hooks/useSignInForm.ts new file mode 100644 index 0000000000..be0f09f54d --- /dev/null +++ b/packages/magento-customer/hooks/useSignInForm.ts @@ -0,0 +1,44 @@ +import { useApolloClient } from '@graphcommerce/graphql' +import { UseFormGraphQlOptions, useFormGqlMutation } from '@graphcommerce/react-hook-form' +import { + SignInDocument, + SignInMutation, + SignInMutationVariables, +} from '../components/SignInForm/SignIn.gql' +import { signOut } from '../components/SignOutForm/signOut' +import { CustomerDocument } from './Customer.gql' + +type UseSignInFormProps = { + email: string +} & UseFormGraphQlOptions + +/** + * To extend the actions that happen after a successful sign in, you can use the `onComplete` option. + * + * @example @graphcommerce/magento-cart/plugins/useSignInFormMergeCart + */ +export function useSignInForm({ email, ...options }: UseSignInFormProps) { + const client = useApolloClient() + + return useFormGqlMutation( + SignInDocument, + { + ...options, + defaultValues: { email, ...options?.defaultValues }, + onBeforeSubmit: async (values) => { + const oldEmail = client.cache.readQuery({ query: CustomerDocument })?.customer?.email + + /** + * We are logging in because the session expired, but we're logging in with a different + * email address, we need to reset the store. + */ + if (oldEmail && oldEmail !== email) signOut(client) + + return options?.onBeforeSubmit + ? options.onBeforeSubmit({ ...values, email }) + : { ...values, email } + }, + }, + { errorPolicy: 'all' }, + ) +} From 3fbf3da8a67f2fbaa7fa974a37cbbf34613844e4 Mon Sep 17 00:00:00 2001 From: Paul Hachmang Date: Thu, 15 Feb 2024 14:16:34 +0100 Subject: [PATCH 05/13] Create nice-bugs-push.md --- .changeset/nice-bugs-push.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/nice-bugs-push.md diff --git a/.changeset/nice-bugs-push.md b/.changeset/nice-bugs-push.md new file mode 100644 index 0000000000..8f7c1aebdd --- /dev/null +++ b/.changeset/nice-bugs-push.md @@ -0,0 +1,6 @@ +--- +"@graphcommerce/magento-cart": patch +"@graphcommerce/magento-customer": patch +--- + +Solve an issue where the user would be presented with the Session expired dialog when the user would be logging in during the checkout process. From 67c8327a966ae8c56c15e67568d0f69e1cfe077f Mon Sep 17 00:00:00 2001 From: Paul Hachmang Date: Fri, 16 Feb 2024 16:31:53 +0100 Subject: [PATCH 06/13] Lock the current cart so the queries do not get refreshed during load --- .../magento-cart/hooks/CurrentCartId.graphql | 1 + .../magento-cart/hooks/CurrentCartId.graphqls | 1 + .../hooks/useAssignCurrentCartId.ts | 2 +- packages/magento-cart/hooks/useCartQuery.ts | 10 +++---- .../hooks/useClearCurrentCartId.ts | 6 +---- .../magento-cart/hooks/useCurrentCartId.ts | 7 ++++- .../plugins/useSignInFormMergeCart.ts | 26 +++++++++++++------ .../hooks/CustomerToken.graphqls | 2 +- 8 files changed, 32 insertions(+), 23 deletions(-) diff --git a/packages/magento-cart/hooks/CurrentCartId.graphql b/packages/magento-cart/hooks/CurrentCartId.graphql index 0ed32d02f5..a8b4b8a3bf 100644 --- a/packages/magento-cart/hooks/CurrentCartId.graphql +++ b/packages/magento-cart/hooks/CurrentCartId.graphql @@ -2,5 +2,6 @@ query CurrentCartId { currentCartId @client { __typename id + locked } } diff --git a/packages/magento-cart/hooks/CurrentCartId.graphqls b/packages/magento-cart/hooks/CurrentCartId.graphqls index e9cee2a29c..4856711677 100644 --- a/packages/magento-cart/hooks/CurrentCartId.graphqls +++ b/packages/magento-cart/hooks/CurrentCartId.graphqls @@ -4,6 +4,7 @@ extend type Query { type CurrentCartId { id: String + locked: Boolean } input RegisterCartIdInput { diff --git a/packages/magento-cart/hooks/useAssignCurrentCartId.ts b/packages/magento-cart/hooks/useAssignCurrentCartId.ts index de62813bec..4aeafa9dd1 100644 --- a/packages/magento-cart/hooks/useAssignCurrentCartId.ts +++ b/packages/magento-cart/hooks/useAssignCurrentCartId.ts @@ -8,7 +8,7 @@ export const CART_ID_COOKIE = 'cart' export function writeCartId(cache: ApolloCache, id: string | null = null) { cache.writeQuery({ query: CurrentCartIdDocument, - data: { currentCartId: { __typename: 'CurrentCartId', id } }, + data: { currentCartId: { __typename: 'CurrentCartId', locked: false, id } }, broadcast: true, }) } diff --git a/packages/magento-cart/hooks/useCartQuery.ts b/packages/magento-cart/hooks/useCartQuery.ts index d93e84d981..67c457d42f 100644 --- a/packages/magento-cart/hooks/useCartQuery.ts +++ b/packages/magento-cart/hooks/useCartQuery.ts @@ -20,13 +20,14 @@ export function useCartQuery) - - return { - ...result, - // error: called && !currentCartId ? noCartError : result.error, - } + return useQuery(document, queryOptions as QueryHookOptions) } diff --git a/packages/magento-cart/hooks/useClearCurrentCartId.ts b/packages/magento-cart/hooks/useClearCurrentCartId.ts index 5590918cba..662d2cfd06 100644 --- a/packages/magento-cart/hooks/useClearCurrentCartId.ts +++ b/packages/magento-cart/hooks/useClearCurrentCartId.ts @@ -10,11 +10,7 @@ export function useClearCurrentCartId() { const id = cache.readQuery({ query: CurrentCartIdDocument })?.currentCartId?.id if (!id) return - cache.writeQuery({ - query: CurrentCartIdDocument, - data: { currentCartId: { __typename: 'CurrentCartId', id: null } }, - broadcast: true, - }) + cache.evict({ fieldName: 'currentCartId', broadcast: true }) cookie(CART_ID_COOKIE, null) } } diff --git a/packages/magento-cart/hooks/useCurrentCartId.ts b/packages/magento-cart/hooks/useCurrentCartId.ts index 47fae36cda..a6da6b5e85 100644 --- a/packages/magento-cart/hooks/useCurrentCartId.ts +++ b/packages/magento-cart/hooks/useCurrentCartId.ts @@ -10,5 +10,10 @@ export function useCurrentCartId< V extends CurrentCartIdQueryVariables, >(options: QueryHookOptions = {}) { const queryResults = useQuery(CurrentCartIdDocument, options) - return { currentCartId: queryResults.data?.currentCartId?.id || '', ...queryResults } + + return { + currentCartId: queryResults.data?.currentCartId?.id || '', + locked: queryResults.data?.currentCartId?.locked || false, + ...queryResults, + } } diff --git a/packages/magento-cart/plugins/useSignInFormMergeCart.ts b/packages/magento-cart/plugins/useSignInFormMergeCart.ts index 10bdcbbca6..53d1b2daf1 100644 --- a/packages/magento-cart/plugins/useSignInFormMergeCart.ts +++ b/packages/magento-cart/plugins/useSignInFormMergeCart.ts @@ -1,7 +1,8 @@ import { useApolloClient } from '@graphcommerce/graphql' import type { useSignInForm } from '@graphcommerce/magento-customer/hooks/useSignInForm' import type { MethodPlugin } from '@graphcommerce/next-config' -import { useCurrentCartId, useAssignCurrentCartId } from '../hooks' +import { useAssignCurrentCartId } from '../hooks' +import { CurrentCartIdDocument } from '../hooks/CurrentCartId.gql' import { CustomerCartDocument } from '../hooks/CustomerCart.gql' import { UseMergeCustomerCartDocument } from '../hooks/UseMergeCustomerCart.gql' @@ -9,31 +10,40 @@ export const func = 'useSignInForm' export const exported = '@graphcommerce/magento-customer/hooks/useSignInForm' const useSignInFormMergeCart: MethodPlugin = (useSignInForm, options) => { - const { currentCartId } = useCurrentCartId() const client = useApolloClient() const assignCurrentCartId = useAssignCurrentCartId() return useSignInForm({ ...options, onComplete: async (data, variables) => { + const currentCartId = client.cache.readQuery({ query: CurrentCartIdDocument })?.currentCartId + if (currentCartId?.id && !currentCartId.locked) { + client.cache.writeQuery({ + query: CurrentCartIdDocument, + data: { currentCartId: { ...currentCartId, locked: true } }, + }) + } + await options.onComplete?.(data, variables) - const destinationCartId = ( - await client.query({ query: CustomerCartDocument, fetchPolicy: 'network-only' }) - ).data.customerCart.id + const customerCart = await client.query({ + query: CustomerCartDocument, + fetchPolicy: 'network-only', + }) + const destinationCartId = customerCart.data.customerCart.id // If we don't have a customer cart, we're done // If the vistor cart is the same as the customer cart, we're done - if (!destinationCartId || currentCartId === destinationCartId) return + if (!destinationCartId || currentCartId?.id === destinationCartId) return // Assign the customer cart as the new cart id assignCurrentCartId(destinationCartId) - if (currentCartId) { + if (currentCartId?.id) { await client .mutate({ mutation: UseMergeCustomerCartDocument, - variables: { sourceCartId: currentCartId, destinationCartId }, + variables: { sourceCartId: currentCartId.id, destinationCartId }, }) // We're not handling exceptions here: // If the merge returns an error, we'll use the customer cart without merging the guest cart. diff --git a/packages/magento-customer/hooks/CustomerToken.graphqls b/packages/magento-customer/hooks/CustomerToken.graphqls index a62a01fba0..60f0b7434e 100644 --- a/packages/magento-customer/hooks/CustomerToken.graphqls +++ b/packages/magento-customer/hooks/CustomerToken.graphqls @@ -3,6 +3,6 @@ extend type Query { } extend type CustomerToken { - createdAt: String + createdAt: String @deprecated(reason: "Value is not used in GraphCommerce, but still filled in.") valid: Boolean } From eb14696fc65e084a06790c88a8218fb3003f7c2c Mon Sep 17 00:00:00 2001 From: Paul Hachmang Date: Fri, 16 Feb 2024 17:12:13 +0100 Subject: [PATCH 07/13] Wait for queries to resolve by default, this might introduce an additional spinner but prevents a flash where it is shown that there is no cart --- .changeset/late-cats-jump.md | 5 +++++ .../components/WaitForQueries/WaitForQueries.tsx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/late-cats-jump.md diff --git a/.changeset/late-cats-jump.md b/.changeset/late-cats-jump.md new file mode 100644 index 0000000000..2c1cd88ba0 --- /dev/null +++ b/.changeset/late-cats-jump.md @@ -0,0 +1,5 @@ +--- +"@graphcommerce/ecommerce-ui": patch +--- + +Wait for queries to resolve by default, this might introduce an additional spinner but prevents a flash where it is shown that there is no cart diff --git a/packages/ecommerce-ui/components/WaitForQueries/WaitForQueries.tsx b/packages/ecommerce-ui/components/WaitForQueries/WaitForQueries.tsx index 4f52af378b..59812e86e3 100644 --- a/packages/ecommerce-ui/components/WaitForQueries/WaitForQueries.tsx +++ b/packages/ecommerce-ui/components/WaitForQueries/WaitForQueries.tsx @@ -10,7 +10,7 @@ export type WaitForQueriesProps = { /** Shows the fallback during: SSR, Hydration and Query Loading. */ export const WaitForQueries = (props: WaitForQueriesProps) => { - const { waitFor, fallback, children, noSsr = false } = props + const { waitFor, fallback, children, noSsr = true } = props // Make sure the first render is always the same as the server. // Make sure we we use startTransition to make sure we don't get into trouble with Suspense. From d67c89d464a60f0e2618dab670b63a39f6291341 Mon Sep 17 00:00:00 2001 From: Paul Hachmang Date: Mon, 19 Feb 2024 10:13:54 +0100 Subject: [PATCH 08/13] Deprecate the allowUrl option for useCartQuery, it was already enabled by default and should never be set to false. --- .changeset/mighty-humans-listen.md | 7 +++++++ .../components/CartItemSummary/CartItemSummary.tsx | 7 ++----- packages/magento-cart/components/CartTotals/CartTotals.tsx | 2 +- packages/magento-cart/hooks/useCartQuery.ts | 7 +++++-- .../GuestNewsletterToggle/GuestNewsletterToggle.tsx | 3 +-- .../components/SignupNewsletter/SignupNewsletter.tsx | 2 +- 6 files changed, 17 insertions(+), 11 deletions(-) create mode 100644 .changeset/mighty-humans-listen.md diff --git a/.changeset/mighty-humans-listen.md b/.changeset/mighty-humans-listen.md new file mode 100644 index 0000000000..5fce194f9a --- /dev/null +++ b/.changeset/mighty-humans-listen.md @@ -0,0 +1,7 @@ +--- +'@graphcommerce/magento-cart-shipping-address': patch +'@graphcommerce/magento-newsletter': patch +'@graphcommerce/magento-cart': patch +--- + +Deprecate the allowUrl option for useCartQuery, it was already enabled by default and should never be set to false. diff --git a/packages/magento-cart/components/CartItemSummary/CartItemSummary.tsx b/packages/magento-cart/components/CartItemSummary/CartItemSummary.tsx index 82971603cb..4aa6e5c1af 100644 --- a/packages/magento-cart/components/CartItemSummary/CartItemSummary.tsx +++ b/packages/magento-cart/components/CartItemSummary/CartItemSummary.tsx @@ -38,10 +38,7 @@ type OrderSummaryProps = ActionCardLayoutProps & { export function CartItemSummary(props: OrderSummaryProps) { const { sx = [], size, layout = 'list', itemProps, ...cardLayout } = props - const { data } = useCartQuery(CartItemSummaryDocument, { - allowUrl: true, - fetchPolicy: 'cache-only', - }) + const { data } = useCartQuery(CartItemSummaryDocument, { fetchPolicy: 'cache-only' }) if (!data?.cart) return null @@ -85,7 +82,7 @@ export function CartItemSummary(props: OrderSummaryProps) { className={classes.scrollerContainer} {...cardLayout} > - {items?.filter(nonNullable).map((item) => ( + {(items ?? []).filter(nonNullable).map((item) => ( * @see https://github.com/magento/magento2/issues/33848 */ export function CartTotals(props: CartTotalsProps) { - const { data } = useCartQuery(GetCartTotalsDocument, { allowUrl: true }) + const { data } = useCartQuery(GetCartTotalsDocument) const { containerMargin, additionalSubtotals, additionalTotals, sx = [] } = props const classes = withState({ containerMargin }) diff --git a/packages/magento-cart/hooks/useCartQuery.ts b/packages/magento-cart/hooks/useCartQuery.ts index 67c457d42f..c2a25c4424 100644 --- a/packages/magento-cart/hooks/useCartQuery.ts +++ b/packages/magento-cart/hooks/useCartQuery.ts @@ -15,15 +15,18 @@ import { useCurrentCartId } from './useCurrentCartId' export function useCartQuery( document: TypedDocumentNode, options: QueryHookOptions> & { + /** + * @deprecated Not used anymore, when the cart_id is in the URL, it will always be used. + */ allowUrl?: boolean } = {}, ) { - const { allowUrl = true, ...queryOptions } = options + const { allowUrl, ...queryOptions } = options const router = useRouter() const { currentCartId, locked } = useCurrentCartId() const urlCartId = router.query.cart_id - const usingUrl = allowUrl && typeof urlCartId === 'string' + const usingUrl = typeof urlCartId === 'string' const cartId = usingUrl ? urlCartId : currentCartId if (usingUrl) queryOptions.fetchPolicy = 'cache-first' diff --git a/packages/magento-newsletter/components/GuestNewsletterToggle/GuestNewsletterToggle.tsx b/packages/magento-newsletter/components/GuestNewsletterToggle/GuestNewsletterToggle.tsx index 3c1ed0bdce..3e530dc543 100644 --- a/packages/magento-newsletter/components/GuestNewsletterToggle/GuestNewsletterToggle.tsx +++ b/packages/magento-newsletter/components/GuestNewsletterToggle/GuestNewsletterToggle.tsx @@ -25,8 +25,7 @@ export type GuestNewsletterToggleProps = SwitchProps & { sx?: SxProps } export function GuestNewsletterToggle(props: GuestNewsletterToggleProps) { const { sx = [], ...switchProps } = props - const email = - useCartQuery(GetCartEmailDocument, { allowUrl: true }).data?.cart?.email ?? undefined + const email = useCartQuery(GetCartEmailDocument).data?.cart?.email ?? undefined const form = useFormGqlMutation< GuestNewsletterToggleMutation, GuestNewsletterToggleMutationVariables & { isSubscribed?: boolean } diff --git a/packages/magento-newsletter/components/SignupNewsletter/SignupNewsletter.tsx b/packages/magento-newsletter/components/SignupNewsletter/SignupNewsletter.tsx index ec2582b78b..1fd2ed0738 100644 --- a/packages/magento-newsletter/components/SignupNewsletter/SignupNewsletter.tsx +++ b/packages/magento-newsletter/components/SignupNewsletter/SignupNewsletter.tsx @@ -17,7 +17,7 @@ const { withState } = extendableComponent export function SignupNewsletter(props: SignupNewsletterProps) { const { sx = [] } = props - const { data: cartData } = useCartQuery(GetCartEmailDocument, { allowUrl: true }) + const { data: cartData } = useCartQuery(GetCartEmailDocument) const { loggedIn } = useCustomerSession() const classes = withState({ loggedIn }) From d47c94e2a165882e5e286b8e8f5c665eaf6c7264 Mon Sep 17 00:00:00 2001 From: Paul Hachmang Date: Mon, 19 Feb 2024 11:32:07 +0100 Subject: [PATCH 09/13] Cleanup cart merge logic and make sure the cart gets unlocked after revalidating the cart, introduce utility function readCartId --- .../components/CartDebugger/CartDebugger.tsx | 18 ++------ .../magento-cart/hooks/CustomerCart.graphql | 1 - ...ustomerCart.graphql => MergeCarts.graphql} | 3 +- .../hooks/useAssignCurrentCartId.ts | 15 +++++++ .../magento-cart/hooks/useCartIdCreate.ts | 6 +-- .../hooks/useClearCurrentCartId.ts | 4 -- .../plugins/useSignInFormMergeCart.ts | 45 ++++++++----------- packages/magento-cart/typePolicies.ts | 7 ++- 8 files changed, 44 insertions(+), 55 deletions(-) rename packages/magento-cart/hooks/{UseMergeCustomerCart.graphql => MergeCarts.graphql} (50%) diff --git a/packages/magento-cart/components/CartDebugger/CartDebugger.tsx b/packages/magento-cart/components/CartDebugger/CartDebugger.tsx index 05152c3fb8..21f066e123 100644 --- a/packages/magento-cart/components/CartDebugger/CartDebugger.tsx +++ b/packages/magento-cart/components/CartDebugger/CartDebugger.tsx @@ -1,6 +1,6 @@ import { useApolloClient } from '@graphcommerce/graphql' import { Button } from '@mui/material' -import { CurrentCartIdDocument } from '../../hooks/CurrentCartId.gql' +import { readCartId, writeCartId } from '../../hooks' export function CartDebugger() { const client = useApolloClient() @@ -12,24 +12,14 @@ export function CartDebugger() { variant='text' size='small' onClick={() => { - const currentCardId = client.readQuery({ query: CurrentCartIdDocument }) - if (!currentCardId?.currentCartId) { + const currentCartId = readCartId(client.cache) + if (!currentCartId) { // eslint-disable-next-line no-console console.log('No customerToken available, nothing to break') } else { // eslint-disable-next-line no-console console.log(`Changing current token to a random one`) - - client.writeQuery({ - query: CurrentCartIdDocument, - data: { - currentCartId: { - ...currentCardId.currentCartId, - id: `${Math.random().toString(36).slice(2)}random-cardId`, - }, - }, - broadcast: true, - }) + writeCartId(client.cache, `${Math.random().toString(36).slice(2)}random-cardId`) } }} > diff --git a/packages/magento-cart/hooks/CustomerCart.graphql b/packages/magento-cart/hooks/CustomerCart.graphql index 3ce034a4e3..d055551c10 100644 --- a/packages/magento-cart/hooks/CustomerCart.graphql +++ b/packages/magento-cart/hooks/CustomerCart.graphql @@ -2,6 +2,5 @@ query CustomerCart { customerCart { id __typename - ...CartItemCountChanged } } diff --git a/packages/magento-cart/hooks/UseMergeCustomerCart.graphql b/packages/magento-cart/hooks/MergeCarts.graphql similarity index 50% rename from packages/magento-cart/hooks/UseMergeCustomerCart.graphql rename to packages/magento-cart/hooks/MergeCarts.graphql index 2ffb7fc8ad..9bb7d501e6 100644 --- a/packages/magento-cart/hooks/UseMergeCustomerCart.graphql +++ b/packages/magento-cart/hooks/MergeCarts.graphql @@ -1,7 +1,6 @@ -mutation UseMergeCustomerCart($sourceCartId: String!, $destinationCartId: String!) { +mutation MergeCarts($sourceCartId: String!, $destinationCartId: String!) { mergeCarts(source_cart_id: $sourceCartId, destination_cart_id: $destinationCartId) { __typename id - ...CartItemCountChanged } } diff --git a/packages/magento-cart/hooks/useAssignCurrentCartId.ts b/packages/magento-cart/hooks/useAssignCurrentCartId.ts index 4aeafa9dd1..dfc3fb1213 100644 --- a/packages/magento-cart/hooks/useAssignCurrentCartId.ts +++ b/packages/magento-cart/hooks/useAssignCurrentCartId.ts @@ -13,6 +13,21 @@ export function writeCartId(cache: ApolloCache, id: string | null = null }) } +export function readCartId(cache: ApolloCache) { + return cache.readQuery({ query: CurrentCartIdDocument })?.currentCartId +} + +export function cartLock(cache: ApolloCache, locked: boolean) { + const currentCartId = cache.readQuery({ query: CurrentCartIdDocument })?.currentCartId + if (currentCartId?.id && currentCartId.locked !== locked) { + cache.writeQuery({ + query: CurrentCartIdDocument, + data: { currentCartId: { ...currentCartId, locked } }, + broadcast: true, + }) + } +} + export function useAssignCurrentCartId() { const { cache } = useApolloClient() diff --git a/packages/magento-cart/hooks/useCartIdCreate.ts b/packages/magento-cart/hooks/useCartIdCreate.ts index 95d2395699..0cde1b5ed5 100644 --- a/packages/magento-cart/hooks/useCartIdCreate.ts +++ b/packages/magento-cart/hooks/useCartIdCreate.ts @@ -1,16 +1,14 @@ import { useApolloClient } from '@graphcommerce/graphql' import { i18n } from '@lingui/core' import { CreateEmptyCartDocument } from './CreateEmptyCart.gql' -import { CurrentCartIdDocument } from './CurrentCartId.gql' -import { useAssignCurrentCartId } from './useAssignCurrentCartId' +import { readCartId, useAssignCurrentCartId } from './useAssignCurrentCartId' export function useCartIdCreate() { const client = useApolloClient() const assignCurrentCartId = useAssignCurrentCartId() return async (): Promise => { - const currentCartId = client.cache.readQuery({ query: CurrentCartIdDocument })?.currentCartId - ?.id + const currentCartId = readCartId(client.cache)?.id if (currentCartId) return currentCartId diff --git a/packages/magento-cart/hooks/useClearCurrentCartId.ts b/packages/magento-cart/hooks/useClearCurrentCartId.ts index 662d2cfd06..a72adbc210 100644 --- a/packages/magento-cart/hooks/useClearCurrentCartId.ts +++ b/packages/magento-cart/hooks/useClearCurrentCartId.ts @@ -1,15 +1,11 @@ import { useApolloClient } from '@graphcommerce/graphql' import { cookie } from '@graphcommerce/next-ui' -import { CurrentCartIdDocument } from './CurrentCartId.gql' import { CART_ID_COOKIE } from './useAssignCurrentCartId' export function useClearCurrentCartId() { const { cache } = useApolloClient() return () => { - const id = cache.readQuery({ query: CurrentCartIdDocument })?.currentCartId?.id - if (!id) return - cache.evict({ fieldName: 'currentCartId', broadcast: true }) cookie(CART_ID_COOKIE, null) } diff --git a/packages/magento-cart/plugins/useSignInFormMergeCart.ts b/packages/magento-cart/plugins/useSignInFormMergeCart.ts index 53d1b2daf1..f50fb9d456 100644 --- a/packages/magento-cart/plugins/useSignInFormMergeCart.ts +++ b/packages/magento-cart/plugins/useSignInFormMergeCart.ts @@ -1,10 +1,9 @@ import { useApolloClient } from '@graphcommerce/graphql' import type { useSignInForm } from '@graphcommerce/magento-customer/hooks/useSignInForm' import type { MethodPlugin } from '@graphcommerce/next-config' -import { useAssignCurrentCartId } from '../hooks' -import { CurrentCartIdDocument } from '../hooks/CurrentCartId.gql' +import { cartLock, readCartId, useAssignCurrentCartId } from '../hooks' import { CustomerCartDocument } from '../hooks/CustomerCart.gql' -import { UseMergeCustomerCartDocument } from '../hooks/UseMergeCustomerCart.gql' +import { MergeCartsDocument } from '../hooks/MergeCarts.gql' export const func = 'useSignInForm' export const exported = '@graphcommerce/magento-customer/hooks/useSignInForm' @@ -16,38 +15,32 @@ const useSignInFormMergeCart: MethodPlugin = (useSignInFor return useSignInForm({ ...options, onComplete: async (data, variables) => { - const currentCartId = client.cache.readQuery({ query: CurrentCartIdDocument })?.currentCartId - if (currentCartId?.id && !currentCartId.locked) { - client.cache.writeQuery({ - query: CurrentCartIdDocument, - data: { currentCartId: { ...currentCartId, locked: true } }, - }) - } - await options.onComplete?.(data, variables) + cartLock(client.cache, true) + const customerCart = await client.query({ query: CustomerCartDocument, fetchPolicy: 'network-only', }) const destinationCartId = customerCart.data.customerCart.id - // If we don't have a customer cart, we're done - // If the vistor cart is the same as the customer cart, we're done - if (!destinationCartId || currentCartId?.id === destinationCartId) return - - // Assign the customer cart as the new cart id - assignCurrentCartId(destinationCartId) - - if (currentCartId?.id) { - await client - .mutate({ - mutation: UseMergeCustomerCartDocument, - variables: { sourceCartId: currentCartId.id, destinationCartId }, + try { + const sourceCartId = readCartId(client.cache)?.id + if (sourceCartId && sourceCartId !== destinationCartId) { + await client.mutate({ + mutation: MergeCartsDocument, + variables: { sourceCartId, destinationCartId }, }) - // We're not handling exceptions here: - // If the merge returns an error, we'll use the customer cart without merging the guest cart. - .catch((e) => console.error('Error merging carts', e)) + } + } catch (error) { + console.error( + 'Error merging carts, continuing without merging, this might cause issues.', + error, + ) + } finally { + // Assign the customer cart as the new cart id + assignCurrentCartId(destinationCartId) } }, }) diff --git a/packages/magento-cart/typePolicies.ts b/packages/magento-cart/typePolicies.ts index 5b37f34066..fc5cdacd54 100644 --- a/packages/magento-cart/typePolicies.ts +++ b/packages/magento-cart/typePolicies.ts @@ -2,7 +2,7 @@ import { ApolloCache, NormalizedCacheObject } from '@graphcommerce/graphql' import type { StrictTypedTypePolicies } from '@graphcommerce/graphql' import type { CartPrices, QuerycartArgs, ShippingCartAddress } from '@graphcommerce/graphql-mesh' import { CartFabDocument } from './components/CartFab/CartFab.gql' -import { CurrentCartIdDocument } from './hooks/CurrentCartId.gql' +import { readCartId, writeCartId } from './hooks' export const cartTypePolicies: StrictTypedTypePolicies = { CurrentCartId: { keyFields: [] }, @@ -53,11 +53,10 @@ export const migrateCart = ( oldCache: ApolloCache, newCache: ApolloCache, ) => { - const currentCartId = oldCache.readQuery({ query: CurrentCartIdDocument }) - const cartId = currentCartId?.currentCartId?.id + const cartId = readCartId(oldCache)?.id if (cartId) { - newCache.writeQuery({ query: CurrentCartIdDocument, data: currentCartId, broadcast: true }) + writeCartId(newCache, cartId) // We have special handling for the CartFab because it tries to load data only from the cache. const cartFab = oldCache.readQuery({ query: CartFabDocument }) From b2aae502414b236c86356d19e7979c24f240f962 Mon Sep 17 00:00:00 2001 From: Paul Hachmang Date: Mon, 19 Feb 2024 11:56:11 +0100 Subject: [PATCH 10/13] Changeset --- .changeset/late-cats-jump.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/late-cats-jump.md b/.changeset/late-cats-jump.md index 2c1cd88ba0..77817f3594 100644 --- a/.changeset/late-cats-jump.md +++ b/.changeset/late-cats-jump.md @@ -1,5 +1,5 @@ --- -"@graphcommerce/ecommerce-ui": patch +'@graphcommerce/ecommerce-ui': patch --- -Wait for queries to resolve by default, this might introduce an additional spinner but prevents a flash where it is shown that there is no cart +`` will default to loading, restoring the previous behavior. This might introduce , this might introduce an additional spinner but prevents a flash where it is shown that there is no cart From a45979d15cf2e3d5a25a127a5f2f10b886a7c42d Mon Sep 17 00:00:00 2001 From: Paul Hachmang Date: Mon, 19 Feb 2024 12:14:05 +0100 Subject: [PATCH 11/13] Faster transition for the WaitForQueries method --- .../ecommerce-ui/components/WaitForQueries/WaitForQueries.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ecommerce-ui/components/WaitForQueries/WaitForQueries.tsx b/packages/ecommerce-ui/components/WaitForQueries/WaitForQueries.tsx index 59812e86e3..247778db2d 100644 --- a/packages/ecommerce-ui/components/WaitForQueries/WaitForQueries.tsx +++ b/packages/ecommerce-ui/components/WaitForQueries/WaitForQueries.tsx @@ -1,3 +1,4 @@ +import { useIsomorphicLayoutEffect } from '@graphcommerce/framer-utils' import { QueryResult } from '@graphcommerce/graphql' import React, { startTransition, useEffect, useState } from 'react' @@ -16,7 +17,7 @@ export const WaitForQueries = (props: WaitForQueriesProps) => { // Make sure we we use startTransition to make sure we don't get into trouble with Suspense. const [mounted, setMounted] = useState(!noSsr) useEffect(() => { - if (noSsr) startTransition(() => setMounted(true)) + if (noSsr) setMounted(true) }, [noSsr]) // We are done when all queries either have data or an error. From b87d1e368f0a37c2249aea199286f8b276551681 Mon Sep 17 00:00:00 2001 From: Paul Hachmang Date: Mon, 19 Feb 2024 12:14:19 +0100 Subject: [PATCH 12/13] Lock the cart on the currentcartid as well --- .../magento-cart-payment-method/hooks/useCartLock.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/magento-cart-payment-method/hooks/useCartLock.ts b/packages/magento-cart-payment-method/hooks/useCartLock.ts index d687ce145f..38e666644f 100644 --- a/packages/magento-cart-payment-method/hooks/useCartLock.ts +++ b/packages/magento-cart-payment-method/hooks/useCartLock.ts @@ -1,4 +1,5 @@ -import { useCurrentCartId } from '@graphcommerce/magento-cart' +import { useApolloClient } from '@graphcommerce/graphql' +import { cartLock, useCurrentCartId } from '@graphcommerce/magento-cart' import { useUrlQuery } from '@graphcommerce/next-ui' import { useEffect, useState } from 'react' @@ -19,9 +20,10 @@ let justLocked = false * Todo: Block all operations on the cart while the cart is being blocked. */ export function useCartLock() { - const { currentCartId } = useCurrentCartId() + const { currentCartId, locked } = useCurrentCartId() const [queryState, setRouterQuery] = useUrlQuery() const [, setForceRender] = useState(0) + const client = useApolloClient() useEffect(() => { const pageshow = (e: PageTransitionEvent) => { @@ -38,6 +40,7 @@ export function useCartLock() { const lock = (params: Omit) => { if (!currentCartId) return undefined justLocked = true + cartLock(client.cache, true) return setRouterQuery({ locked: '1', cart_id: currentCartId, @@ -46,13 +49,14 @@ export function useCartLock() { } const unlock = async (params: Omit) => { + cartLock(client.cache, false) await setRouterQuery({ cart_id: null, locked: null, method: null, ...params } as E) return queryState } const resulting: Omit & { locked: boolean; justLocked: boolean } = { ...queryState, - locked: queryState.locked === '1' || Boolean(queryState.PayerID), + locked: locked || queryState.locked === '1' || Boolean(queryState.PayerID), justLocked, } From 5a6262366a99406242b75800f289d7790499d02e Mon Sep 17 00:00:00 2001 From: Paul Hachmang Date: Mon, 19 Feb 2024 12:14:31 +0100 Subject: [PATCH 13/13] Cleanup --- packages/magento-cart/hooks/useCartQuery.ts | 3 +-- .../magento-cart/plugins/useSignInFormMergeCart.ts | 11 ++++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/magento-cart/hooks/useCartQuery.ts b/packages/magento-cart/hooks/useCartQuery.ts index c2a25c4424..b62530fc68 100644 --- a/packages/magento-cart/hooks/useCartQuery.ts +++ b/packages/magento-cart/hooks/useCartQuery.ts @@ -29,8 +29,7 @@ export function useCartQuery = (useSignInFor cartLock(client.cache, true) - const customerCart = await client.query({ - query: CustomerCartDocument, - fetchPolicy: 'network-only', - }) - const destinationCartId = customerCart.data.customerCart.id + const destinationCartId = ( + await client.query({ + query: CustomerCartDocument, + fetchPolicy: 'network-only', + }) + ).data.customerCart.id try { const sourceCartId = readCartId(client.cache)?.id