@graphcommerce/magento-cart 9.0.0-canary.81 → 9.0.0-canary.82

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,11 @@
1
1
  # Change Log
2
2
 
3
+ ## 9.0.0-canary.82
4
+
5
+ ### Minor Changes
6
+
7
+ - [#2366](https://github.com/graphcommerce-org/graphcommerce/pull/2366) [`3612c99`](https://github.com/graphcommerce-org/graphcommerce/commit/3612c994b80bb3b1bc02de10668f69a332402dc4) - Add `permissions` config so the website or store can be configurated to run in different modes. ([@Giovanni-Schroevers](https://github.com/Giovanni-Schroevers))
8
+
3
9
  ## 9.0.0-canary.81
4
10
 
5
11
  ## 9.0.0-canary.80
package/Config.graphqls CHANGED
@@ -1,3 +1,20 @@
1
+ enum CartPermissions {
2
+ ENABLED
3
+ CUSTOMER_ONLY
4
+ DISABLED
5
+ }
6
+
7
+ extend input GraphCommercePermissions {
8
+ """
9
+ Changes the availability of the add to cart buttons and the cart page to either customer only or completely disables it.
10
+ """
11
+ cart: CartPermissions
12
+ """
13
+ Changes the availability of the checkout to either customer only or completely disables it.
14
+ """
15
+ checkout: CartPermissions
16
+ }
17
+
1
18
  extend input GraphCommerceStorefrontConfig {
2
19
  """
3
20
  Due to a limitation of the GraphQL API it is not possible to determine if a cart should be displayed including or excluding tax.
@@ -1,55 +1,10 @@
1
- import { useQuery } from '@graphcommerce/graphql'
2
1
  import {
3
2
  ApolloCustomerErrorAlert,
4
3
  ApolloCustomerErrorAlertProps,
5
- CustomerDocument,
6
4
  } from '@graphcommerce/magento-customer'
7
- import { graphqlErrorByCategory } from '@graphcommerce/magento-graphql'
8
- import { i18n } from '@lingui/core'
9
- import { Trans } from '@lingui/react'
10
- import { Button } from '@mui/material'
11
- import { useClearCurrentCartId } from '../../hooks'
12
5
 
13
6
  export type ApolloCartErrorAlertProps = ApolloCustomerErrorAlertProps
14
7
 
15
8
  export function ApolloCartErrorAlert(props: ApolloCartErrorAlertProps) {
16
- const { error, graphqlErrorAlertProps } = props
17
-
18
- const email = useQuery(CustomerDocument, { fetchPolicy: 'cache-only' }).data?.customer?.email
19
-
20
- const [newError, unauthorized] = graphqlErrorByCategory({
21
- category: 'graphql-authorization',
22
- error,
23
- mask: email
24
- ? i18n._(
25
- /* i18n */ 'This cart is assigned to {email}. Please sign in to continue shopping.',
26
- { email },
27
- )
28
- : undefined,
29
- extract: false,
30
- })
31
-
32
- const clear = useClearCurrentCartId()
33
-
34
- return (
35
- <ApolloCustomerErrorAlert
36
- {...props}
37
- error={newError}
38
- graphqlErrorAlertProps={{
39
- action: unauthorized ? (
40
- <>
41
- {graphqlErrorAlertProps?.action}
42
- <Button onClick={clear} color='error' size='small'>
43
- <Trans id='Sign out' />
44
- </Button>
45
- <Button href='/account/signin' color='error' size='small'>
46
- <Trans id='Sign in' />
47
- </Button>
48
- </>
49
- ) : (
50
- graphqlErrorAlertProps?.action
51
- ),
52
- }}
53
- />
54
- )
9
+ return <ApolloCustomerErrorAlert {...props} />
55
10
  }
@@ -11,6 +11,7 @@ import { i18n } from '@lingui/core'
11
11
  import { alpha, Fab, FabProps, styled, useTheme, Box, SxProps, Theme } from '@mui/material'
12
12
  import { m, useTransform } from 'framer-motion'
13
13
  import React from 'react'
14
+ import { useCartEnabled, useCartShouldLoginToContinue } from '../../hooks'
14
15
  import { useCartQuery } from '../../hooks/useCartQuery'
15
16
  import { CartFabDocument } from './CartFab.gql'
16
17
  import { CartTotalQuantityFragment } from './CartTotalQuantity.gql'
@@ -100,7 +101,12 @@ function CartFabContent(props: CartFabContentProps) {
100
101
  }
101
102
 
102
103
  export function CartFab(props: CartFabProps) {
103
- const cartQuery = useCartQuery(CartFabDocument)
104
+ const cartEnabled = useCartEnabled()
105
+ const shouldLoginToContinue = useCartShouldLoginToContinue()
106
+ const cartQuery = useCartQuery(CartFabDocument, {
107
+ skip: shouldLoginToContinue,
108
+ })
109
+ if (!cartEnabled) return null
104
110
 
105
111
  return (
106
112
  <WaitForQueries waitFor={cartQuery} fallback={<CartFabContent {...props} total_quantity={0} />}>
@@ -1,8 +1,9 @@
1
1
  import { Money } from '@graphcommerce/magento-store'
2
2
  import { iconChevronRight, IconSvg, extendableComponent } from '@graphcommerce/next-ui'
3
- import { Trans } from '@lingui/react'
4
- import { Box, Button, ButtonProps, SxProps, Theme } from '@mui/material'
3
+ import { Trans } from '@lingui/macro'
4
+ import { Box, Button, ButtonProps, Link, SxProps, Theme } from '@mui/material'
5
5
  import React from 'react'
6
+ import { useCheckoutShouldLoginToContinue } from '../../hooks'
6
7
  import { CartStartCheckoutFragment } from './CartStartCheckout.gql'
7
8
 
8
9
  export type CartStartCheckoutProps = {
@@ -17,12 +18,13 @@ export type CartStartCheckoutProps = {
17
18
  ) => void
18
19
  }
19
20
 
20
- const name = 'CartStartCheckout' as const
21
+ const name = 'CartStartCheckout'
21
22
  const parts = [
22
23
  'checkoutButtonContainer',
23
24
  'checkoutButton',
24
25
  'checkoutButtonTotal',
25
26
  'checkoutMoney',
27
+ 'loginContainer',
26
28
  ] as const
27
29
  const { classes } = extendableComponent(name, parts)
28
30
 
@@ -36,6 +38,7 @@ export function CartStartCheckout(props: CartStartCheckoutProps) {
36
38
  cart,
37
39
  } = props
38
40
 
41
+ const shouldLoginToContinue = useCheckoutShouldLoginToContinue()
39
42
  const hasTotals = (cart?.prices?.grand_total?.value ?? 0) > 0
40
43
  const hasErrors = cart?.items?.some((item) => (item?.errors?.length ?? 0) > 0)
41
44
 
@@ -43,10 +46,21 @@ export function CartStartCheckout(props: CartStartCheckoutProps) {
43
46
  <Box
44
47
  className={classes.checkoutButtonContainer}
45
48
  sx={[
46
- (theme) => ({ textAlign: 'center', my: theme.spacings.md }),
49
+ (theme) => ({
50
+ textAlign: 'center',
51
+ my: theme.spacings.md,
52
+ }),
47
53
  ...(Array.isArray(sx) ? sx : [sx]),
48
54
  ]}
49
55
  >
56
+ {shouldLoginToContinue && (
57
+ <Box sx={{ mb: 1 }} className={classes.loginContainer}>
58
+ <Link href='/account/signin'>
59
+ <Trans>You must first login before you can continue</Trans>
60
+ </Link>
61
+ </Box>
62
+ )}
63
+
50
64
  <Button
51
65
  href='/checkout'
52
66
  id='cart-start-checkout'
@@ -60,7 +74,7 @@ export function CartStartCheckout(props: CartStartCheckoutProps) {
60
74
  onStart?.(e, cart)
61
75
  return onClick?.(e)
62
76
  }}
63
- disabled={disabled || !hasTotals || hasErrors}
77
+ disabled={disabled || !hasTotals || hasErrors || shouldLoginToContinue}
64
78
  {...buttonProps}
65
79
  >
66
80
  <Box
@@ -71,7 +85,7 @@ export function CartStartCheckout(props: CartStartCheckoutProps) {
71
85
  '& ~ span.MuiButton-endIcon': { marginLeft: '6px' },
72
86
  })}
73
87
  >
74
- <Trans id='Start Checkout' />
88
+ <Trans>Start Checkout</Trans>
75
89
  </Box>{' '}
76
90
  {hasTotals && (
77
91
  <span className={classes.checkoutMoney}>
@@ -84,7 +98,9 @@ export function CartStartCheckout(props: CartStartCheckoutProps) {
84
98
 
85
99
  {hasErrors && (
86
100
  <Box sx={(theme) => ({ color: 'error.main', mt: theme.spacings.xs })}>
87
- <Trans id='Some items in your cart contain errors, please update or remove them, then try again.' />
101
+ <Trans>
102
+ Some items in your cart contain errors, please update or remove them, then try again.
103
+ </Trans>
88
104
  </Box>
89
105
  )}
90
106
  </Box>
@@ -2,6 +2,7 @@ import { iconChevronRight, IconSvg, LinkOrButton, LinkOrButtonProps } from '@gra
2
2
  import { Trans } from '@lingui/react'
3
3
  import { SxProps, Theme } from '@mui/material'
4
4
  import React from 'react'
5
+ import { useCheckoutShouldLoginToContinue } from '../../hooks'
5
6
  import { CartStartCheckoutFragment } from './CartStartCheckout.gql'
6
7
 
7
8
  export type CartStartCheckoutLinkOrButtonProps = {
@@ -18,13 +19,14 @@ export type CartStartCheckoutLinkOrButtonProps = {
18
19
 
19
20
  export function CartStartCheckoutLinkOrButton(props: CartStartCheckoutLinkOrButtonProps) {
20
21
  const {
21
- children,
22
22
  onStart,
23
23
  disabled,
24
24
  linkOrButtonProps: { onClick, button, ...linkOrButtonProps } = {},
25
25
  cart,
26
26
  } = props
27
27
 
28
+ const shouldLoginToContinue = useCheckoutShouldLoginToContinue()
29
+
28
30
  const hasTotals = (cart?.prices?.grand_total?.value ?? 0) > 0
29
31
  const hasErrors = cart?.items?.some((item) => (item?.errors?.length ?? 0) > 0)
30
32
 
@@ -38,7 +40,7 @@ export function CartStartCheckoutLinkOrButton(props: CartStartCheckoutLinkOrButt
38
40
  onStart?.(e, cart)
39
41
  }}
40
42
  button={{ variant: 'pill', ...button }}
41
- disabled={disabled || !hasTotals || hasErrors}
43
+ disabled={disabled || !hasTotals || hasErrors || shouldLoginToContinue}
42
44
  color='secondary'
43
45
  endIcon={<IconSvg src={iconChevronRight} />}
44
46
  {...linkOrButtonProps}
@@ -3,6 +3,7 @@ import {
3
3
  IsEmailAvailableDocument,
4
4
  useCustomerSession,
5
5
  useGuestQuery,
6
+ useCustomerAccountCanSignIn,
6
7
  } from '@graphcommerce/magento-customer'
7
8
  import { Button, FormRow, extendableComponent } from '@graphcommerce/next-ui'
8
9
  import { Trans } from '@lingui/react'
@@ -21,13 +22,15 @@ export type InlineAccountProps = {
21
22
  sx?: SxProps<Theme>
22
23
  }
23
24
 
24
- const name = 'InlineAccount' as const
25
+ const name = 'InlineAccount'
25
26
  const parts = ['root', 'innerContainer', 'form', 'button', 'title'] as const
26
27
  const { classes } = extendableComponent(name, parts)
27
28
 
28
29
  export function InlineAccount(props: InlineAccountProps) {
29
30
  const { title, description, sx = [] } = props
30
31
 
32
+ const canLogin = useCustomerAccountCanSignIn()
33
+
31
34
  const [toggled, setToggled] = useState<boolean>(false)
32
35
 
33
36
  const { loading, data } = useCartQuery(InlineAccountDocument)
@@ -42,7 +45,7 @@ export function InlineAccount(props: InlineAccountProps) {
42
45
  const { firstname, lastname } = cart?.shipping_addresses?.[0] ?? {}
43
46
  const canSignUp = isEmailAvailableData?.isEmailAvailable?.is_email_available === true
44
47
 
45
- if (loggedIn || !canSignUp) return null
48
+ if (loggedIn || !canSignUp || !canLogin) return null
46
49
 
47
50
  return (
48
51
  <div>
package/hooks/index.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  export * from './useAssignCurrentCartId'
2
- export * from './useCurrentCartId'
2
+ export * from './useCartIdCreate'
3
+ export * from './useCartPermissions'
3
4
  export * from './useCartQuery'
5
+ export * from './useCheckoutPermissions'
4
6
  export * from './useClearCurrentCartId'
5
- export * from './useCartIdCreate'
7
+ export * from './useCurrentCartId'
8
+ export * from './useDisplayInclTax'
6
9
  export * from './useFormGqlMutationCart'
7
10
  export * from './useMergeCustomerCart'
8
- export * from './useDisplayInclTax'
@@ -0,0 +1,21 @@
1
+ import { useCustomerSession } from '@graphcommerce/magento-customer/hooks/useCustomerSession'
2
+ import { useStorefrontConfig } from '@graphcommerce/next-ui'
3
+
4
+ function useCartPermissions() {
5
+ return (
6
+ useStorefrontConfig().permissions?.cart ??
7
+ import.meta.graphCommerce.permissions?.cart ??
8
+ 'ENABLED'
9
+ )
10
+ }
11
+
12
+ export function useCartEnabled() {
13
+ return useCartPermissions() !== 'DISABLED'
14
+ }
15
+
16
+ export function useCartShouldLoginToContinue() {
17
+ const { loggedIn } = useCustomerSession()
18
+ const permission = useCartPermissions()
19
+ if (permission === 'ENABLED') return false
20
+ return !loggedIn
21
+ }
@@ -1,5 +1,7 @@
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'
4
+ import { useCartShouldLoginToContinue } from './useCartPermissions'
3
5
  import { useCurrentCartId } from './useCurrentCartId'
4
6
 
5
7
  /**
@@ -22,12 +24,14 @@ export function useCartQuery<Q, V extends { cartId: string; [index: string]: unk
22
24
  } = {},
23
25
  ) {
24
26
  const { allowUrl, ...queryOptions } = options
27
+
25
28
  const router = useRouter()
26
29
  const { currentCartId, locked } = useCurrentCartId()
27
30
 
28
31
  const urlCartId = router.query.cart_id
29
32
  const usingUrl = typeof urlCartId === 'string'
30
33
  const cartId = usingUrl ? urlCartId : currentCartId
34
+ const shouldLoginToContinue = useCartShouldLoginToContinue()
31
35
 
32
36
  if (usingUrl || locked) queryOptions.fetchPolicy = 'cache-only'
33
37
 
@@ -35,7 +39,24 @@ export function useCartQuery<Q, V extends { cartId: string; [index: string]: unk
35
39
  queryOptions.returnPartialData = true
36
40
 
37
41
  queryOptions.variables = { cartId, ...options?.variables } as V
38
- queryOptions.skip = queryOptions?.skip || !cartId
39
42
 
40
- return useQuery(document, queryOptions as QueryHookOptions<Q, V>)
43
+ const query = useQuery(document, {
44
+ ...(queryOptions as QueryHookOptions<Q, V>),
45
+ skip: queryOptions.skip || !cartId || shouldLoginToContinue,
46
+ })
47
+
48
+ if (shouldLoginToContinue && !queryOptions?.skip) {
49
+ return {
50
+ ...query,
51
+ error: new ApolloError({
52
+ graphQLErrors: [
53
+ new GraphQLError('Action can not be performed by the current user', {
54
+ extensions: { category: 'graphql-authorization' },
55
+ }),
56
+ ],
57
+ }),
58
+ }
59
+ }
60
+
61
+ return query
41
62
  }
@@ -0,0 +1,21 @@
1
+ import { useCustomerSession } from '@graphcommerce/magento-customer/hooks/useCustomerSession'
2
+ import { useStorefrontConfig } from '@graphcommerce/next-ui'
3
+
4
+ function useCheckoutPermission() {
5
+ return (
6
+ useStorefrontConfig().permissions?.checkout ??
7
+ import.meta.graphCommerce.permissions?.checkout ??
8
+ 'ENABLED'
9
+ )
10
+ }
11
+
12
+ export function useCheckoutGuestEnabled() {
13
+ return useCheckoutPermission() === 'ENABLED'
14
+ }
15
+
16
+ export function useCheckoutShouldLoginToContinue() {
17
+ const { loggedIn } = useCustomerSession()
18
+ const permission = useCheckoutPermission()
19
+ if (permission === 'ENABLED') return false
20
+ return !loggedIn
21
+ }
@@ -1,11 +1,19 @@
1
- import { MutationHookOptions, TypedDocumentNode, useApolloClient } from '@graphcommerce/graphql'
1
+ import {
2
+ ApolloError,
3
+ MutationHookOptions,
4
+ TypedDocumentNode,
5
+ useApolloClient,
6
+ } from '@graphcommerce/graphql'
2
7
  import {
3
8
  useFormGqlMutation,
4
9
  UseFormGqlMutationReturn,
5
10
  UseFormGraphQlOptions,
6
11
  } from '@graphcommerce/react-hook-form'
12
+ import { GraphQLError, Kind } from 'graphql'
13
+ import { isProtectedCartOperation } from '../link/isProtectedCartOperation'
7
14
  import { CurrentCartIdDocument } from './CurrentCartId.gql'
8
15
  import { useCartIdCreate } from './useCartIdCreate'
16
+ import { useCartShouldLoginToContinue } from './useCartPermissions'
9
17
 
10
18
  export function useFormGqlMutationCart<
11
19
  Q extends Record<string, unknown>,
@@ -17,18 +25,30 @@ export function useFormGqlMutationCart<
17
25
  ): UseFormGqlMutationReturn<Q, V> {
18
26
  const cartId = useCartIdCreate()
19
27
  const client = useApolloClient()
28
+ const shouldLoginToContinue = useCartShouldLoginToContinue()
29
+
30
+ let shouldBlockOperation = false
31
+ document.definitions.forEach((defenition) => {
32
+ if (defenition.kind === Kind.OPERATION_DEFINITION) {
33
+ shouldBlockOperation = !isProtectedCartOperation(defenition.name?.value ?? '')
34
+ }
35
+ })
20
36
 
21
37
  const result = useFormGqlMutation<Q, V>(
22
38
  document,
23
39
  {
24
40
  ...options,
25
41
  onBeforeSubmit: async (variables) => {
42
+ if (shouldLoginToContinue && shouldBlockOperation) {
43
+ return false
44
+ }
26
45
  const vars = { ...variables, cartId: await cartId() }
27
46
 
28
47
  const res = client.cache.readQuery({ query: CurrentCartIdDocument })
29
48
  if (!options.submitWhileLocked && res?.currentCartId?.locked) {
30
- console.log('Could not submit form, cart is locked', res.currentCartId.locked)
31
- return false
49
+ throw Error('Could not submit form, cart is locked')
50
+ // console.log('Could not submit form, cart is locked', res.currentCartId.locked)
51
+ // return false
32
52
  }
33
53
 
34
54
  return options.onBeforeSubmit ? options.onBeforeSubmit(vars) : vars
@@ -37,5 +57,18 @@ export function useFormGqlMutationCart<
37
57
  { errorPolicy: 'all', ...operationOptions },
38
58
  )
39
59
 
60
+ if (shouldLoginToContinue && result.formState.isSubmitted && shouldBlockOperation) {
61
+ return {
62
+ ...result,
63
+ error: new ApolloError({
64
+ graphQLErrors: [
65
+ new GraphQLError('Action can not be performed by the current user', {
66
+ extensions: { category: 'graphql-authorization' },
67
+ }),
68
+ ],
69
+ }),
70
+ }
71
+ }
72
+
40
73
  return result
41
74
  }
package/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from './Api/CartItemCountChanged.gql'
2
- export * from './hooks'
3
2
  export * from './components'
4
- export * from './typePolicies'
5
- export * from './link/createCartErrorLink'
6
3
  export * from './components/CartDebugger/CartDebugger'
4
+ export * from './hooks'
5
+ export * from './link/cartLink'
6
+ export * from './typePolicies'
7
+ export * from './utils'
@@ -1,9 +1,14 @@
1
1
  import { fromPromise, globalApolloClient, Operation } from '@graphcommerce/graphql'
2
- import { onError } from '@graphcommerce/graphql/apollo'
2
+ import { ApolloLink, Observable, onError } from '@graphcommerce/graphql/apollo'
3
+ import { CustomerTokenDocument, getCustomerAccountCanSignIn } from '@graphcommerce/magento-customer'
4
+ import { PushRouter, pushWithPromise } from '@graphcommerce/magento-customer/link/customerLink'
3
5
  import { ErrorCategory } from '@graphcommerce/magento-graphql'
4
- import type { GraphQLError } from 'graphql'
6
+ import { t } from '@lingui/macro'
7
+ import { GraphQLError } from 'graphql'
5
8
  import { writeCartId } from '../hooks'
6
9
  import { CreateEmptyCartDocument } from '../hooks/CreateEmptyCart.gql'
10
+ import { getCartEnabledForUser } from '../utils'
11
+ import { isProtectedCartOperation } from './isProtectedCartOperation'
7
12
 
8
13
  type CartOperation = Operation & { variables: { cartId: string } }
9
14
  function isCartOperation(operation: Operation): operation is CartOperation {
@@ -15,7 +20,7 @@ function errorIsIncluded(errorPath: readonly (string | number)[] | undefined, ke
15
20
  return keys.some((value) => value === error)
16
21
  }
17
22
 
18
- export const cartErrorLink = onError(({ graphQLErrors, operation, forward }) => {
23
+ const cartErrorLink = onError(({ graphQLErrors, operation, forward }) => {
19
24
  if (!globalApolloClient.current) return undefined
20
25
 
21
26
  const client = globalApolloClient.current
@@ -68,4 +73,55 @@ export const cartErrorLink = onError(({ graphQLErrors, operation, forward }) =>
68
73
  })
69
74
  })
70
75
 
71
- export const createCartErrorLink = () => cartErrorLink
76
+ const cartPermissionLink = (router: PushRouter) =>
77
+ new ApolloLink((operation, forward) => {
78
+ const { locale } = router
79
+ const { cache } = operation.getContext()
80
+
81
+ if (!isProtectedCartOperation(operation.operationName)) return forward(operation)
82
+
83
+ const check = () => Boolean(cache?.readQuery({ query: CustomerTokenDocument }))
84
+ if (getCartEnabledForUser(locale, check)) return forward(operation)
85
+
86
+ if (!getCustomerAccountCanSignIn(locale))
87
+ throw new Error(
88
+ 'Permission error: permissions.customerAccount is DISABLED, while permissions.cart is set to CUSTOMER_ONLY',
89
+ )
90
+
91
+ const oldHeaders = operation.getContext().headers
92
+ const signInAgainPromise = pushWithPromise(router, '/account/signin')
93
+
94
+ return fromPromise(signInAgainPromise).flatMap(() => {
95
+ const tokenQuery = cache?.readQuery({ query: CustomerTokenDocument })
96
+
97
+ if (tokenQuery?.customerToken?.valid) {
98
+ // Customer is authenticated, retrying request.
99
+ operation.setContext({
100
+ headers: {
101
+ ...oldHeaders,
102
+ authorization: `Bearer ${tokenQuery?.customerToken?.token}`,
103
+ },
104
+ })
105
+ return forward(operation)
106
+ }
107
+
108
+ return Observable.of({
109
+ data: null,
110
+ errors: [
111
+ new GraphQLError(t`Please login to add products to your cart`, {
112
+ extensions: { category: 'graphql-authorization' },
113
+ }),
114
+ ],
115
+ })
116
+ })
117
+ })
118
+
119
+ export const cartLink = (router: PushRouter) => {
120
+ const links = [cartErrorLink]
121
+
122
+ if (!(import.meta.graphCommerce.permissions?.cart === 'ENABLED')) {
123
+ links.push(cartPermissionLink(router))
124
+ }
125
+
126
+ return ApolloLink.from(links)
127
+ }
@@ -0,0 +1,5 @@
1
+ export function isProtectedCartOperation(name: string): boolean {
2
+ /* Todo: Determine what operations should be added here */
3
+ const mutations = ['AddProductsToCart', 'CreateEmptyCart']
4
+ return mutations.includes(name)
5
+ }
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": "9.0.0-canary.81",
5
+ "version": "9.0.0-canary.82",
6
6
  "sideEffects": false,
7
7
  "prettier": "@graphcommerce/prettier-config-pwa",
8
8
  "eslintConfig": {
@@ -12,25 +12,26 @@
12
12
  }
13
13
  },
14
14
  "peerDependencies": {
15
- "@graphcommerce/ecommerce-ui": "^9.0.0-canary.81",
16
- "@graphcommerce/eslint-config-pwa": "^9.0.0-canary.81",
17
- "@graphcommerce/framer-next-pages": "^9.0.0-canary.81",
18
- "@graphcommerce/framer-scroller": "^9.0.0-canary.81",
19
- "@graphcommerce/framer-utils": "^9.0.0-canary.81",
20
- "@graphcommerce/graphql": "^9.0.0-canary.81",
21
- "@graphcommerce/image": "^9.0.0-canary.81",
22
- "@graphcommerce/magento-customer": "^9.0.0-canary.81",
23
- "@graphcommerce/magento-graphql": "^9.0.0-canary.81",
24
- "@graphcommerce/magento-store": "^9.0.0-canary.81",
25
- "@graphcommerce/next-ui": "^9.0.0-canary.81",
26
- "@graphcommerce/prettier-config-pwa": "^9.0.0-canary.81",
27
- "@graphcommerce/react-hook-form": "^9.0.0-canary.81",
28
- "@graphcommerce/typescript-config-pwa": "^9.0.0-canary.81",
15
+ "@graphcommerce/ecommerce-ui": "^9.0.0-canary.82",
16
+ "@graphcommerce/eslint-config-pwa": "^9.0.0-canary.82",
17
+ "@graphcommerce/framer-next-pages": "^9.0.0-canary.82",
18
+ "@graphcommerce/framer-scroller": "^9.0.0-canary.82",
19
+ "@graphcommerce/framer-utils": "^9.0.0-canary.82",
20
+ "@graphcommerce/graphql": "^9.0.0-canary.82",
21
+ "@graphcommerce/image": "^9.0.0-canary.82",
22
+ "@graphcommerce/magento-customer": "^9.0.0-canary.82",
23
+ "@graphcommerce/magento-graphql": "^9.0.0-canary.82",
24
+ "@graphcommerce/magento-store": "^9.0.0-canary.82",
25
+ "@graphcommerce/next-ui": "^9.0.0-canary.82",
26
+ "@graphcommerce/prettier-config-pwa": "^9.0.0-canary.82",
27
+ "@graphcommerce/react-hook-form": "^9.0.0-canary.82",
28
+ "@graphcommerce/typescript-config-pwa": "^9.0.0-canary.82",
29
29
  "@lingui/core": "^4.2.1",
30
30
  "@lingui/macro": "^4.2.1",
31
31
  "@lingui/react": "^4.2.1",
32
32
  "@mui/material": "^5.10.16",
33
33
  "framer-motion": "^10.0.0",
34
+ "graphql": "^16.0.0",
34
35
  "next": "*",
35
36
  "react": "^18.2.0",
36
37
  "react-dom": "^18.2.0"
@@ -1,6 +1,9 @@
1
1
  import { GraphQLProviderProps } from '@graphcommerce/graphql'
2
2
  import type { PluginConfig, PluginProps } from '@graphcommerce/next-config'
3
- import { cartErrorLink } from '../link/createCartErrorLink'
3
+ import { useEventCallback } from '@mui/material'
4
+ import { NextRouter } from 'next/router'
5
+ import { useMemo } from 'react'
6
+ import { cartLink } from '../link/cartLink'
4
7
  import { cartTypePolicies, migrateCart } from '../typePolicies'
5
8
 
6
9
  export const config: PluginConfig = {
@@ -9,11 +12,20 @@ export const config: PluginConfig = {
9
12
  }
10
13
 
11
14
  export function GraphQLProvider(props: PluginProps<GraphQLProviderProps>) {
12
- const { Prev, links = [], policies = [], migrations = [], ...rest } = props
15
+ const { Prev, router, links = [], policies = [], migrations = [], ...rest } = props
16
+
17
+ const push = useEventCallback<NextRouter['push']>((...args) => router.push(...args))
18
+
19
+ const cartLinkMemo = useMemo(
20
+ () => cartLink({ push, events: router.events, locale: router.locale }),
21
+ [push, router.events, router.locale],
22
+ )
23
+
13
24
  return (
14
25
  <Prev
15
26
  {...rest}
16
- links={[...links, cartErrorLink]}
27
+ router={router}
28
+ links={[...links, cartLinkMemo]}
17
29
  policies={[...policies, cartTypePolicies]}
18
30
  migrations={[...migrations, migrateCart]}
19
31
  />
@@ -0,0 +1,23 @@
1
+ import { storefrontConfig } from '@graphcommerce/next-ui'
2
+
3
+ function getCartPermissions(locale: string | undefined) {
4
+ return (
5
+ storefrontConfig(locale)?.permissions?.cart ??
6
+ import.meta.graphCommerce.permissions?.cart ??
7
+ 'ENABLED'
8
+ )
9
+ }
10
+
11
+ export function getCartDisabled(locale: string | undefined) {
12
+ return getCartPermissions(locale) === 'DISABLED'
13
+ }
14
+
15
+ export function getCartGuestEnabled(locale: string | undefined) {
16
+ return getCartPermissions(locale) === 'ENABLED'
17
+ }
18
+
19
+ export function getCartEnabledForUser(locale: string | undefined, loggedIn: () => boolean) {
20
+ if (getCartGuestEnabled(locale)) return true
21
+ if (getCartDisabled(locale)) return false
22
+ return !!loggedIn()
23
+ }
@@ -0,0 +1,13 @@
1
+ import { storefrontConfig } from '@graphcommerce/next-ui'
2
+
3
+ function getCheckoutPermission(locale: string | undefined) {
4
+ return (
5
+ storefrontConfig(locale)?.permissions?.checkout ??
6
+ import.meta.graphCommerce.permissions?.checkout ??
7
+ 'ENABLED'
8
+ )
9
+ }
10
+
11
+ export function getCheckoutIsDisabled(locale: string | undefined) {
12
+ return getCheckoutPermission(locale) === 'DISABLED'
13
+ }
package/utils/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './cartPermissions'
2
+ export * from './checkoutPermissions'