@graphcommerce/magento-cart 9.0.0-canary.80 → 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 +8 -0
- package/Config.graphqls +17 -0
- package/components/ApolloCartError/ApolloCartErrorAlert.tsx +1 -46
- package/components/CartFab/CartFab.tsx +7 -1
- package/components/CartStartCheckout/CartStartCheckout.tsx +23 -7
- package/components/CartStartCheckout/CartStartCheckoutLinkOrButton.tsx +4 -2
- package/components/InlineAccount/InlineAccount.tsx +5 -2
- package/hooks/index.ts +5 -3
- package/hooks/useCartPermissions.ts +21 -0
- package/hooks/useCartQuery.ts +24 -3
- package/hooks/useCheckoutPermissions.ts +21 -0
- package/hooks/useFormGqlMutationCart.ts +36 -3
- package/index.ts +4 -3
- package/link/{createCartErrorLink.ts → cartLink.ts} +60 -4
- package/link/isProtectedCartOperation.ts +5 -0
- package/package.json +16 -15
- package/plugins/MagentoCartGraphqlProvider.tsx +15 -3
- package/utils/cartPermissions.ts +23 -0
- package/utils/checkoutPermissions.ts +13 -0
- package/utils/index.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
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
|
+
|
|
9
|
+
## 9.0.0-canary.81
|
|
10
|
+
|
|
3
11
|
## 9.0.0-canary.80
|
|
4
12
|
|
|
5
13
|
## 9.0.0-canary.79
|
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
|
-
|
|
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
|
|
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/
|
|
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'
|
|
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) => ({
|
|
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
|
|
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
|
|
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'
|
|
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 './
|
|
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 './
|
|
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
|
+
}
|
package/hooks/useCartQuery.ts
CHANGED
|
@@ -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
|
-
|
|
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 {
|
|
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
|
-
|
|
31
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
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.
|
|
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.
|
|
16
|
-
"@graphcommerce/eslint-config-pwa": "^9.0.0-canary.
|
|
17
|
-
"@graphcommerce/framer-next-pages": "^9.0.0-canary.
|
|
18
|
-
"@graphcommerce/framer-scroller": "^9.0.0-canary.
|
|
19
|
-
"@graphcommerce/framer-utils": "^9.0.0-canary.
|
|
20
|
-
"@graphcommerce/graphql": "^9.0.0-canary.
|
|
21
|
-
"@graphcommerce/image": "^9.0.0-canary.
|
|
22
|
-
"@graphcommerce/magento-customer": "^9.0.0-canary.
|
|
23
|
-
"@graphcommerce/magento-graphql": "^9.0.0-canary.
|
|
24
|
-
"@graphcommerce/magento-store": "^9.0.0-canary.
|
|
25
|
-
"@graphcommerce/next-ui": "^9.0.0-canary.
|
|
26
|
-
"@graphcommerce/prettier-config-pwa": "^9.0.0-canary.
|
|
27
|
-
"@graphcommerce/react-hook-form": "^9.0.0-canary.
|
|
28
|
-
"@graphcommerce/typescript-config-pwa": "^9.0.0-canary.
|
|
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 {
|
|
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
|
-
|
|
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