@graphcommerce/magento-cart 4.5.1 → 4.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,54 @@
1
1
  # Change Log
2
2
 
3
+ ## 4.6.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1557](https://github.com/graphcommerce-org/graphcommerce/pull/1557) [`2ce406727`](https://github.com/graphcommerce-org/graphcommerce/commit/2ce406727c01a3367cea26c331d8455748592ce9) Thanks [@paales](https://github.com/paales)! - Solves hydration warning for the CartFab when products are in the cart
8
+
9
+ - Updated dependencies [[`01f1588c9`](https://github.com/graphcommerce-org/graphcommerce/commit/01f1588c9200bb39dd61146e260bfa2b32060612), [`84428ccab`](https://github.com/graphcommerce-org/graphcommerce/commit/84428ccab8d1d263893766197076651eae68759c), [`c0a7f9427`](https://github.com/graphcommerce-org/graphcommerce/commit/c0a7f9427466f0a3886b2c3ebf2f0aa5d79ee081)]:
10
+ - @graphcommerce/graphql@3.4.3
11
+ - @graphcommerce/magento-customer@4.8.1
12
+ - @graphcommerce/ecommerce-ui@1.1.4
13
+ - @graphcommerce/magento-store@4.2.19
14
+ - @graphcommerce/magento-graphql@3.1.3
15
+
16
+ ## 4.6.0
17
+
18
+ ### Minor Changes
19
+
20
+ - [#1553](https://github.com/graphcommerce-org/graphcommerce/pull/1553) [`03d01c06c`](https://github.com/graphcommerce-org/graphcommerce/commit/03d01c06c6dc13df8d38ab5b40bd100c567a9e8d) Thanks [@NickdeK](https://github.com/NickdeK)! - Added a `{ hydrate: boolean }` option to `useCartQuery`, `useCurrentCartId`, `useCustomerQuery` and `useCustomerSession` to allow for data during the hydration phase. This can cause hydration warnings, but prevents an additional rerender.
21
+
22
+ ### Patch Changes
23
+
24
+ - [#1553](https://github.com/graphcommerce-org/graphcommerce/pull/1553) [`d03f0860b`](https://github.com/graphcommerce-org/graphcommerce/commit/d03f0860b882db4f280d9467aef9d66e56c1c030) Thanks [@NickdeK](https://github.com/NickdeK)! - Checkout address would incorrectly be null instead of an actual address
25
+
26
+ * [#1553](https://github.com/graphcommerce-org/graphcommerce/pull/1553) [`b68d0b44a`](https://github.com/graphcommerce-org/graphcommerce/commit/b68d0b44a87688c80fb0aa4a5c840f262ce48d2f) Thanks [@NickdeK](https://github.com/NickdeK)! - Simplify `useCartIdCreate` hook to reduce a potential rerender when not nessesary
27
+
28
+ * Updated dependencies [[`1afc6a547`](https://github.com/graphcommerce-org/graphcommerce/commit/1afc6a5473d6e31f47b5d0188801803b31865290), [`03d01c06c`](https://github.com/graphcommerce-org/graphcommerce/commit/03d01c06c6dc13df8d38ab5b40bd100c567a9e8d), [`4a4579bb2`](https://github.com/graphcommerce-org/graphcommerce/commit/4a4579bb2f7da378f3fcc504405caf2560dc10f6), [`afcd8e4bf`](https://github.com/graphcommerce-org/graphcommerce/commit/afcd8e4bfb7010da4d5faeed85b61991ed7975f4), [`02e1988e5`](https://github.com/graphcommerce-org/graphcommerce/commit/02e1988e5f361c6f66ae30d3bbee38ef2ac062df), [`323fdee4b`](https://github.com/graphcommerce-org/graphcommerce/commit/323fdee4b15ae23e0e84dd0588cb2c6446dcfd50)]:
29
+ - @graphcommerce/graphql@3.4.2
30
+ - @graphcommerce/magento-customer@4.8.0
31
+ - @graphcommerce/react-hook-form@3.3.1
32
+ - @graphcommerce/next-ui@4.14.0
33
+ - @graphcommerce/ecommerce-ui@1.1.3
34
+ - @graphcommerce/magento-graphql@3.1.2
35
+ - @graphcommerce/magento-store@4.2.18
36
+ - @graphcommerce/framer-scroller@2.1.24
37
+
38
+ ## 4.5.2
39
+
40
+ ### Patch Changes
41
+
42
+ - Updated dependencies [[`18054c441`](https://github.com/graphcommerce-org/graphcommerce/commit/18054c441962ba750bed3acc39ab46c8d3a341ce), [`c5c539c44`](https://github.com/graphcommerce-org/graphcommerce/commit/c5c539c44eeac524cd62ce649e132d2e00333794), [`6f69bc54c`](https://github.com/graphcommerce-org/graphcommerce/commit/6f69bc54c6e0224452817c532ae58d9c332b61ea), [`21886d6fa`](https://github.com/graphcommerce-org/graphcommerce/commit/21886d6fa64a48d9e932bfaf8d138c9b13c36e43), [`b4936e961`](https://github.com/graphcommerce-org/graphcommerce/commit/b4936e96175fe80717895822e245274db05638bd)]:
43
+ - @graphcommerce/framer-next-pages@3.2.4
44
+ - @graphcommerce/graphql@3.4.1
45
+ - @graphcommerce/magento-customer@4.7.2
46
+ - @graphcommerce/next-ui@4.13.1
47
+ - @graphcommerce/framer-scroller@2.1.23
48
+ - @graphcommerce/ecommerce-ui@1.1.2
49
+ - @graphcommerce/magento-graphql@3.1.1
50
+ - @graphcommerce/magento-store@4.2.17
51
+
3
52
  ## 4.5.1
4
53
 
5
54
  ### Patch Changes
@@ -3,23 +3,39 @@ import {
3
3
  ApolloCustomerErrorFullPageProps,
4
4
  } from '@graphcommerce/magento-customer'
5
5
  import { graphqlErrorByCategory } from '@graphcommerce/magento-graphql'
6
- import { iconSadFace, IconSvg } from '@graphcommerce/next-ui'
6
+ import { iconShoppingBag, IconSvg } from '@graphcommerce/next-ui'
7
7
  import { Trans } from '@lingui/react'
8
8
  import { Button } from '@mui/material'
9
+ import { useCurrentCartId } from '../../hooks'
9
10
  import { useClearCurrentCartId } from '../../hooks/useClearCurrentCartId'
11
+ import { EmptyCart } from '../EmptyCart/EmptyCart'
10
12
 
11
13
  export type ApolloCartErrorFullPageProps = Omit<ApolloCustomerErrorFullPageProps, 'icon'>
12
14
 
13
15
  export function ApolloCartErrorFullPage(props: ApolloCartErrorFullPageProps) {
14
16
  const { error, button } = props
15
17
  const clear = useClearCurrentCartId()
18
+ const { currentCartId } = useCurrentCartId()
16
19
 
17
20
  const [, noSuchEntity] = graphqlErrorByCategory({ category: 'graphql-no-such-entity', error })
18
21
 
22
+ if (noSuchEntity)
23
+ return (
24
+ <EmptyCart
25
+ button={
26
+ currentCartId ? (
27
+ <Button onClick={clear}>
28
+ <Trans id='Reset Cart' />
29
+ </Button>
30
+ ) : undefined
31
+ }
32
+ />
33
+ )
34
+
19
35
  return (
20
36
  <ApolloCustomerErrorFullPage
21
37
  {...props}
22
- icon={<IconSvg src={iconSadFace} size='xxl' />}
38
+ icon={<IconSvg src={iconShoppingBag} size='xxl' />}
23
39
  button={
24
40
  noSuchEntity ? (
25
41
  <Button onClick={clear}>
@@ -1,3 +1,4 @@
1
+ import { WaitForQueries } from '@graphcommerce/ecommerce-ui'
1
2
  import {
2
3
  extendableComponent,
3
4
  iconShoppingBag,
@@ -69,13 +70,14 @@ function CartFabContent(props: CartFabContentProps) {
69
70
  })}
70
71
  {...fabProps}
71
72
  >
72
- {total_quantity > 0 ? (
73
- <DesktopHeaderBadge color='primary' variant='dot' overlap='circular'>
74
- {cartIcon}
75
- </DesktopHeaderBadge>
76
- ) : (
77
- cartIcon
78
- )}
73
+ <DesktopHeaderBadge
74
+ color='primary'
75
+ variant='dot'
76
+ overlap='circular'
77
+ badgeContent={total_quantity}
78
+ >
79
+ {cartIcon}
80
+ </DesktopHeaderBadge>
79
81
  </MotionFab>
80
82
  </PageLink>
81
83
  <MotionDiv
@@ -109,15 +111,14 @@ function CartFabContent(props: CartFabContentProps) {
109
111
  * product to the cart. This would mean that it would immediately start executing this query.
110
112
  */
111
113
  export function CartFab(props: CartFabProps) {
112
- const { data, loading, called } = useCartQuery(CartFabDocument, {
114
+ const cartQuery = useCartQuery(CartFabDocument, {
113
115
  fetchPolicy: 'cache-only',
114
116
  nextFetchPolicy: 'cache-first',
115
117
  })
116
- const qty = data?.cart?.total_quantity ?? 0
117
118
 
118
- if (loading || !called) {
119
- return <CartFabContent {...props} total_quantity={0} />
120
- }
121
-
122
- return <CartFabContent total_quantity={qty} {...props} />
119
+ return (
120
+ <WaitForQueries waitFor={cartQuery} fallback={<CartFabContent {...props} total_quantity={0} />}>
121
+ <CartFabContent total_quantity={cartQuery.data?.cart?.total_quantity ?? 0} {...props} />
122
+ </WaitForQueries>
123
+ )
123
124
  }
@@ -1,23 +1,30 @@
1
- import { FullPageMessage, IconSvg, iconShoppingBag } from '@graphcommerce/next-ui'
1
+ import {
2
+ FullPageMessage,
3
+ IconSvg,
4
+ iconShoppingBag,
5
+ FullPageMessageProps,
6
+ } from '@graphcommerce/next-ui'
2
7
  import { Trans } from '@lingui/react'
3
8
  import { Button } from '@mui/material'
4
9
  import Link from 'next/link'
5
10
  import React from 'react'
6
11
 
7
- type EmptyCartProps = { children?: React.ReactNode }
12
+ type EmptyCartProps = { children?: React.ReactNode } & Pick<FullPageMessageProps, 'button'>
8
13
  export function EmptyCart(props: EmptyCartProps) {
9
- const { children } = props
14
+ const { children, button } = props
10
15
 
11
16
  return (
12
17
  <FullPageMessage
13
18
  title={<Trans id='Your cart is empty' />}
14
19
  icon={<IconSvg src={iconShoppingBag} size='xxl' />}
15
20
  button={
16
- <Link href='/' passHref>
17
- <Button variant='pill' color='secondary' size='large'>
18
- <Trans id='Continue shopping' />
19
- </Button>
20
- </Link>
21
+ button || (
22
+ <Link href='/' passHref>
23
+ <Button variant='pill' color='secondary' size='large'>
24
+ <Trans id='Continue shopping' />
25
+ </Button>
26
+ </Link>
27
+ )
21
28
  }
22
29
  >
23
30
  {children ?? <Trans id='Discover our collection and add items to your cart!' />}
@@ -1,17 +1,25 @@
1
- import { useApolloClient } from '@graphcommerce/graphql'
1
+ import { ApolloCache, useApolloClient } from '@graphcommerce/graphql'
2
+ import { cookie } from '@graphcommerce/next-ui'
2
3
  import { useCallback } from 'react'
3
4
  import { CurrentCartIdDocument } from './CurrentCartId.gql'
4
5
 
6
+ export const CART_ID_COOKIE = 'cart'
7
+
8
+ export function writeCartId(cache: ApolloCache<object>, id: string | null = null) {
9
+ cache.writeQuery({
10
+ query: CurrentCartIdDocument,
11
+ data: { currentCartId: { __typename: 'CurrentCartId', id } },
12
+ broadcast: true,
13
+ })
14
+ }
15
+
5
16
  export function useAssignCurrentCartId() {
6
17
  const { cache } = useApolloClient()
7
18
 
8
19
  return useCallback(
9
20
  (id: string) => {
10
- cache.writeQuery({
11
- query: CurrentCartIdDocument,
12
- data: { currentCartId: { __typename: 'CurrentCartId', id } },
13
- broadcast: true,
14
- })
21
+ writeCartId(cache, id)
22
+ cookie(CART_ID_COOKIE, id)
15
23
  },
16
24
  [cache],
17
25
  )
@@ -1,18 +1,20 @@
1
- import { useMutation } from '@graphcommerce/graphql'
1
+ import { useApolloClient } from '@graphcommerce/graphql'
2
2
  import { i18n } from '@lingui/core'
3
3
  import { CreateEmptyCartDocument } from './CreateEmptyCart.gql'
4
+ import { CurrentCartIdDocument } from './CurrentCartId.gql'
4
5
  import { useAssignCurrentCartId } from './useAssignCurrentCartId'
5
- import { useCurrentCartId } from './useCurrentCartId'
6
6
 
7
7
  export function useCartIdCreate() {
8
- const { currentCartId } = useCurrentCartId()
9
- const [create] = useMutation(CreateEmptyCartDocument)
8
+ const client = useApolloClient()
10
9
  const assignCurrentCartId = useAssignCurrentCartId()
11
10
 
12
11
  return async (): Promise<string> => {
12
+ const currentCartId = client.cache.readQuery({ query: CurrentCartIdDocument })?.currentCartId
13
+ ?.id
14
+
13
15
  if (currentCartId) return currentCartId
14
16
 
15
- const { data } = await create()
17
+ const { data } = await client.mutate({ mutation: CreateEmptyCartDocument })
16
18
  if (!data?.createEmptyCart) throw Error(i18n._(/* i18n */ 'Could not create an empty cart'))
17
19
 
18
20
  // We store the cartId that is returned as the currentCartId result
@@ -1,8 +1,16 @@
1
- import { useQuery, TypedDocumentNode, QueryHookOptions } from '@graphcommerce/graphql'
1
+ import { useQuery, TypedDocumentNode, QueryHookOptions, ApolloError } from '@graphcommerce/graphql'
2
+ import { GraphQLError } from 'graphql'
2
3
  import { useRouter } from 'next/router'
3
- import { useEffect, useState } from 'react'
4
4
  import { useCurrentCartId } from './useCurrentCartId'
5
5
 
6
+ const noCartError = new ApolloError({
7
+ graphQLErrors: [
8
+ new GraphQLError('No cart found', {
9
+ extensions: { category: 'graphql-no-such-entity' },
10
+ }),
11
+ ],
12
+ })
13
+
6
14
  /**
7
15
  * Requires the query to have a `$cartId: String!` argument. It will automatically inject the
8
16
  * currently active cart_id.
@@ -15,11 +23,15 @@ import { useCurrentCartId } from './useCurrentCartId'
15
23
  */
16
24
  export function useCartQuery<Q, V extends { cartId: string; [index: string]: unknown }>(
17
25
  document: TypedDocumentNode<Q, V>,
18
- options: QueryHookOptions<Q, Omit<V, 'cartId'>> & { allowUrl?: boolean } = {},
26
+ options: QueryHookOptions<Q, Omit<V, 'cartId'>> & {
27
+ allowUrl?: boolean
28
+ hydration?: boolean
29
+ } = {},
19
30
  ) {
20
- const { allowUrl = true, ...queryOptions } = options
31
+ const { allowUrl = true, hydration, ...queryOptions } = options
21
32
  const router = useRouter()
22
- const { currentCartId } = useCurrentCartId()
33
+ const { currentCartId, called } = useCurrentCartId({ hydration })
34
+
23
35
  const urlCartId = router.query.cart_id
24
36
  const usingUrl = allowUrl && typeof urlCartId === 'string'
25
37
  const cartId = usingUrl ? urlCartId : currentCartId
@@ -32,7 +44,13 @@ export function useCartQuery<Q, V extends { cartId: string; [index: string]: unk
32
44
 
33
45
  queryOptions.variables = { cartId, ...options?.variables } as V
34
46
  queryOptions.skip = queryOptions?.skip || !cartId
35
- queryOptions.ssr = false
47
+ queryOptions.ssr = !!hydration
48
+
49
+ const result = useQuery(document, queryOptions)
36
50
 
37
- return useQuery(document, queryOptions)
51
+ return {
52
+ ...useQuery(document, queryOptions),
53
+ ...result,
54
+ error: called && !currentCartId ? noCartError : result.error,
55
+ }
38
56
  }
@@ -1,5 +1,7 @@
1
1
  import { useApolloClient } from '@graphcommerce/graphql'
2
+ import { cookie } from '@graphcommerce/next-ui'
2
3
  import { CurrentCartIdDocument } from './CurrentCartId.gql'
4
+ import { CART_ID_COOKIE } from './useAssignCurrentCartId'
3
5
 
4
6
  export function useClearCurrentCartId() {
5
7
  const { cache } = useApolloClient()
@@ -13,5 +15,6 @@ export function useClearCurrentCartId() {
13
15
  data: { currentCartId: { __typename: 'CurrentCartId', id: null } },
14
16
  broadcast: true,
15
17
  })
18
+ cookie(CART_ID_COOKIE, null)
16
19
  }
17
20
  }
@@ -2,22 +2,24 @@ import { useIsomorphicLayoutEffect } from '@graphcommerce/framer-utils'
2
2
  import { QueryHookOptions, useQuery } from '@graphcommerce/graphql'
3
3
  import { useState } from 'react'
4
4
  import { CurrentCartIdDocument, CurrentCartIdQuery } from './CurrentCartId.gql'
5
+ import {} from 'react-dom'
5
6
 
6
- export function useCurrentCartId<Q, V>(
7
- options: QueryHookOptions<Q & Pick<CurrentCartIdQuery, 'currentCartId'>, Omit<V, 'skip'>> = {},
8
- ) {
9
- const [skip, setSkip] = useState(true)
10
- const { data, ...queryResults } = useQuery(CurrentCartIdDocument, {
11
- ...options,
12
- skip,
13
- })
7
+ type UseCurrentCartIdOptions<Q, V> = QueryHookOptions<
8
+ Q & Pick<CurrentCartIdQuery, 'currentCartId'>,
9
+ V
10
+ > & { hydration?: boolean }
14
11
 
15
- useIsomorphicLayoutEffect(() => {
16
- if (skip) setSkip(false)
17
- }, [skip])
12
+ export function useCurrentCartId<Q, V>(options: UseCurrentCartIdOptions<Q, V> = {}) {
13
+ const { hydration = true, ...queryOptions } = options
14
+ const [hydrating, setHydrating] = useState(!hydration)
15
+ useIsomorphicLayoutEffect(() => setHydrating(false), [])
16
+ const skip = options.skip !== undefined ? options.skip : hydrating
17
+
18
+ const { data, ...queryResults } = useQuery(CurrentCartIdDocument, { ...queryOptions, skip })
18
19
 
19
20
  return {
20
21
  currentCartId: data?.currentCartId?.id || '',
22
+ data,
21
23
  ...queryResults,
22
24
  }
23
25
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@graphcommerce/magento-cart",
3
3
  "homepage": "https://www.graphcommerce.org/",
4
4
  "repository": "github:graphcommerce-org/graphcommerce",
5
- "version": "4.5.1",
5
+ "version": "4.6.1",
6
6
  "sideEffects": false,
7
7
  "prettier": "@graphcommerce/prettier-config-pwa",
8
8
  "eslintConfig": {
@@ -12,23 +12,23 @@
12
12
  }
13
13
  },
14
14
  "devDependencies": {
15
- "@graphcommerce/eslint-config-pwa": "^4.1.8",
15
+ "@graphcommerce/eslint-config-pwa": "^4.1.9",
16
16
  "@graphcommerce/prettier-config-pwa": "^4.0.6",
17
- "@graphcommerce/typescript-config-pwa": "^4.0.3",
17
+ "@graphcommerce/typescript-config-pwa": "^4.0.4",
18
18
  "@playwright/test": "^1.21.1"
19
19
  },
20
20
  "dependencies": {
21
- "@graphcommerce/ecommerce-ui": "1.1.1",
21
+ "@graphcommerce/ecommerce-ui": "1.1.4",
22
22
  "@graphcommerce/framer-utils": "3.1.4",
23
- "@graphcommerce/framer-next-pages": "3.2.3",
24
- "@graphcommerce/framer-scroller": "2.1.22",
25
- "@graphcommerce/graphql": "3.4.0",
23
+ "@graphcommerce/framer-next-pages": "3.2.4",
24
+ "@graphcommerce/framer-scroller": "2.1.24",
25
+ "@graphcommerce/graphql": "3.4.3",
26
26
  "@graphcommerce/image": "3.1.7",
27
- "@graphcommerce/magento-customer": "4.7.1",
28
- "@graphcommerce/magento-graphql": "3.1.0",
29
- "@graphcommerce/magento-store": "4.2.16",
30
- "@graphcommerce/next-ui": "4.13.0",
31
- "@graphcommerce/react-hook-form": "3.3.0"
27
+ "@graphcommerce/magento-customer": "4.8.1",
28
+ "@graphcommerce/magento-graphql": "3.1.3",
29
+ "@graphcommerce/magento-store": "4.2.19",
30
+ "@graphcommerce/next-ui": "4.14.0",
31
+ "@graphcommerce/react-hook-form": "3.3.1"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "@lingui/react": "^3.13.2",
package/typePolicies.ts CHANGED
@@ -16,7 +16,10 @@ export const cartTypePolicies: StrictTypedTypePolicies = {
16
16
  existing: ShippingCartAddress[] | undefined,
17
17
  incoming: ShippingCartAddress[],
18
18
  options,
19
- ) => [options.mergeObjects(existing?.[0] ?? {}, incoming[0])],
19
+ ) => {
20
+ const merged = options.mergeObjects(existing?.[0] ?? {}, incoming[0])
21
+ return merged ? [merged] : []
22
+ },
20
23
  },
21
24
  prices: {
22
25
  merge: (existing: CartPrices[] | undefined, incoming: CartPrices[], options) =>