@graphcommerce/magento-store 9.1.0-canary.18 → 9.1.0-canary.19
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 +12 -0
- package/components/AttributeForm/AttributeFormField.tsx +27 -0
- package/components/AttributeForm/AttributesFormAutoLayout.tsx +91 -0
- package/components/AttributeForm/index.ts +3 -0
- package/components/AttributeForm/preloadAttributesForm.ts +10 -0
- package/components/AttributeForm/useAttributesForm.tsx +266 -0
- package/components/CurrencySymbol/CurrencySymbol.tsx +1 -1
- package/components/GlobalHead/GlobalHead.tsx +27 -4
- package/components/Money/Money.tsx +2 -2
- package/components/PageMeta/PageMeta.tsx +13 -5
- package/components/PriceModifiers/PriceModifiers.tsx +38 -0
- package/components/PriceModifiers/PriceModifiersList.tsx +43 -0
- package/components/PriceModifiers/PriceModifiersListChildItem.tsx +48 -0
- package/components/PriceModifiers/PriceModifiersListItem.tsx +25 -0
- package/components/PriceModifiers/index.ts +4 -0
- package/components/StoreSwitcher/StoreSwitcherGroupSelector.tsx +2 -2
- package/components/StoreSwitcher/StoreSwitcherList.tsx +1 -1
- package/components/StoreSwitcher/index.ts +0 -1
- package/components/StoreSwitcher/useStoreSwitcher.tsx +2 -4
- package/components/StoreSwitcherButton/StoreSwitcherButton.tsx +1 -1
- package/graphql/fragments/AttributeValueFragment.graphql +13 -0
- package/graphql/fragments/CustomAttributeMetadata.graphql +15 -0
- package/{queries → graphql/fragments}/StoreConfigFragment.graphql +13 -2
- package/graphql/fragments/index.ts +5 -0
- package/graphql/index.ts +2 -0
- package/graphql/queries/AttributesForm.graphql +13 -0
- package/{queries → graphql/queries}/CountryRegions.graphql +1 -0
- package/graphql/queries/index.ts +5 -0
- package/hooks/useFindCountry.ts +2 -2
- package/index.ts +3 -3
- package/package.json +10 -10
- package/utils/redirectOrNotFound.ts +2 -3
- /package/{components/Money → graphql/fragments}/Money.graphql +0 -0
- /package/{queries → graphql/fragments}/StoreConfigQueryFragment.graphql +0 -0
- /package/{utils → graphql/queries}/HandleRedirect.graphql +0 -0
- /package/{queries → graphql/queries}/StoreConfig.graphql +0 -0
- /package/{components/StoreSwitcher → graphql/queries}/StoreSwitcherList.graphql +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 9.1.0-canary.19
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#2499](https://github.com/graphcommerce-org/graphcommerce/pull/2499) [`9f040a4`](https://github.com/graphcommerce-org/graphcommerce/commit/9f040a4c0947f05f2f27e4c5078a684b04e711e1) - Implemented the `query { attributesForm }` to be able to dynamically render forms with useAttributesForm/preloadAttributesForm and AttributesFormAutoLayout, and additional utilities to handle form submissions. ([@paales](https://github.com/paales))
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- [#2499](https://github.com/graphcommerce-org/graphcommerce/pull/2499) [`798c122`](https://github.com/graphcommerce-org/graphcommerce/commit/798c12271e90cf0e02f021e7ad4aa088b4c7c2a3) - Use the `storeConfig.head_shortcut_icon` when configured, if not configured it will use the favicon.ico and favicon.svg from the public folder. ([@paales](https://github.com/paales))
|
|
12
|
+
|
|
13
|
+
- [#2499](https://github.com/graphcommerce-org/graphcommerce/pull/2499) [`6b2b44c`](https://github.com/graphcommerce-org/graphcommerce/commit/6b2b44ca853279144d7768067f3462d4d4bf0af1) - Created a new PriceModifiers component that is implemented on CartItems, allowing different product types to render their options in a consistent manner and allow rendering a base price so that the sum in the cart is correct. ([@paales](https://github.com/paales))
|
|
14
|
+
|
|
3
15
|
## 9.1.0-canary.18
|
|
4
16
|
|
|
5
17
|
## 9.1.0-canary.17
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Control, FieldValues } from '@graphcommerce/react-hook-form'
|
|
2
|
+
import type { SxProps, Theme } from '@mui/material'
|
|
3
|
+
import type { CustomAttributeMetadata, CustomAttributeMetadataTypename } from './useAttributesForm'
|
|
4
|
+
|
|
5
|
+
export type AttributeFormFieldProps<
|
|
6
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
7
|
+
Typename extends CustomAttributeMetadataTypename = CustomAttributeMetadataTypename,
|
|
8
|
+
> = {
|
|
9
|
+
control: Control<TFieldValues>
|
|
10
|
+
sx?: SxProps<Theme>
|
|
11
|
+
metadata: CustomAttributeMetadata<Typename>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function AttributeFormField<
|
|
15
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
16
|
+
Typename extends CustomAttributeMetadataTypename = CustomAttributeMetadataTypename,
|
|
17
|
+
>(props: AttributeFormFieldProps<TFieldValues, Typename>) {
|
|
18
|
+
const { metadata, control, sx } = props
|
|
19
|
+
|
|
20
|
+
if (process.env.NODE_ENV === 'production') return null
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div>
|
|
24
|
+
Please specify a renderer for {metadata.__typename}:{metadata.code}
|
|
25
|
+
</div>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import {
|
|
2
|
+
nonNullable,
|
|
3
|
+
SectionContainer,
|
|
4
|
+
sxx,
|
|
5
|
+
type SectionContainerProps,
|
|
6
|
+
} from '@graphcommerce/next-ui'
|
|
7
|
+
import type { Control, FieldValues } from '@graphcommerce/react-hook-form'
|
|
8
|
+
import { Box, type SxProps, type Theme } from '@mui/material'
|
|
9
|
+
import { AttributeFormField, type AttributeFormFieldProps } from './AttributeFormField'
|
|
10
|
+
import type {
|
|
11
|
+
CustomAttributeMetadata,
|
|
12
|
+
CustomAttributeMetadataTypename,
|
|
13
|
+
GridArea,
|
|
14
|
+
} from './useAttributesForm'
|
|
15
|
+
|
|
16
|
+
export type AttributeFormAutoLayoutFieldset = {
|
|
17
|
+
label?: React.ReactNode
|
|
18
|
+
gridAreas: GridArea[]
|
|
19
|
+
sx?: SxProps<Theme>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type AttributeFormAutoLayoutProps<
|
|
23
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
24
|
+
Typename extends CustomAttributeMetadataTypename = CustomAttributeMetadataTypename,
|
|
25
|
+
> = {
|
|
26
|
+
control: Control<TFieldValues>
|
|
27
|
+
fieldsets?: AttributeFormAutoLayoutFieldset[]
|
|
28
|
+
attributes: CustomAttributeMetadata<Typename>[]
|
|
29
|
+
render?: React.FC<AttributeFormFieldProps<NoInfer<TFieldValues>, Typename>>
|
|
30
|
+
sectionContainer?: Omit<SectionContainerProps, 'labelLeft'>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function AttributesFormAutoLayout<
|
|
34
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
35
|
+
Typename extends CustomAttributeMetadataTypename = CustomAttributeMetadataTypename,
|
|
36
|
+
>(props: AttributeFormAutoLayoutProps<TFieldValues, Typename>) {
|
|
37
|
+
const {
|
|
38
|
+
control,
|
|
39
|
+
fieldsets,
|
|
40
|
+
attributes: incomingAttributes,
|
|
41
|
+
render: Component = AttributeFormField,
|
|
42
|
+
sectionContainer,
|
|
43
|
+
} = props
|
|
44
|
+
|
|
45
|
+
let itemsRemaining = incomingAttributes
|
|
46
|
+
const byFieldSet = (fieldsets ?? []).map(({ gridAreas, ...rest }) => ({
|
|
47
|
+
...rest,
|
|
48
|
+
attributes: gridAreas
|
|
49
|
+
.map((gridArea) => {
|
|
50
|
+
const item = itemsRemaining.find((i) => i.gridArea === gridArea)
|
|
51
|
+
if (item) itemsRemaining = itemsRemaining.filter((i) => i.gridArea !== item.gridArea)
|
|
52
|
+
return item
|
|
53
|
+
})
|
|
54
|
+
.filter(nonNullable),
|
|
55
|
+
}))
|
|
56
|
+
|
|
57
|
+
if (itemsRemaining.length > 0) byFieldSet.push({ label: 'Other', attributes: itemsRemaining })
|
|
58
|
+
|
|
59
|
+
return byFieldSet.map((fieldSet) => {
|
|
60
|
+
const key = fieldSet.attributes.map((fieldName) => fieldName.gridArea).join('-')
|
|
61
|
+
|
|
62
|
+
const fields = (
|
|
63
|
+
<Box
|
|
64
|
+
sx={sxx(
|
|
65
|
+
(theme) => ({
|
|
66
|
+
display: 'grid',
|
|
67
|
+
gridTemplateAreas: fieldSet.attributes
|
|
68
|
+
.map((metadata) => `"${metadata.gridArea}"`)
|
|
69
|
+
.join('\n'),
|
|
70
|
+
py: theme.spacings.xs,
|
|
71
|
+
columnGap: theme.spacings.xs,
|
|
72
|
+
rowGap: theme.spacings.xxs,
|
|
73
|
+
}),
|
|
74
|
+
fieldSet.sx,
|
|
75
|
+
)}
|
|
76
|
+
>
|
|
77
|
+
{fieldSet.attributes.map((metadata) => (
|
|
78
|
+
<Component key={metadata.gridArea} control={control} metadata={metadata} />
|
|
79
|
+
))}
|
|
80
|
+
</Box>
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return fieldSet.label ? (
|
|
84
|
+
<SectionContainer labelLeft={fieldSet.label} key={key} {...sectionContainer}>
|
|
85
|
+
{fields}
|
|
86
|
+
</SectionContainer>
|
|
87
|
+
) : (
|
|
88
|
+
fields
|
|
89
|
+
)
|
|
90
|
+
})
|
|
91
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ApolloClient, NormalizedCacheObject } from '@graphcommerce/graphql'
|
|
2
|
+
import { AttributesFormDocument } from '../../graphql'
|
|
3
|
+
import type { CustomAttributeFormCode } from './useAttributesForm'
|
|
4
|
+
|
|
5
|
+
export function preloadAttributesForm(
|
|
6
|
+
client: ApolloClient<NormalizedCacheObject>,
|
|
7
|
+
formCode: CustomAttributeFormCode,
|
|
8
|
+
) {
|
|
9
|
+
return client.query({ query: AttributesFormDocument, variables: { formCode } })
|
|
10
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { useQuery } from '@graphcommerce/graphql'
|
|
2
|
+
import type { AttributeValueInput } from '@graphcommerce/graphql-mesh'
|
|
3
|
+
import { nonNullable, useMemoObject } from '@graphcommerce/next-ui'
|
|
4
|
+
import { useMemo } from 'react'
|
|
5
|
+
import { AttributesFormDocument } from '../../graphql'
|
|
6
|
+
import type { AttributeValueFragment, CustomAttributeMetadataFragment } from '../../graphql'
|
|
7
|
+
|
|
8
|
+
type CustomAttributesFormFieldValue = string | string[] | boolean | undefined
|
|
9
|
+
type CustomAttributesFormField = Record<string, CustomAttributesFormFieldValue>
|
|
10
|
+
|
|
11
|
+
export type CustomAttributesFormValues<
|
|
12
|
+
AdditionalKnownAttributes extends Record<string, unknown> = Record<string, unknown>,
|
|
13
|
+
> = {
|
|
14
|
+
custom_attributes?: CustomAttributesFormField & AdditionalKnownAttributes
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Convert the GraphQL custom_attributes field to the CustomAttributesFormField so the data is
|
|
19
|
+
* correctly mapped to the form.
|
|
20
|
+
*
|
|
21
|
+
* So for example it maps this data:
|
|
22
|
+
*
|
|
23
|
+
* ```graphql
|
|
24
|
+
* type Customer {
|
|
25
|
+
* """Customer's custom attributes."""
|
|
26
|
+
* custom_attributes(attributeCodes: [ID!]): [AttributeValueInterface]
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* To a format that is compatible with the useForm hook with the `CustomAttributesFormValues` type.
|
|
31
|
+
*/
|
|
32
|
+
export function AttributesValueArray_to_CustomAttributesFormField(
|
|
33
|
+
attributes: CustomAttributeMetadata[],
|
|
34
|
+
custom_attributes: (AttributeValueFragment | null | undefined)[] | null | undefined,
|
|
35
|
+
): CustomAttributesFormField {
|
|
36
|
+
const result: CustomAttributesFormField = {}
|
|
37
|
+
|
|
38
|
+
attributes.forEach((metadata) => {
|
|
39
|
+
const attr = (custom_attributes ?? [])
|
|
40
|
+
.filter(nonNullable)
|
|
41
|
+
.find((ca) => ca.code === metadata.code)
|
|
42
|
+
|
|
43
|
+
// Handle multiselect
|
|
44
|
+
if (
|
|
45
|
+
metadata.frontend_input === 'MULTISELECT' ||
|
|
46
|
+
attr?.__typename === 'AttributeSelectedOptions'
|
|
47
|
+
) {
|
|
48
|
+
const value =
|
|
49
|
+
attr?.__typename === 'AttributeSelectedOptions'
|
|
50
|
+
? (attr?.selected_options ?? [])
|
|
51
|
+
.filter(nonNullable)
|
|
52
|
+
.map((option) => option.value)
|
|
53
|
+
.filter((v) => !!v)
|
|
54
|
+
: metadata.options
|
|
55
|
+
?.filter(nonNullable)
|
|
56
|
+
.filter((o) => o.is_default)
|
|
57
|
+
.map((o) => o.value)
|
|
58
|
+
|
|
59
|
+
result[metadata.code] = value
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Handle MULTILINE fields
|
|
64
|
+
if (metadata.frontend_input === 'MULTILINE' && metadata.index !== undefined) {
|
|
65
|
+
const value = attr?.value.split('\n')[metadata.index]
|
|
66
|
+
const valueArray = result[metadata.code] ?? []
|
|
67
|
+
valueArray[metadata.index] = value ?? metadata.default_value ?? undefined
|
|
68
|
+
result[metadata.code] = valueArray
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Handle DATE fields
|
|
73
|
+
if (metadata.frontend_input === 'DATE' && attr?.__typename === 'AttributeValue' && attr.value) {
|
|
74
|
+
const [date] = attr.value.split(' ')
|
|
75
|
+
result[metadata.code] = date
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Handle regular fields
|
|
80
|
+
result[metadata.code] = attr?.value ?? metadata.default_value ?? undefined
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
return result
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Covert the CustomAttributesFormField to the AttributeValueInput[] so the data is correctly mapped
|
|
88
|
+
* To the Magento GraphQL mutation.
|
|
89
|
+
*
|
|
90
|
+
* So the CustomAttributesFormField is converted to the input for a mutation which can look lke:s
|
|
91
|
+
*
|
|
92
|
+
* ```graphql
|
|
93
|
+
* input CustomerUpdateInput {
|
|
94
|
+
* """The customer's custom attributes."""
|
|
95
|
+
* custom_attributes: [AttributeValueInput]
|
|
96
|
+
* }
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export function CustomAttributesField_to_AttributeValueInputs(
|
|
100
|
+
attributes: CustomAttributeMetadata[],
|
|
101
|
+
custom_attributes: CustomAttributesFormField,
|
|
102
|
+
): AttributeValueInput[] {
|
|
103
|
+
const values: Record<string, AttributeValueInput> = {}
|
|
104
|
+
|
|
105
|
+
attributes.forEach((metadata) => {
|
|
106
|
+
const attribute_code = metadata.code
|
|
107
|
+
const value = custom_attributes[metadata.code]
|
|
108
|
+
|
|
109
|
+
if (!value) return
|
|
110
|
+
|
|
111
|
+
if (
|
|
112
|
+
(metadata.__typename === 'CustomerAttributeMetadata' &&
|
|
113
|
+
metadata.frontend_input === 'BOOLEAN') ||
|
|
114
|
+
typeof value === 'boolean'
|
|
115
|
+
) {
|
|
116
|
+
values[metadata.code] = { attribute_code, value: value ? '1' : '0' }
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (Array.isArray(value)) {
|
|
121
|
+
if (metadata.frontend_input === 'MULTILINE') {
|
|
122
|
+
values[metadata.code] = { attribute_code, value: value.join('\n') }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (metadata.frontend_input === 'MULTISELECT') {
|
|
126
|
+
values[metadata.code] = { attribute_code, value: value.join(',') }
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
values[metadata.code] = { attribute_code, value }
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
return Object.values(values)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export type CustomAttributeFormCode = (
|
|
137
|
+
| 'customer_account_create'
|
|
138
|
+
| 'customer_account_edit'
|
|
139
|
+
| 'customer_address_edit'
|
|
140
|
+
| 'customer_register_address'
|
|
141
|
+
) &
|
|
142
|
+
(string & {})
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Magento attribute code, the actual possible values are deterimined in the database of Magento and
|
|
146
|
+
* therefor are now known at compile time.
|
|
147
|
+
*/
|
|
148
|
+
export type MagentoAttributeCode = string
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* For each field a gridArea is set. Used for the name of the field that doesn't contain any dots.
|
|
152
|
+
* etc.
|
|
153
|
+
*/
|
|
154
|
+
export type GridArea = string
|
|
155
|
+
|
|
156
|
+
export type CustomAttributeMetadataTypename = CustomAttributeMetadataFragment['__typename']
|
|
157
|
+
|
|
158
|
+
export type CustomAttributeMetadata<
|
|
159
|
+
Typename extends CustomAttributeMetadataTypename = CustomAttributeMetadataTypename,
|
|
160
|
+
> = Extract<CustomAttributeMetadataFragment, { __typename: Typename }> & {
|
|
161
|
+
name: string
|
|
162
|
+
index?: number
|
|
163
|
+
gridArea: GridArea
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export type UseAttributesFormConfig<
|
|
167
|
+
Typename extends CustomAttributeMetadata['__typename'] = CustomAttributeMetadata['__typename'],
|
|
168
|
+
> = {
|
|
169
|
+
/** The form code that is used to retrieve the attributes */
|
|
170
|
+
formCode: CustomAttributeFormCode | (string & Record<never, never>)
|
|
171
|
+
/** A list of attribute codes that should be excluded from the form completely */
|
|
172
|
+
exclude?: MagentoAttributeCode[]
|
|
173
|
+
|
|
174
|
+
typename?: Typename
|
|
175
|
+
|
|
176
|
+
attributeToName?: Record<MagentoAttributeCode, string>
|
|
177
|
+
|
|
178
|
+
customizeAttributes?: (
|
|
179
|
+
attribute: CustomAttributeMetadata<Typename>,
|
|
180
|
+
) => CustomAttributeMetadata<Typename>
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Retrieve the available attributes.
|
|
185
|
+
*
|
|
186
|
+
* Example:
|
|
187
|
+
*
|
|
188
|
+
* ```tsx
|
|
189
|
+
* const attributes = useAttributesForm({
|
|
190
|
+
* formCode: 'customer_account_create',
|
|
191
|
+
* exclude: ['email'],
|
|
192
|
+
* })
|
|
193
|
+
* ```
|
|
194
|
+
*
|
|
195
|
+
* Note: When using this hook, make sure the page also calls preloadAttributesForm in getStaticProps
|
|
196
|
+
* so the query doesn't need to run in the browser. Example:
|
|
197
|
+
*
|
|
198
|
+
* ```tsx:
|
|
199
|
+
* const client = graphqlSharedClient(context)
|
|
200
|
+
* await preloadAttributesForm(client, 'customer_account_create')
|
|
201
|
+
* ```
|
|
202
|
+
*/
|
|
203
|
+
export function useAttributesForm<
|
|
204
|
+
Typename extends CustomAttributeMetadata['__typename'] = CustomAttributeMetadata['__typename'],
|
|
205
|
+
>({
|
|
206
|
+
customizeAttributes,
|
|
207
|
+
...incomingConfig
|
|
208
|
+
}: UseAttributesFormConfig<Typename>): CustomAttributeMetadata<Typename>[] {
|
|
209
|
+
const config = useMemoObject(incomingConfig)
|
|
210
|
+
|
|
211
|
+
const { data } = useQuery(AttributesFormDocument, {
|
|
212
|
+
variables: { formCode: config.formCode },
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
return useMemo(() => {
|
|
216
|
+
const items = (data?.attributesForm?.items ?? [])
|
|
217
|
+
.filter(nonNullable)
|
|
218
|
+
.filter((item) => !config.exclude?.includes(item.code))
|
|
219
|
+
.map((item) => ({
|
|
220
|
+
...item,
|
|
221
|
+
gridArea: item.code,
|
|
222
|
+
name: config.attributeToName?.[item.code] ?? `custom_attributes.${item.code}`,
|
|
223
|
+
}))
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* We handle frontend_input=MULTILINE input fields a bit different as they have multiple values.
|
|
227
|
+
* In this case we replace the original field and add multiple fields for the same attributes.
|
|
228
|
+
*/
|
|
229
|
+
const newItems = items
|
|
230
|
+
.map((item) => {
|
|
231
|
+
if (
|
|
232
|
+
item.frontend_input === 'MULTILINE' &&
|
|
233
|
+
item.__typename === 'CustomerAttributeMetadata' &&
|
|
234
|
+
item.multiline_count
|
|
235
|
+
) {
|
|
236
|
+
const defaultValues = item.default_value?.split('\n') ?? []
|
|
237
|
+
return Array.from({ length: item.multiline_count }, (_, index) => ({
|
|
238
|
+
...item,
|
|
239
|
+
label: index === 0 ? item.label : undefined,
|
|
240
|
+
index,
|
|
241
|
+
gridArea: `${item.code}_${index}`,
|
|
242
|
+
name: `custom_attributes.${item.code}.${index}`,
|
|
243
|
+
default_value: defaultValues[index] ?? undefined,
|
|
244
|
+
}))
|
|
245
|
+
}
|
|
246
|
+
return [item]
|
|
247
|
+
})
|
|
248
|
+
.flat(1)
|
|
249
|
+
|
|
250
|
+
return newItems
|
|
251
|
+
.filter((item): item is CustomAttributeMetadata<Typename> =>
|
|
252
|
+
config.typename ? item.__typename === config.typename : true,
|
|
253
|
+
)
|
|
254
|
+
.map((attribute) => customizeAttributes?.(attribute) || attribute)
|
|
255
|
+
}, [data?.attributesForm?.items, config, customizeAttributes])
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export function extractAttributes(
|
|
259
|
+
attributes: CustomAttributeMetadataFragment[],
|
|
260
|
+
attributeCodes: MagentoAttributeCode[],
|
|
261
|
+
): [extracted: CustomAttributeMetadataFragment[], remaining: CustomAttributeMetadataFragment[]] {
|
|
262
|
+
return [
|
|
263
|
+
attributes.filter((attribute) => attributeCodes.includes(attribute.code)),
|
|
264
|
+
attributes.filter((attribute) => !attributeCodes.includes(attribute.code)),
|
|
265
|
+
]
|
|
266
|
+
}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
CurrencySymbol as CurrencySymbolBase,
|
|
4
4
|
type CurrencySymbolProps,
|
|
5
5
|
} from '@graphcommerce/next-ui'
|
|
6
|
-
import { StoreConfigDocument } from '../../
|
|
6
|
+
import { StoreConfigDocument } from '../../graphql'
|
|
7
7
|
|
|
8
8
|
export function CurrencySymbol(props: CurrencySymbolProps) {
|
|
9
9
|
const { currency } = props
|
|
@@ -1,11 +1,34 @@
|
|
|
1
1
|
import { useQuery } from '@graphcommerce/graphql'
|
|
2
2
|
import type { GlobalHeadProps as GlobalHeadPropsBase } from '@graphcommerce/next-ui'
|
|
3
3
|
import { GlobalHead as GlobalHeadBase } from '@graphcommerce/next-ui'
|
|
4
|
-
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
|
5
|
+
import { getImageProps } from 'next/image'
|
|
6
|
+
import React from 'react'
|
|
7
|
+
import { StoreConfigDocument } from '../../graphql'
|
|
5
8
|
|
|
6
9
|
export type GlobalHeadProps = Omit<GlobalHeadPropsBase, 'name'>
|
|
7
10
|
|
|
8
|
-
export
|
|
11
|
+
export const GlobalHead = React.memo<GlobalHeadProps>((props) => {
|
|
12
|
+
const { children } = props
|
|
9
13
|
const name = useQuery(StoreConfigDocument).data?.storeConfig?.website_name ?? ''
|
|
10
|
-
|
|
11
|
-
}
|
|
14
|
+
|
|
15
|
+
const { head_shortcut_icon, secure_base_media_url } =
|
|
16
|
+
useQuery(StoreConfigDocument).data?.storeConfig ?? {}
|
|
17
|
+
|
|
18
|
+
let icon: string | undefined
|
|
19
|
+
if (head_shortcut_icon && secure_base_media_url) {
|
|
20
|
+
icon = getImageProps({
|
|
21
|
+
src: `${secure_base_media_url}favicon/${head_shortcut_icon}`,
|
|
22
|
+
alt: 'favicon',
|
|
23
|
+
width: 16,
|
|
24
|
+
height: 16,
|
|
25
|
+
}).props.src
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<GlobalHeadBase name={name} {...props}>
|
|
30
|
+
<link rel='icon' href={icon ?? '/favicon.ico'} sizes='any' key='icon' />
|
|
31
|
+
{!icon && <link rel='icon' href='/favicon.svg' type='image/svg+xml' key='icon-svg' />}
|
|
32
|
+
</GlobalHeadBase>
|
|
33
|
+
)
|
|
34
|
+
})
|
|
@@ -2,8 +2,8 @@ import { useQuery } from '@graphcommerce/graphql'
|
|
|
2
2
|
import type { CurrencyFormatProps } from '@graphcommerce/next-ui'
|
|
3
3
|
import { CurrencyFormat } from '@graphcommerce/next-ui'
|
|
4
4
|
import type { SxProps, Theme } from '@mui/material'
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
5
|
+
import type { MoneyFragment } from '../../graphql'
|
|
6
|
+
import { StoreConfigDocument } from '../../graphql'
|
|
7
7
|
|
|
8
8
|
type OverridableProps = {
|
|
9
9
|
round?: boolean
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { useQuery } from '@graphcommerce/graphql'
|
|
2
2
|
import type { PageMetaProps as NextPageMetaProps } from '@graphcommerce/next-ui'
|
|
3
3
|
import { PageMeta as NextPageMeta } from '@graphcommerce/next-ui'
|
|
4
|
-
import { StoreConfigDocument } from '../../
|
|
4
|
+
import { StoreConfigDocument } from '../../graphql'
|
|
5
5
|
|
|
6
|
-
export type PageMetaProps = Omit<NextPageMetaProps, 'canonical'> & {
|
|
7
|
-
|
|
6
|
+
export type PageMetaProps = Omit<NextPageMetaProps, 'metaDescription' | 'title' | 'canonical'> & {
|
|
7
|
+
metaDescription?: string | null
|
|
8
|
+
title?: string | null
|
|
9
|
+
canonical?: string | null
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
export function PageMeta(props: PageMetaProps) {
|
|
11
|
-
const { children, title, ...pageMetaProps } = props
|
|
13
|
+
const { children, title, metaDescription, metaKeywords, canonical, ...pageMetaProps } = props
|
|
12
14
|
const config = useQuery(StoreConfigDocument)
|
|
13
15
|
|
|
14
16
|
const prefix = config.data?.storeConfig?.title_prefix ?? ''
|
|
@@ -22,7 +24,13 @@ export function PageMeta(props: PageMetaProps) {
|
|
|
22
24
|
if (suffix) pageTitle += ` ${suffix}`
|
|
23
25
|
|
|
24
26
|
return (
|
|
25
|
-
<NextPageMeta
|
|
27
|
+
<NextPageMeta
|
|
28
|
+
title={pageTitle}
|
|
29
|
+
metaDescription={metaDescription ?? config.data?.storeConfig?.default_description}
|
|
30
|
+
metaKeywords={metaKeywords ?? config.data?.storeConfig?.default_keywords}
|
|
31
|
+
canonical={canonical ?? undefined}
|
|
32
|
+
{...pageMetaProps}
|
|
33
|
+
>
|
|
26
34
|
{children}
|
|
27
35
|
</NextPageMeta>
|
|
28
36
|
)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { CurrencyEnum } from '@graphcommerce/graphql-mesh'
|
|
2
|
+
|
|
3
|
+
export function sumPriceModifiers(modifiers: PriceModifier[]) {
|
|
4
|
+
return modifiers.reduce(
|
|
5
|
+
(price, mod) =>
|
|
6
|
+
price +
|
|
7
|
+
(mod.amount ?? 0) * (mod.quantity ?? 1) +
|
|
8
|
+
(mod.items ?? []).reduce(
|
|
9
|
+
(itemPrice, item) => itemPrice + (item.amount ?? 0) * (item.quantity ?? 1),
|
|
10
|
+
0,
|
|
11
|
+
),
|
|
12
|
+
0,
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type PriceModifierItem = {
|
|
17
|
+
key: string
|
|
18
|
+
label: React.ReactNode
|
|
19
|
+
secondary?: React.ReactNode
|
|
20
|
+
quantity?: number
|
|
21
|
+
amount?: number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type PriceModifier = {
|
|
25
|
+
position?: number
|
|
26
|
+
key: string
|
|
27
|
+
label?: React.ReactNode
|
|
28
|
+
amount?: number
|
|
29
|
+
quantity?: number
|
|
30
|
+
items?: PriceModifierItem[]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type PriceModifiersProps = {
|
|
34
|
+
label: React.ReactNode
|
|
35
|
+
total: number
|
|
36
|
+
currency: CurrencyEnum | null | undefined
|
|
37
|
+
modifiers: PriceModifier[]
|
|
38
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { sumPriceModifiers, type PriceModifiersProps } from './PriceModifiers'
|
|
2
|
+
import { PriceModifierListChildItem } from './PriceModifiersListChildItem'
|
|
3
|
+
import { PriceModifierListItem } from './PriceModifiersListItem'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A utility component to display price modifiers in a table format. Used for:
|
|
7
|
+
*
|
|
8
|
+
* Abstracts aways rendering from the price calculations.
|
|
9
|
+
*
|
|
10
|
+
* Renders a base price if it's different from the row total.
|
|
11
|
+
*
|
|
12
|
+
* - `<CartItemActionCard />`
|
|
13
|
+
* - `<OrderItem />`
|
|
14
|
+
* - `<InvoiceItem />`
|
|
15
|
+
* - `<CreditMemoItem />`
|
|
16
|
+
* - `<ShipmentItem />`
|
|
17
|
+
* - `<ReturnItem />`
|
|
18
|
+
*/
|
|
19
|
+
export function PriceModifiersList(props: PriceModifiersProps) {
|
|
20
|
+
const { total: row_total, currency, modifiers, label } = props
|
|
21
|
+
const basePrice = row_total - sumPriceModifiers(modifiers)
|
|
22
|
+
|
|
23
|
+
const sortedModifiers = [...modifiers].sort((a, b) => (a.position ?? 0) - (b.position ?? 0))
|
|
24
|
+
|
|
25
|
+
if (modifiers.length === 0) return null
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
{basePrice > 0 && basePrice !== row_total && (
|
|
30
|
+
<PriceModifierListChildItem
|
|
31
|
+
key='base-price'
|
|
32
|
+
label={label}
|
|
33
|
+
amount={basePrice}
|
|
34
|
+
currency={currency}
|
|
35
|
+
color='text.primary'
|
|
36
|
+
/>
|
|
37
|
+
)}
|
|
38
|
+
{sortedModifiers.map((mod) => (
|
|
39
|
+
<PriceModifierListItem key={mod.key} label={mod.label} items={mod.items} />
|
|
40
|
+
))}
|
|
41
|
+
</>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { CurrencyEnum } from '@graphcommerce/graphql-mesh'
|
|
2
|
+
import { extendableComponent, type ExtendableComponentProps } from '@graphcommerce/next-ui'
|
|
3
|
+
import { Box } from '@mui/material'
|
|
4
|
+
import { Money } from '../Money/Money'
|
|
5
|
+
import type { PriceModifierItem } from './PriceModifiers'
|
|
6
|
+
|
|
7
|
+
const name = 'PriceModifierListChildItem'
|
|
8
|
+
const slots = ['root', 'label', 'quantity', 'amount'] as const
|
|
9
|
+
const { withState } = extendableComponent(name, slots)
|
|
10
|
+
|
|
11
|
+
type PriceModifierListChildItemProps = Omit<PriceModifierItem, 'key'> & {
|
|
12
|
+
currency?: CurrencyEnum | null | undefined
|
|
13
|
+
color?: 'text.primary' | 'text.secondary'
|
|
14
|
+
} & ExtendableComponentProps<typeof slots>
|
|
15
|
+
|
|
16
|
+
export function PriceModifierListChildItem(props: PriceModifierListChildItemProps) {
|
|
17
|
+
const { label, amount = 0, quantity = 1, secondary, currency, color = 'text.secondary' } = props
|
|
18
|
+
|
|
19
|
+
const classes = withState(props)
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Box className={classes.root} sx={(theme) => ({ display: 'flex', gap: theme.spacings.xxs })}>
|
|
23
|
+
<Box className={classes.label} sx={{ color }}>
|
|
24
|
+
{label}
|
|
25
|
+
</Box>
|
|
26
|
+
|
|
27
|
+
{(quantity > 1 || secondary) && (
|
|
28
|
+
<Box className={classes.quantity} sx={{ color: 'text.secondary', whiteSpace: 'nowrap' }}>
|
|
29
|
+
{quantity > 1 && (
|
|
30
|
+
<>
|
|
31
|
+
{quantity} × <Money value={amount} currency={currency} />
|
|
32
|
+
</>
|
|
33
|
+
)}
|
|
34
|
+
{secondary}
|
|
35
|
+
</Box>
|
|
36
|
+
)}
|
|
37
|
+
|
|
38
|
+
{amount !== 0 && amount && (
|
|
39
|
+
<Box
|
|
40
|
+
className={classes.amount}
|
|
41
|
+
sx={(theme) => ({ position: 'absolute', right: theme.spacings.xs })}
|
|
42
|
+
>
|
|
43
|
+
<Money value={quantity * amount} currency={currency} />
|
|
44
|
+
</Box>
|
|
45
|
+
)}
|
|
46
|
+
</Box>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { extendableComponent, type ExtendableComponentProps } from '@graphcommerce/next-ui'
|
|
2
|
+
import { Box } from '@mui/material'
|
|
3
|
+
import type { PriceModifier } from './PriceModifiers'
|
|
4
|
+
import { PriceModifierListChildItem } from './PriceModifiersListChildItem'
|
|
5
|
+
|
|
6
|
+
const name = 'PriceModifierListItem'
|
|
7
|
+
const slots = ['root'] as const
|
|
8
|
+
const { withState } = extendableComponent(name, slots)
|
|
9
|
+
|
|
10
|
+
type PriceModifierListItemProps = ExtendableComponentProps<typeof slots> & PriceModifier
|
|
11
|
+
|
|
12
|
+
export function PriceModifierListItem(props: PriceModifierListItemProps) {
|
|
13
|
+
const { label, items } = props
|
|
14
|
+
|
|
15
|
+
const classes = withState(props)
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<Box className={classes.root}>
|
|
19
|
+
{label && <PriceModifierListChildItem label={label} color='text.primary' />}
|
|
20
|
+
{items?.map(({ key: itemKey, ...item }) => (
|
|
21
|
+
<PriceModifierListChildItem key={itemKey} {...item} color='text.secondary' />
|
|
22
|
+
))}
|
|
23
|
+
</Box>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
@@ -42,7 +42,7 @@ export function StoreSwitcherGroupSelector(props: StoreSwitcherGroupSelectorProp
|
|
|
42
42
|
group,
|
|
43
43
|
image: group.country ? <FlagAvatar country={group.country} size='40px' /> : undefined,
|
|
44
44
|
title: <>{group.store_group_name}</>,
|
|
45
|
-
details: showStores
|
|
45
|
+
details: showStores ? (
|
|
46
46
|
<>
|
|
47
47
|
{group.stores.length <= showStores && (
|
|
48
48
|
<ListFormat listStyle='short' type='unit'>
|
|
@@ -59,7 +59,7 @@ export function StoreSwitcherGroupSelector(props: StoreSwitcherGroupSelectorProp
|
|
|
59
59
|
</ListFormat>
|
|
60
60
|
)}
|
|
61
61
|
</>
|
|
62
|
-
),
|
|
62
|
+
) : undefined,
|
|
63
63
|
disabled: group.disabled,
|
|
64
64
|
value: group.store_group_code,
|
|
65
65
|
slotProps: {
|
|
@@ -3,8 +3,8 @@ import { extendableComponent, FlagAvatar, NextLink } from '@graphcommerce/next-u
|
|
|
3
3
|
import type { SxProps, Theme } from '@mui/material'
|
|
4
4
|
import { Collapse, List, ListItemAvatar, ListItemButton, ListItemText } from '@mui/material'
|
|
5
5
|
import React from 'react'
|
|
6
|
+
import type { StoreSwitcherListQuery } from '../../graphql'
|
|
6
7
|
import { localeToStore, storeToLocale } from '../../utils/localeToStore'
|
|
7
|
-
import type { StoreSwitcherListQuery } from './StoreSwitcherList.gql'
|
|
8
8
|
|
|
9
9
|
type Store = NonNullable<NonNullable<StoreSwitcherListQuery['availableStores']>[0]>
|
|
10
10
|
|
|
@@ -2,6 +2,5 @@ export * from './StoreSwitcherApply'
|
|
|
2
2
|
export * from './StoreSwitcherCurrencySelector'
|
|
3
3
|
export * from './StoreSwitcherGroupSelector'
|
|
4
4
|
export * from './StoreSwitcherList'
|
|
5
|
-
export * from './StoreSwitcherList.gql'
|
|
6
5
|
export * from './StoreSwitcherStoreSelector'
|
|
7
6
|
export * from './useStoreSwitcher'
|
|
@@ -8,10 +8,8 @@ import {
|
|
|
8
8
|
useStorefrontConfig,
|
|
9
9
|
type RequiredKeys,
|
|
10
10
|
} from '@graphcommerce/next-ui'
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { storeToLocale } from '../../utils/localeToStore'
|
|
14
|
-
import { type StoreSwitcherListQuery } from './StoreSwitcherList.gql'
|
|
11
|
+
import { createContext, useContext, useMemo } from 'react'
|
|
12
|
+
import { type StoreSwitcherListQuery } from '../../graphql'
|
|
15
13
|
|
|
16
14
|
export type StoreSwitcherStore = RequiredKeys<
|
|
17
15
|
NonNullable<NonNullable<StoreSwitcherListQuery['availableStores']>[0]>,
|
|
@@ -4,7 +4,7 @@ import type { SxProps, Theme } from '@mui/material'
|
|
|
4
4
|
import { Button } from '@mui/material'
|
|
5
5
|
import { useRouter } from 'next/router'
|
|
6
6
|
import { useMemo } from 'react'
|
|
7
|
-
import { StoreConfigDocument } from '../../
|
|
7
|
+
import { StoreConfigDocument } from '../../graphql'
|
|
8
8
|
|
|
9
9
|
export type StoreSwitcherButtonProps = { sx?: SxProps<Theme> }
|
|
10
10
|
|
|
@@ -3,21 +3,32 @@ fragment StoreConfigFragment on StoreConfig {
|
|
|
3
3
|
store_code
|
|
4
4
|
store_name
|
|
5
5
|
|
|
6
|
+
header_logo_src
|
|
7
|
+
logo_width
|
|
8
|
+
logo_height
|
|
9
|
+
logo_alt
|
|
10
|
+
|
|
11
|
+
copyright
|
|
12
|
+
|
|
6
13
|
locale
|
|
7
14
|
base_currency_code
|
|
15
|
+
|
|
8
16
|
default_display_currency_code
|
|
9
17
|
|
|
18
|
+
head_shortcut_icon
|
|
19
|
+
head_includes
|
|
10
20
|
title_suffix
|
|
11
21
|
title_prefix
|
|
12
22
|
title_separator
|
|
13
23
|
default_title
|
|
14
|
-
|
|
15
|
-
|
|
24
|
+
default_description
|
|
25
|
+
default_keywords
|
|
16
26
|
|
|
17
27
|
catalog_default_sort_by
|
|
18
28
|
category_url_suffix
|
|
19
29
|
product_url_suffix
|
|
20
30
|
secure_base_link_url
|
|
31
|
+
secure_base_media_url
|
|
21
32
|
secure_base_url
|
|
22
33
|
|
|
23
34
|
root_category_uid
|
package/graphql/index.ts
ADDED
package/hooks/useFindCountry.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useQuery } from '@graphcommerce/graphql'
|
|
2
|
-
import type { CountryRegionsQuery } from '../
|
|
3
|
-
import { CountryRegionsDocument } from '../
|
|
2
|
+
import type { CountryRegionsQuery } from '../graphql'
|
|
3
|
+
import { CountryRegionsDocument } from '../graphql'
|
|
4
4
|
|
|
5
5
|
export function useFindCountry(
|
|
6
6
|
countryCode?: string | null,
|
package/index.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
export * from './components/AttributeForm'
|
|
1
2
|
export * from './components/CurrencySymbol/CurrencySymbol'
|
|
2
3
|
export * from './components/GlobalHead/GlobalHead'
|
|
3
4
|
export * from './components/Money/Money'
|
|
4
|
-
export * from './
|
|
5
|
+
export * from './graphql'
|
|
5
6
|
export * from './components/PageMeta/PageMeta'
|
|
6
7
|
export * from './components/StoreSwitcherButton/StoreSwitcherButton'
|
|
7
8
|
export * from './hooks/useFindCountry'
|
|
8
9
|
export * from './hooks/useFindRegion'
|
|
9
|
-
export * from './queries/CountryRegions.gql'
|
|
10
|
-
export * from './queries/StoreConfig.gql'
|
|
11
10
|
export * from './utils/localeToStore'
|
|
12
11
|
export * from './utils/redirectOrNotFound'
|
|
13
12
|
export * from './components/StoreSwitcher'
|
|
13
|
+
export * from './components/PriceModifiers'
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@graphcommerce/magento-store",
|
|
3
3
|
"homepage": "https://www.graphcommerce.org/",
|
|
4
4
|
"repository": "github:graphcommerce-org/graphcommerce",
|
|
5
|
-
"version": "9.1.0-canary.
|
|
5
|
+
"version": "9.1.0-canary.19",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"prettier": "@graphcommerce/prettier-config-pwa",
|
|
8
8
|
"eslintConfig": {
|
|
@@ -12,15 +12,15 @@
|
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"peerDependencies": {
|
|
15
|
-
"@graphcommerce/ecommerce-ui": "^9.1.0-canary.
|
|
16
|
-
"@graphcommerce/eslint-config-pwa": "^9.1.0-canary.
|
|
17
|
-
"@graphcommerce/framer-utils": "^9.1.0-canary.
|
|
18
|
-
"@graphcommerce/graphql": "^9.1.0-canary.
|
|
19
|
-
"@graphcommerce/graphql-mesh": "^9.1.0-canary.
|
|
20
|
-
"@graphcommerce/image": "^9.1.0-canary.
|
|
21
|
-
"@graphcommerce/next-ui": "^9.1.0-canary.
|
|
22
|
-
"@graphcommerce/prettier-config-pwa": "^9.1.0-canary.
|
|
23
|
-
"@graphcommerce/typescript-config-pwa": "^9.1.0-canary.
|
|
15
|
+
"@graphcommerce/ecommerce-ui": "^9.1.0-canary.19",
|
|
16
|
+
"@graphcommerce/eslint-config-pwa": "^9.1.0-canary.19",
|
|
17
|
+
"@graphcommerce/framer-utils": "^9.1.0-canary.19",
|
|
18
|
+
"@graphcommerce/graphql": "^9.1.0-canary.19",
|
|
19
|
+
"@graphcommerce/graphql-mesh": "^9.1.0-canary.19",
|
|
20
|
+
"@graphcommerce/image": "^9.1.0-canary.19",
|
|
21
|
+
"@graphcommerce/next-ui": "^9.1.0-canary.19",
|
|
22
|
+
"@graphcommerce/prettier-config-pwa": "^9.1.0-canary.19",
|
|
23
|
+
"@graphcommerce/typescript-config-pwa": "^9.1.0-canary.19",
|
|
24
24
|
"@lingui/core": "^4.2.1",
|
|
25
25
|
"@lingui/macro": "^4.2.1",
|
|
26
26
|
"@lingui/react": "^4.2.1",
|
|
@@ -3,9 +3,8 @@ import type { ApolloClient, ApolloQueryResult, NormalizedCacheObject } from '@gr
|
|
|
3
3
|
import { flushMeasurePerf } from '@graphcommerce/graphql'
|
|
4
4
|
import { isTypename, nonNullable, storefrontConfig } from '@graphcommerce/next-ui'
|
|
5
5
|
import type { Redirect } from 'next'
|
|
6
|
-
import type { StoreConfigQuery } from '../
|
|
7
|
-
import
|
|
8
|
-
import { HandleRedirectDocument } from './HandleRedirect.gql'
|
|
6
|
+
import type { HandleRedirectQuery, StoreConfigQuery } from '../graphql'
|
|
7
|
+
import { HandleRedirectDocument } from '../graphql'
|
|
9
8
|
import { defaultLocale } from './localeToStore'
|
|
10
9
|
|
|
11
10
|
export type RedirectOr404Return = Promise<
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|