@vendure/dashboard 3.3.5-master-202506231200 → 3.3.5-master-202506241318
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/package.json +4 -4
- package/src/lib/components/shared/custom-fields-form.tsx +157 -45
- package/src/lib/components/shared/customer-address-form.tsx +18 -13
- package/src/lib/components/shared/logo-mark.tsx +2 -2
- package/src/lib/components/shared/translatable-form-field.tsx +2 -8
- package/src/lib/framework/document-introspection/get-document-structure.ts +43 -10
- package/src/lib/framework/extension-api/define-dashboard-extension.ts +6 -0
- package/src/lib/framework/extension-api/extension-api-types.ts +19 -0
- package/src/lib/framework/form-engine/custom-form-component-extensions.ts +28 -0
- package/src/lib/framework/form-engine/custom-form-component.tsx +33 -0
- package/src/lib/framework/form-engine/use-generated-form.tsx +50 -11
- package/src/lib/framework/page/detail-page.tsx +52 -29
- package/src/lib/framework/page/use-detail-page.ts +18 -2
- package/src/lib/framework/registry/registry-types.ts +2 -0
- package/src/lib/graphql/graphql-env.d.ts +7 -6
- package/src/lib/index.ts +29 -1
- package/src/lib/lib/utils.ts +49 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vendure/dashboard",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "3.3.5-master-
|
|
4
|
+
"version": "3.3.5-master-202506241318",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -86,8 +86,8 @@
|
|
|
86
86
|
"@types/react-dom": "^19.0.4",
|
|
87
87
|
"@types/react-grid-layout": "^1.3.5",
|
|
88
88
|
"@uidotdev/usehooks": "^2.4.1",
|
|
89
|
-
"@vendure/common": "^3.3.5-master-
|
|
90
|
-
"@vendure/core": "^3.3.5-master-
|
|
89
|
+
"@vendure/common": "^3.3.5-master-202506241318",
|
|
90
|
+
"@vendure/core": "^3.3.5-master-202506241318",
|
|
91
91
|
"@vitejs/plugin-react": "^4.3.4",
|
|
92
92
|
"awesome-graphql-client": "^2.1.0",
|
|
93
93
|
"class-variance-authority": "^0.7.1",
|
|
@@ -130,5 +130,5 @@
|
|
|
130
130
|
"lightningcss-linux-arm64-musl": "^1.29.3",
|
|
131
131
|
"lightningcss-linux-x64-musl": "^1.29.1"
|
|
132
132
|
},
|
|
133
|
-
"gitHead": "
|
|
133
|
+
"gitHead": "6420b216556b8173cc4b581d1304cb6d11e04c60"
|
|
134
134
|
}
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import { useCustomFieldConfig } from '@/hooks/use-custom-field-config.js';
|
|
2
|
-
import { Control, ControllerRenderProps } from 'react-hook-form';
|
|
3
1
|
import {
|
|
4
|
-
Form,
|
|
5
2
|
FormControl,
|
|
6
3
|
FormDescription,
|
|
7
4
|
FormField,
|
|
@@ -10,12 +7,15 @@ import {
|
|
|
10
7
|
FormMessage,
|
|
11
8
|
} from '@/components/ui/form.js';
|
|
12
9
|
import { Input } from '@/components/ui/input.js';
|
|
10
|
+
import { CustomFormComponent } from '@/framework/form-engine/custom-form-component.js';
|
|
11
|
+
import { useCustomFieldConfig } from '@/hooks/use-custom-field-config.js';
|
|
13
12
|
import { useUserSettings } from '@/hooks/use-user-settings.js';
|
|
14
|
-
import { Switch } from '../ui/switch.js';
|
|
15
|
-
import { CustomFieldType } from '@vendure/common/lib/shared-types';
|
|
16
|
-
import { TranslatableFormField } from './translatable-form-field.js';
|
|
17
13
|
import { customFieldConfigFragment } from '@/providers/server-config.js';
|
|
14
|
+
import { CustomFieldType } from '@vendure/common/lib/shared-types';
|
|
18
15
|
import { ResultOf } from 'gql.tada';
|
|
16
|
+
import { Control, ControllerRenderProps } from 'react-hook-form';
|
|
17
|
+
import { Switch } from '../ui/switch.js';
|
|
18
|
+
import { TranslatableFormField } from './translatable-form-field.js';
|
|
19
19
|
|
|
20
20
|
type CustomFieldConfig = ResultOf<typeof customFieldConfigFragment>;
|
|
21
21
|
|
|
@@ -29,59 +29,171 @@ export function CustomFieldsForm({ entityType, control, formPathPrefix }: Custom
|
|
|
29
29
|
const {
|
|
30
30
|
settings: { displayLanguage },
|
|
31
31
|
} = useUserSettings();
|
|
32
|
-
|
|
32
|
+
|
|
33
|
+
const getTranslation = (input: Array<{ languageCode: string; value: string }> | null | undefined) => {
|
|
33
34
|
return input?.find(t => t.languageCode === displayLanguage)?.value;
|
|
34
|
-
}
|
|
35
|
+
};
|
|
36
|
+
|
|
35
37
|
const customFields = useCustomFieldConfig(entityType);
|
|
38
|
+
|
|
39
|
+
const getFieldName = (fieldDefName: string) => {
|
|
40
|
+
return formPathPrefix
|
|
41
|
+
? `${formPathPrefix}.customFields.${fieldDefName}`
|
|
42
|
+
: `customFields.${fieldDefName}`;
|
|
43
|
+
};
|
|
44
|
+
|
|
36
45
|
return (
|
|
37
46
|
<div className="grid grid-cols-2 gap-4">
|
|
38
47
|
{customFields?.map(fieldDef => (
|
|
39
|
-
<
|
|
40
|
-
{fieldDef.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
<FormLabel>{getTranslation(fieldDef.label) ?? field.name}</FormLabel>
|
|
47
|
-
<FormControl>
|
|
48
|
-
{fieldDef.readonly ? field.value : <FormInputForType fieldDef={fieldDef} field={field} />}
|
|
49
|
-
</FormControl>
|
|
50
|
-
</FormItem>
|
|
51
|
-
)}
|
|
52
|
-
/>
|
|
53
|
-
) : (
|
|
54
|
-
<FormField
|
|
55
|
-
control={control}
|
|
56
|
-
name={formPathPrefix ? `${formPathPrefix}.customFields.${fieldDef.name}` : `customFields.${fieldDef.name}`}
|
|
57
|
-
render={({ field }) => (
|
|
58
|
-
<FormItem>
|
|
59
|
-
<FormLabel>{getTranslation(fieldDef.label) ?? field.name}</FormLabel>
|
|
60
|
-
<FormControl>
|
|
61
|
-
{fieldDef.readonly ? field.value : <FormInputForType fieldDef={fieldDef} field={field} />}
|
|
62
|
-
</FormControl>
|
|
63
|
-
<FormDescription>{getTranslation(fieldDef.description)}</FormDescription>
|
|
64
|
-
<FormMessage />
|
|
65
|
-
</FormItem>
|
|
66
|
-
)}
|
|
67
|
-
/>
|
|
68
|
-
)}
|
|
69
|
-
</div>
|
|
48
|
+
<CustomFieldItem
|
|
49
|
+
key={fieldDef.name}
|
|
50
|
+
fieldDef={fieldDef}
|
|
51
|
+
control={control}
|
|
52
|
+
fieldName={getFieldName(fieldDef.name)}
|
|
53
|
+
getTranslation={getTranslation}
|
|
54
|
+
/>
|
|
70
55
|
))}
|
|
71
56
|
</div>
|
|
72
57
|
);
|
|
73
58
|
}
|
|
74
59
|
|
|
75
|
-
|
|
76
|
-
|
|
60
|
+
interface CustomFieldItemProps {
|
|
61
|
+
fieldDef: CustomFieldConfig;
|
|
62
|
+
control: Control<any, any>;
|
|
63
|
+
fieldName: string;
|
|
64
|
+
getTranslation: (
|
|
65
|
+
input: Array<{ languageCode: string; value: string }> | null | undefined,
|
|
66
|
+
) => string | undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function CustomFieldItem({ fieldDef, control, fieldName, getTranslation }: CustomFieldItemProps) {
|
|
70
|
+
const hasCustomFormComponent = fieldDef.ui && fieldDef.ui.component;
|
|
71
|
+
const isLocaleField = fieldDef.type === 'localeString' || fieldDef.type === 'localeText';
|
|
72
|
+
|
|
73
|
+
// For locale fields, always use TranslatableFormField regardless of custom components
|
|
74
|
+
if (isLocaleField) {
|
|
75
|
+
return (
|
|
76
|
+
<TranslatableFormField
|
|
77
|
+
control={control}
|
|
78
|
+
name={fieldName}
|
|
79
|
+
render={({ field, ...props }) => (
|
|
80
|
+
<FormItem>
|
|
81
|
+
<FormLabel>{getTranslation(fieldDef.label) ?? field.name}</FormLabel>
|
|
82
|
+
<FormControl>
|
|
83
|
+
{hasCustomFormComponent ? (
|
|
84
|
+
<CustomFormComponent
|
|
85
|
+
fieldDef={fieldDef}
|
|
86
|
+
fieldProps={{
|
|
87
|
+
...props,
|
|
88
|
+
field: {
|
|
89
|
+
...field,
|
|
90
|
+
disabled: fieldDef.readonly ?? false,
|
|
91
|
+
},
|
|
92
|
+
}}
|
|
93
|
+
/>
|
|
94
|
+
) : (
|
|
95
|
+
<FormInputForType fieldDef={fieldDef} field={field} />
|
|
96
|
+
)}
|
|
97
|
+
</FormControl>
|
|
98
|
+
<FormDescription>{getTranslation(fieldDef.description)}</FormDescription>
|
|
99
|
+
<FormMessage />
|
|
100
|
+
</FormItem>
|
|
101
|
+
)}
|
|
102
|
+
/>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// For non-locale fields with custom components
|
|
107
|
+
if (hasCustomFormComponent) {
|
|
108
|
+
return (
|
|
109
|
+
<FormField
|
|
110
|
+
control={control}
|
|
111
|
+
name={fieldName}
|
|
112
|
+
render={fieldProps => (
|
|
113
|
+
<CustomFieldFormItem
|
|
114
|
+
fieldDef={fieldDef}
|
|
115
|
+
getTranslation={getTranslation}
|
|
116
|
+
fieldName={fieldProps.field.name}
|
|
117
|
+
>
|
|
118
|
+
<CustomFormComponent
|
|
119
|
+
fieldDef={fieldDef}
|
|
120
|
+
fieldProps={{
|
|
121
|
+
...fieldProps,
|
|
122
|
+
field: {
|
|
123
|
+
...fieldProps.field,
|
|
124
|
+
disabled: fieldDef.readonly ?? false,
|
|
125
|
+
},
|
|
126
|
+
}}
|
|
127
|
+
/>
|
|
128
|
+
</CustomFieldFormItem>
|
|
129
|
+
)}
|
|
130
|
+
/>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// For regular fields without custom components
|
|
135
|
+
return (
|
|
136
|
+
<FormField
|
|
137
|
+
control={control}
|
|
138
|
+
name={fieldName}
|
|
139
|
+
render={({ field }) => (
|
|
140
|
+
<CustomFieldFormItem
|
|
141
|
+
fieldDef={fieldDef}
|
|
142
|
+
getTranslation={getTranslation}
|
|
143
|
+
fieldName={field.name}
|
|
144
|
+
>
|
|
145
|
+
<FormInputForType fieldDef={fieldDef} field={field} />
|
|
146
|
+
</CustomFieldFormItem>
|
|
147
|
+
)}
|
|
148
|
+
/>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
interface CustomFieldFormItemProps {
|
|
153
|
+
fieldDef: CustomFieldConfig;
|
|
154
|
+
getTranslation: (
|
|
155
|
+
input: Array<{ languageCode: string; value: string }> | null | undefined,
|
|
156
|
+
) => string | undefined;
|
|
157
|
+
fieldName: string;
|
|
158
|
+
children: React.ReactNode;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function CustomFieldFormItem({ fieldDef, getTranslation, fieldName, children }: CustomFieldFormItemProps) {
|
|
162
|
+
return (
|
|
163
|
+
<FormItem>
|
|
164
|
+
<FormLabel>{getTranslation(fieldDef.label) ?? fieldName}</FormLabel>
|
|
165
|
+
<FormControl>{children}</FormControl>
|
|
166
|
+
<FormDescription>{getTranslation(fieldDef.description)}</FormDescription>
|
|
167
|
+
<FormMessage />
|
|
168
|
+
</FormItem>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function FormInputForType({
|
|
173
|
+
fieldDef,
|
|
174
|
+
field,
|
|
175
|
+
}: {
|
|
176
|
+
fieldDef: CustomFieldConfig;
|
|
177
|
+
field: ControllerRenderProps<any, any>;
|
|
178
|
+
}) {
|
|
179
|
+
const isReadonly = fieldDef.readonly ?? false;
|
|
180
|
+
|
|
181
|
+
switch (fieldDef.type as CustomFieldType) {
|
|
77
182
|
case 'string':
|
|
78
|
-
return <Input {...field} />;
|
|
183
|
+
return <Input {...field} disabled={isReadonly} />;
|
|
79
184
|
case 'float':
|
|
80
185
|
case 'int':
|
|
81
|
-
return
|
|
186
|
+
return (
|
|
187
|
+
<Input
|
|
188
|
+
type="number"
|
|
189
|
+
{...field}
|
|
190
|
+
disabled={isReadonly}
|
|
191
|
+
onChange={e => field.onChange(e.target.valueAsNumber)}
|
|
192
|
+
/>
|
|
193
|
+
);
|
|
82
194
|
case 'boolean':
|
|
83
|
-
return <Switch checked={field.value} onCheckedChange={field.onChange} />;
|
|
195
|
+
return <Switch checked={field.value} onCheckedChange={field.onChange} disabled={isReadonly} />;
|
|
84
196
|
default:
|
|
85
|
-
return <Input {...field}
|
|
197
|
+
return <Input {...field} disabled={isReadonly} />;
|
|
86
198
|
}
|
|
87
199
|
}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { api } from '@/graphql/api.js';
|
|
2
|
+
import { graphql } from '@/graphql/graphql.js';
|
|
3
|
+
import { Trans, useLingui } from '@/lib/trans.js';
|
|
4
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
5
|
+
import { useQuery } from '@tanstack/react-query';
|
|
6
|
+
import { useForm } from 'react-hook-form';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { Button } from '../ui/button.js';
|
|
9
|
+
import { Checkbox } from '../ui/checkbox.js';
|
|
3
10
|
import {
|
|
4
11
|
Form,
|
|
5
12
|
FormControl,
|
|
@@ -8,16 +15,9 @@ import {
|
|
|
8
15
|
FormItem,
|
|
9
16
|
FormLabel,
|
|
10
17
|
FormMessage,
|
|
11
|
-
} from '
|
|
12
|
-
import { Input } from '
|
|
13
|
-
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '
|
|
14
|
-
import { api } from '@vendure/dashboard';
|
|
15
|
-
import { graphql } from '@vendure/dashboard';
|
|
16
|
-
import { zodResolver } from '@hookform/resolvers/zod';
|
|
17
|
-
import { Trans, useLingui } from '@/lib/trans.js';
|
|
18
|
-
import { useQuery } from '@tanstack/react-query';
|
|
19
|
-
import { useForm } from 'react-hook-form';
|
|
20
|
-
import { z } from 'zod';
|
|
18
|
+
} from '../ui/form.js';
|
|
19
|
+
import { Input } from '../ui/input.js';
|
|
20
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select.js';
|
|
21
21
|
|
|
22
22
|
// Query document to fetch available countries
|
|
23
23
|
const getAvailableCountriesDocument = graphql(`
|
|
@@ -58,7 +58,12 @@ interface CustomerAddressFormProps<T = any> {
|
|
|
58
58
|
onCancel?: () => void;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
export function CustomerAddressForm<T>({
|
|
61
|
+
export function CustomerAddressForm<T>({
|
|
62
|
+
address,
|
|
63
|
+
setValuesForUpdate,
|
|
64
|
+
onSubmit,
|
|
65
|
+
onCancel,
|
|
66
|
+
}: CustomerAddressFormProps<T>) {
|
|
62
67
|
const { i18n } = useLingui();
|
|
63
68
|
|
|
64
69
|
// Fetch available countries
|
|
@@ -6,8 +6,8 @@ export function LogoMark(props: React.ComponentProps<'svg'>) {
|
|
|
6
6
|
fill="currentColor"
|
|
7
7
|
/>
|
|
8
8
|
<path
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
fillRule="evenodd"
|
|
10
|
+
clipRule="evenodd"
|
|
11
11
|
d="M174.388 4.798a2.148 2.148 0 0 0-2.136 2.157c0 1.191.957 2.158 2.136 2.158a2.149 2.149 0 0 0 2.137-2.158c0-1.19-.958-2.157-2.137-2.157Zm-2.611 2.157c0-1.456 1.169-2.637 2.611-2.637 1.443 0 2.612 1.181 2.612 2.637 0 1.457-1.169 2.638-2.612 2.638-1.442 0-2.611-1.181-2.611-2.638Z"
|
|
12
12
|
fill="currentColor"
|
|
13
13
|
/>
|
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
import { Controller } from 'react-hook-form';
|
|
2
|
-
import { FieldPath } from 'react-hook-form';
|
|
3
1
|
import { useUserSettings } from '@/hooks/use-user-settings.js';
|
|
4
|
-
import { ControllerProps } from 'react-hook-form';
|
|
5
|
-
import {
|
|
2
|
+
import { Controller, ControllerProps, FieldPath, FieldValues } from 'react-hook-form';
|
|
3
|
+
import { FormControl, FormDescription, FormItem, FormLabel, FormMessage } from '../ui/form.js';
|
|
6
4
|
import { FormFieldWrapper } from './form-field-wrapper.js';
|
|
7
|
-
import { FormMessage } from '../ui/form.js';
|
|
8
|
-
import { FormControl } from '../ui/form.js';
|
|
9
|
-
import { FormItem } from '../ui/form.js';
|
|
10
|
-
import { FormDescription, FormField, FormLabel } from '../ui/form.js';
|
|
11
5
|
|
|
12
6
|
export type TranslatableEntity = FieldValues & {
|
|
13
7
|
translations?: Array<{ languageCode: string }> | null;
|
|
@@ -199,6 +199,23 @@ function unwrapVariableDefinitionType(type: TypeNode): NamedTypeNode {
|
|
|
199
199
|
return type;
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
+
/**
|
|
203
|
+
* @description
|
|
204
|
+
* Helper function to get the first field selection from a query operation definition.
|
|
205
|
+
*/
|
|
206
|
+
function getFirstQueryField(documentNode: DocumentNode): FieldNode {
|
|
207
|
+
const operationDefinition = documentNode.definitions.find(
|
|
208
|
+
(def): def is OperationDefinitionNode =>
|
|
209
|
+
def.kind === 'OperationDefinition' && def.operation === 'query',
|
|
210
|
+
);
|
|
211
|
+
const firstSelection = operationDefinition?.selectionSet.selections[0];
|
|
212
|
+
if (firstSelection?.kind === 'Field') {
|
|
213
|
+
return firstSelection;
|
|
214
|
+
} else {
|
|
215
|
+
throw new Error('Could not determine query field');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
202
219
|
/**
|
|
203
220
|
* @description
|
|
204
221
|
* This function is used to get the name of the query from a DocumentNode.
|
|
@@ -216,16 +233,32 @@ function unwrapVariableDefinitionType(type: TypeNode): NamedTypeNode {
|
|
|
216
233
|
* The query name is `product`.
|
|
217
234
|
*/
|
|
218
235
|
export function getQueryName(documentNode: DocumentNode): string {
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
236
|
+
const firstField = getFirstQueryField(documentNode);
|
|
237
|
+
return firstField.name.value;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* @description
|
|
242
|
+
* This function is used to get the entity name from a DocumentNode.
|
|
243
|
+
*
|
|
244
|
+
* For example, in the following query:
|
|
245
|
+
*
|
|
246
|
+
* ```graphql
|
|
247
|
+
* query ProductDetail($id: ID!) {
|
|
248
|
+
* product(id: $id) {
|
|
249
|
+
* ...ProductDetail
|
|
250
|
+
* }
|
|
251
|
+
* }
|
|
252
|
+
* ```
|
|
253
|
+
*
|
|
254
|
+
* The entity name is `Product`.
|
|
255
|
+
*/
|
|
256
|
+
export function getEntityName(documentNode: DocumentNode): string {
|
|
257
|
+
const firstField = getFirstQueryField(documentNode);
|
|
258
|
+
// Get the return type from the field definition
|
|
259
|
+
const fieldName = firstField.name.value;
|
|
260
|
+
const queryInfo = getQueryInfo(fieldName);
|
|
261
|
+
return queryInfo.type;
|
|
229
262
|
}
|
|
230
263
|
|
|
231
264
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { registerDashboardWidget } from '../dashboard-widget/widget-extensions.js';
|
|
2
|
+
import { addCustomFormComponent } from '../form-engine/custom-form-component-extensions.js';
|
|
2
3
|
import {
|
|
3
4
|
registerDashboardActionBarItem,
|
|
4
5
|
registerDashboardPageBlock,
|
|
@@ -76,6 +77,11 @@ export function defineDashboardExtension(extension: DashboardExtension) {
|
|
|
76
77
|
registerDashboardWidget(widget);
|
|
77
78
|
}
|
|
78
79
|
}
|
|
80
|
+
if (extension.customFormComponents) {
|
|
81
|
+
for (const component of extension.customFormComponents) {
|
|
82
|
+
addCustomFormComponent(component);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
79
85
|
const callbacks = globalRegistry.get('extensionSourceChangeCallbacks');
|
|
80
86
|
if (callbacks.size) {
|
|
81
87
|
for (const callback of callbacks) {
|
|
@@ -5,8 +5,21 @@ import type React from 'react';
|
|
|
5
5
|
|
|
6
6
|
import { DashboardAlertDefinition } from '../alert/types.js';
|
|
7
7
|
import { DashboardWidgetDefinition } from '../dashboard-widget/types.js';
|
|
8
|
+
import { CustomFormComponentInputProps } from '../form-engine/custom-form-component.js';
|
|
8
9
|
import { NavMenuItem } from '../nav-menu/nav-menu-extensions.js';
|
|
9
10
|
|
|
11
|
+
/**
|
|
12
|
+
* @description
|
|
13
|
+
* Allows you to define custom form components for custom fields in the dashboard.
|
|
14
|
+
*
|
|
15
|
+
* @docsCategory extensions
|
|
16
|
+
* @since 3.4.0
|
|
17
|
+
*/
|
|
18
|
+
export interface DashboardCustomFormComponent {
|
|
19
|
+
id: string;
|
|
20
|
+
component: React.FunctionComponent<CustomFormComponentInputProps>;
|
|
21
|
+
}
|
|
22
|
+
|
|
10
23
|
export interface DashboardRouteDefinition {
|
|
11
24
|
component: (route: AnyRoute) => React.ReactNode;
|
|
12
25
|
path: string;
|
|
@@ -137,4 +150,10 @@ export interface DashboardExtension {
|
|
|
137
150
|
* given components and optionally also add a nav menu item.
|
|
138
151
|
*/
|
|
139
152
|
widgets?: DashboardWidgetDefinition[];
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @description
|
|
156
|
+
* Allows you to define custom form components for custom fields in the dashboard.
|
|
157
|
+
*/
|
|
158
|
+
customFormComponents?: DashboardCustomFormComponent[];
|
|
140
159
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { DashboardCustomFormComponent } from '../extension-api/extension-api-types.js';
|
|
2
|
+
import { globalRegistry } from '../registry/global-registry.js';
|
|
3
|
+
|
|
4
|
+
import { CustomFormComponentInputProps } from './custom-form-component.js';
|
|
5
|
+
|
|
6
|
+
globalRegistry.register(
|
|
7
|
+
'customFormComponents',
|
|
8
|
+
new Map<string, React.FunctionComponent<CustomFormComponentInputProps>>(),
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
export function getCustomFormComponents() {
|
|
12
|
+
return globalRegistry.get('customFormComponents');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getCustomFormComponent(
|
|
16
|
+
id: string,
|
|
17
|
+
): React.FunctionComponent<CustomFormComponentInputProps> | undefined {
|
|
18
|
+
return globalRegistry.get('customFormComponents').get(id);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function addCustomFormComponent({ id, component }: DashboardCustomFormComponent) {
|
|
22
|
+
const customFormComponents = globalRegistry.get('customFormComponents');
|
|
23
|
+
if (customFormComponents.has(id)) {
|
|
24
|
+
// eslint-disable-next-line no-console
|
|
25
|
+
console.warn(`Custom form component with id "${id}" is already registered and will be overwritten.`);
|
|
26
|
+
}
|
|
27
|
+
customFormComponents.set(id, component);
|
|
28
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { CustomFieldConfig } from '@vendure/common/lib/generated-types';
|
|
2
|
+
import {
|
|
3
|
+
ControllerFieldState,
|
|
4
|
+
ControllerRenderProps,
|
|
5
|
+
FieldPath,
|
|
6
|
+
FieldValues,
|
|
7
|
+
UseFormStateReturn,
|
|
8
|
+
} from 'react-hook-form';
|
|
9
|
+
import { getCustomFormComponent } from './custom-form-component-extensions.js';
|
|
10
|
+
|
|
11
|
+
export interface CustomFormComponentProps {
|
|
12
|
+
fieldProps: CustomFormComponentInputProps;
|
|
13
|
+
fieldDef: Pick<CustomFieldConfig, 'ui' | 'type' | 'name'>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface CustomFormComponentInputProps<
|
|
17
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
18
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
19
|
+
> {
|
|
20
|
+
field: ControllerRenderProps<TFieldValues, TName>;
|
|
21
|
+
fieldState: ControllerFieldState;
|
|
22
|
+
formState: UseFormStateReturn<TFieldValues>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function CustomFormComponent({ fieldDef, fieldProps }: CustomFormComponentProps) {
|
|
26
|
+
const Component = getCustomFormComponent(fieldDef.ui?.component);
|
|
27
|
+
|
|
28
|
+
if (!Component) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return <Component {...fieldProps} />;
|
|
33
|
+
}
|
|
@@ -13,14 +13,18 @@ import { useForm } from 'react-hook-form';
|
|
|
13
13
|
|
|
14
14
|
export interface GeneratedFormOptions<
|
|
15
15
|
T extends TypedDocumentNode<any, any>,
|
|
16
|
-
VarName extends
|
|
16
|
+
VarName extends keyof VariablesOf<T> | undefined = 'input',
|
|
17
17
|
E extends Record<string, any> = Record<string, any>,
|
|
18
18
|
> {
|
|
19
19
|
document?: T;
|
|
20
20
|
varName?: VarName;
|
|
21
21
|
entity: E | null | undefined;
|
|
22
|
-
setValues: (
|
|
23
|
-
|
|
22
|
+
setValues: (
|
|
23
|
+
entity: NonNullable<E>,
|
|
24
|
+
) => VarName extends keyof VariablesOf<T> ? VariablesOf<T>[VarName] : VariablesOf<T>;
|
|
25
|
+
onSubmit?: (
|
|
26
|
+
values: VarName extends keyof VariablesOf<T> ? VariablesOf<T>[VarName] : VariablesOf<T>,
|
|
27
|
+
) => void;
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
/**
|
|
@@ -41,7 +45,7 @@ export function useGeneratedForm<
|
|
|
41
45
|
const updateFields = document ? getOperationVariablesFields(document, varName) : [];
|
|
42
46
|
const schema = createFormSchemaFromFields(updateFields);
|
|
43
47
|
const defaultValues = getDefaultValuesFromFields(updateFields, activeChannel?.defaultLanguageCode);
|
|
44
|
-
const processedEntity = ensureTranslationsForAllLanguages(entity, availableLanguages);
|
|
48
|
+
const processedEntity = ensureTranslationsForAllLanguages(entity, availableLanguages, defaultValues);
|
|
45
49
|
|
|
46
50
|
const form = useForm({
|
|
47
51
|
resolver: async (values, context, options) => {
|
|
@@ -53,7 +57,7 @@ export function useGeneratedForm<
|
|
|
53
57
|
},
|
|
54
58
|
mode: 'onChange',
|
|
55
59
|
defaultValues,
|
|
56
|
-
values: processedEntity ?
|
|
60
|
+
values: processedEntity ? processedEntity : defaultValues,
|
|
57
61
|
});
|
|
58
62
|
let submitHandler = (event: FormEvent) => {
|
|
59
63
|
event.preventDefault();
|
|
@@ -69,11 +73,13 @@ export function useGeneratedForm<
|
|
|
69
73
|
|
|
70
74
|
/**
|
|
71
75
|
* Ensures that an entity with translations has entries for all available languages.
|
|
72
|
-
* If a language is missing, it creates an empty translation based on the structure of existing translations
|
|
76
|
+
* If a language is missing, it creates an empty translation based on the structure of existing translations
|
|
77
|
+
* and the expected form structure from defaultValues.
|
|
73
78
|
*/
|
|
74
79
|
function ensureTranslationsForAllLanguages<E extends Record<string, any>>(
|
|
75
80
|
entity: E | null | undefined,
|
|
76
81
|
availableLanguages: string[] = [],
|
|
82
|
+
expectedStructure?: Record<string, any>,
|
|
77
83
|
): E | null | undefined {
|
|
78
84
|
if (
|
|
79
85
|
!entity ||
|
|
@@ -91,23 +97,56 @@ function ensureTranslationsForAllLanguages<E extends Record<string, any>>(
|
|
|
91
97
|
// Get existing language codes
|
|
92
98
|
const existingLanguageCodes = new Set(translations.map((t: any) => t.languageCode));
|
|
93
99
|
|
|
100
|
+
// Get the expected translation structure from defaultValues or existing translations
|
|
101
|
+
const existingTemplate = translations[0] || {};
|
|
102
|
+
const expectedTranslationStructure = expectedStructure?.translations?.[0] || {};
|
|
103
|
+
|
|
104
|
+
// Merge the structures to ensure we have all expected fields
|
|
105
|
+
const templateStructure = {
|
|
106
|
+
...expectedTranslationStructure,
|
|
107
|
+
...existingTemplate,
|
|
108
|
+
};
|
|
109
|
+
|
|
94
110
|
// Add missing language translations
|
|
95
111
|
for (const langCode of availableLanguages) {
|
|
96
112
|
if (!existingLanguageCodes.has(langCode)) {
|
|
97
|
-
// Find a translation to use as template for field structure
|
|
98
|
-
const template = translations[0] || {};
|
|
99
113
|
const emptyTranslation: Record<string, any> = {
|
|
100
114
|
languageCode: langCode,
|
|
101
115
|
};
|
|
102
116
|
|
|
103
|
-
// Add empty fields based on template (excluding languageCode)
|
|
104
|
-
Object.keys(
|
|
117
|
+
// Add empty fields based on merged template structure (excluding languageCode)
|
|
118
|
+
Object.keys(templateStructure).forEach(key => {
|
|
105
119
|
if (key !== 'languageCode') {
|
|
106
|
-
|
|
120
|
+
if (typeof templateStructure[key] === 'object' && templateStructure[key] !== null) {
|
|
121
|
+
// For nested objects like customFields, create an empty object
|
|
122
|
+
emptyTranslation[key] = Array.isArray(templateStructure[key]) ? [] : {};
|
|
123
|
+
} else {
|
|
124
|
+
// For primitive values, use empty string as default
|
|
125
|
+
emptyTranslation[key] = '';
|
|
126
|
+
}
|
|
107
127
|
}
|
|
108
128
|
});
|
|
109
129
|
|
|
110
130
|
translations.push(emptyTranslation);
|
|
131
|
+
} else {
|
|
132
|
+
// For existing translations, ensure they have all expected fields
|
|
133
|
+
const existingTranslation = translations.find((t: any) => t.languageCode === langCode);
|
|
134
|
+
if (existingTranslation) {
|
|
135
|
+
Object.keys(expectedTranslationStructure).forEach(key => {
|
|
136
|
+
if (key !== 'languageCode' && !(key in existingTranslation)) {
|
|
137
|
+
if (
|
|
138
|
+
typeof expectedTranslationStructure[key] === 'object' &&
|
|
139
|
+
expectedTranslationStructure[key] !== null
|
|
140
|
+
) {
|
|
141
|
+
existingTranslation[key] = Array.isArray(expectedTranslationStructure[key])
|
|
142
|
+
? []
|
|
143
|
+
: {};
|
|
144
|
+
} else {
|
|
145
|
+
existingTranslation[key] = '';
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
111
150
|
}
|
|
112
151
|
}
|
|
113
152
|
|
|
@@ -10,8 +10,12 @@ import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
|
|
|
10
10
|
import { AnyRoute, useNavigate } from '@tanstack/react-router';
|
|
11
11
|
import { ResultOf, VariablesOf } from 'gql.tada';
|
|
12
12
|
import { toast } from 'sonner';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
getEntityName,
|
|
15
|
+
getOperationVariablesFields,
|
|
16
|
+
} from '../document-introspection/get-document-structure.js';
|
|
14
17
|
|
|
18
|
+
import { TranslatableFormFieldWrapper } from '@/components/shared/translatable-form-field.js';
|
|
15
19
|
import {
|
|
16
20
|
CustomFieldsPageBlock,
|
|
17
21
|
DetailFormGrid,
|
|
@@ -41,6 +45,7 @@ export interface DetailPageProps<
|
|
|
41
45
|
/**
|
|
42
46
|
* @description
|
|
43
47
|
* The name of the entity.
|
|
48
|
+
* If not provided, it will be inferred from the query document.
|
|
44
49
|
*/
|
|
45
50
|
entityName?: string;
|
|
46
51
|
/**
|
|
@@ -80,6 +85,29 @@ export interface DetailPageProps<
|
|
|
80
85
|
setValuesForUpdate: (entity: ResultOf<T>[EntityField]) => VariablesOf<U>['input'];
|
|
81
86
|
}
|
|
82
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Renders form input components based on field type
|
|
90
|
+
*/
|
|
91
|
+
function renderFieldInput(fieldInfo: { type: string }, field: any) {
|
|
92
|
+
switch (fieldInfo.type) {
|
|
93
|
+
case 'Int':
|
|
94
|
+
case 'Float':
|
|
95
|
+
return (
|
|
96
|
+
<Input
|
|
97
|
+
type="number"
|
|
98
|
+
value={field.value}
|
|
99
|
+
onChange={e => field.onChange(e.target.valueAsNumber)}
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
case 'DateTime':
|
|
103
|
+
return <DateTimeInput {...field} />;
|
|
104
|
+
case 'Boolean':
|
|
105
|
+
return <Checkbox value={field.value} onCheckedChange={field.onChange} />;
|
|
106
|
+
default:
|
|
107
|
+
return <Input {...field} />;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
83
111
|
/**
|
|
84
112
|
* @description
|
|
85
113
|
* **Status: Developer Preview**
|
|
@@ -100,7 +128,7 @@ export function DetailPage<
|
|
|
100
128
|
>({
|
|
101
129
|
pageId,
|
|
102
130
|
route,
|
|
103
|
-
entityName,
|
|
131
|
+
entityName: passedEntityName,
|
|
104
132
|
queryDocument,
|
|
105
133
|
createDocument,
|
|
106
134
|
updateDocument,
|
|
@@ -110,11 +138,15 @@ export function DetailPage<
|
|
|
110
138
|
const params = route.useParams();
|
|
111
139
|
const creatingNewEntity = params.id === NEW_ENTITY_PATH;
|
|
112
140
|
const navigate = useNavigate();
|
|
141
|
+
const inferredEntityName = getEntityName(queryDocument);
|
|
142
|
+
|
|
143
|
+
const entityName = passedEntityName ?? inferredEntityName;
|
|
113
144
|
|
|
114
145
|
const { form, submitHandler, entity, isPending, resetForm } = useDetailPage<any, any, any>({
|
|
115
146
|
queryDocument,
|
|
116
147
|
updateDocument,
|
|
117
148
|
createDocument,
|
|
149
|
+
entityName,
|
|
118
150
|
params: { id: params.id },
|
|
119
151
|
setValuesForUpdate,
|
|
120
152
|
onSuccess: async data => {
|
|
@@ -133,6 +165,7 @@ export function DetailPage<
|
|
|
133
165
|
});
|
|
134
166
|
|
|
135
167
|
const updateFields = getOperationVariablesFields(updateDocument, 'input');
|
|
168
|
+
const translations = updateFields.find(fieldInfo => fieldInfo.name === 'translations');
|
|
136
169
|
|
|
137
170
|
return (
|
|
138
171
|
<Page pageId={pageId} form={form} submitHandler={submitHandler}>
|
|
@@ -152,6 +185,7 @@ export function DetailPage<
|
|
|
152
185
|
<DetailFormGrid>
|
|
153
186
|
{updateFields
|
|
154
187
|
.filter(fieldInfo => fieldInfo.name !== 'customFields')
|
|
188
|
+
.filter(fieldInfo => fieldInfo.name !== 'translations')
|
|
155
189
|
.map(fieldInfo => {
|
|
156
190
|
if (fieldInfo.name === 'id' && fieldInfo.type === 'ID') {
|
|
157
191
|
return null;
|
|
@@ -162,33 +196,22 @@ export function DetailPage<
|
|
|
162
196
|
control={form.control}
|
|
163
197
|
name={fieldInfo.name as never}
|
|
164
198
|
label={fieldInfo.name}
|
|
165
|
-
render={({ field }) =>
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
return (
|
|
182
|
-
<Checkbox
|
|
183
|
-
value={field.value}
|
|
184
|
-
onCheckedChange={field.onChange}
|
|
185
|
-
/>
|
|
186
|
-
);
|
|
187
|
-
case 'String':
|
|
188
|
-
default:
|
|
189
|
-
return <Input {...field} />;
|
|
190
|
-
}
|
|
191
|
-
}}
|
|
199
|
+
render={({ field }) => renderFieldInput(fieldInfo, field)}
|
|
200
|
+
/>
|
|
201
|
+
);
|
|
202
|
+
})}
|
|
203
|
+
{translations?.typeInfo
|
|
204
|
+
?.filter(
|
|
205
|
+
fieldInfo => !['customFields', 'id', 'languageCode'].includes(fieldInfo.name),
|
|
206
|
+
)
|
|
207
|
+
.map(fieldInfo => {
|
|
208
|
+
return (
|
|
209
|
+
<TranslatableFormFieldWrapper
|
|
210
|
+
key={fieldInfo.name}
|
|
211
|
+
control={form.control}
|
|
212
|
+
name={fieldInfo.name as never}
|
|
213
|
+
label={fieldInfo.name}
|
|
214
|
+
render={({ field }) => renderFieldInput(fieldInfo, field)}
|
|
192
215
|
/>
|
|
193
216
|
);
|
|
194
217
|
})}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { NEW_ENTITY_PATH } from '@/constants.js';
|
|
2
2
|
import { api, Variables } from '@/graphql/api.js';
|
|
3
|
+
import { useCustomFieldConfig } from '@/hooks/use-custom-field-config.js';
|
|
4
|
+
import { removeReadonlyCustomFields } from '@/lib/utils.js';
|
|
3
5
|
import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
|
|
4
6
|
import {
|
|
5
7
|
DefinedInitialDataOptions,
|
|
@@ -59,6 +61,12 @@ export interface DetailPageOptions<
|
|
|
59
61
|
params: {
|
|
60
62
|
id: string;
|
|
61
63
|
};
|
|
64
|
+
/**
|
|
65
|
+
* @description
|
|
66
|
+
* The entity type name for custom field configuration lookup.
|
|
67
|
+
* Required to filter out readonly custom fields before mutations.
|
|
68
|
+
*/
|
|
69
|
+
entityName: string;
|
|
62
70
|
/**
|
|
63
71
|
* @description
|
|
64
72
|
* The document to create the entity.
|
|
@@ -232,16 +240,19 @@ export function useDetailPage<
|
|
|
232
240
|
transformUpdateInput,
|
|
233
241
|
params,
|
|
234
242
|
entityField,
|
|
243
|
+
entityName,
|
|
235
244
|
onSuccess,
|
|
236
245
|
onError,
|
|
237
246
|
} = options;
|
|
238
247
|
const isNew = params.id === NEW_ENTITY_PATH;
|
|
239
248
|
const queryClient = useQueryClient();
|
|
249
|
+
const customFieldConfig = useCustomFieldConfig(entityName);
|
|
240
250
|
const detailQueryOptions = getDetailQueryOptions(addCustomFields(queryDocument), {
|
|
241
251
|
id: isNew ? '__NEW__' : params.id,
|
|
242
252
|
});
|
|
243
253
|
const detailQuery = useSuspenseQuery(detailQueryOptions);
|
|
244
254
|
const entityQueryField = entityField ?? getQueryName(queryDocument);
|
|
255
|
+
|
|
245
256
|
const entity = (detailQuery?.data as any)[entityQueryField] as
|
|
246
257
|
| DetailPageEntity<T, EntityField>
|
|
247
258
|
| undefined;
|
|
@@ -280,10 +291,15 @@ export function useDetailPage<
|
|
|
280
291
|
entity,
|
|
281
292
|
setValues: setValuesForUpdate,
|
|
282
293
|
onSubmit(values: any) {
|
|
294
|
+
// Filter out readonly custom fields before submitting
|
|
295
|
+
const filteredValues = removeReadonlyCustomFields(values, customFieldConfig || []);
|
|
296
|
+
|
|
283
297
|
if (isNew) {
|
|
284
|
-
|
|
298
|
+
const finalInput = transformCreateInput?.(filteredValues) ?? filteredValues;
|
|
299
|
+
createMutation.mutate({ input: finalInput });
|
|
285
300
|
} else {
|
|
286
|
-
|
|
301
|
+
const finalInput = transformUpdateInput?.(filteredValues) ?? filteredValues;
|
|
302
|
+
updateMutation.mutate({ input: finalInput });
|
|
287
303
|
}
|
|
288
304
|
},
|
|
289
305
|
});
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
DashboardActionBarItem,
|
|
5
5
|
DashboardPageBlockDefinition,
|
|
6
6
|
} from '../extension-api/extension-api-types.js';
|
|
7
|
+
import { CustomFormComponentInputProps } from '../form-engine/custom-form-component.js';
|
|
7
8
|
import { NavMenuConfig } from '../nav-menu/nav-menu-extensions.js';
|
|
8
9
|
|
|
9
10
|
export interface GlobalRegistryContents {
|
|
@@ -14,6 +15,7 @@ export interface GlobalRegistryContents {
|
|
|
14
15
|
dashboardPageBlockRegistry: Map<string, DashboardPageBlockDefinition[]>;
|
|
15
16
|
dashboardWidgetRegistry: Map<string, DashboardWidgetDefinition>;
|
|
16
17
|
dashboardAlertRegistry: Map<string, DashboardAlertDefinition>;
|
|
18
|
+
customFormComponents: Map<string, React.FunctionComponent<CustomFormComponentInputProps>>;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
export type GlobalRegistryKey = keyof GlobalRegistryContents;
|
|
@@ -291,13 +291,15 @@ export type introspection_types = {
|
|
|
291
291
|
'ProductOptionInUseError': { kind: 'OBJECT'; name: 'ProductOptionInUseError'; fields: { 'errorCode': { name: 'errorCode'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'ErrorCode'; ofType: null; }; } }; 'message': { name: 'message'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'optionGroupCode': { name: 'optionGroupCode'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'productVariantCount': { name: 'productVariantCount'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; };
|
|
292
292
|
'ProductOptionTranslation': { kind: 'OBJECT'; name: 'ProductOptionTranslation'; fields: { 'createdAt': { name: 'createdAt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'languageCode': { name: 'languageCode'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'LanguageCode'; ofType: null; }; } }; 'name': { name: 'name'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; }; };
|
|
293
293
|
'ProductOptionTranslationInput': { kind: 'INPUT_OBJECT'; name: 'ProductOptionTranslationInput'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'languageCode'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'LanguageCode'; ofType: null; }; }; defaultValue: null }, { name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'customFields'; type: { kind: 'SCALAR'; name: 'JSON'; ofType: null; }; defaultValue: null }]; };
|
|
294
|
-
'ProductReview': { kind: 'OBJECT'; name: 'ProductReview'; fields: { 'author': { name: 'author'; type: { kind: 'OBJECT'; name: 'Customer'; ofType: null; } }; 'authorLocation': { name: 'authorLocation'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'authorName': { name: 'authorName'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'body': { name: 'body'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'createdAt': { name: 'createdAt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; 'customFields': { name: 'customFields'; type: { kind: 'OBJECT'; name: 'ProductReviewCustomFields'; ofType: null; } }; 'downvotes': { name: 'downvotes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'product': { name: 'product'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Product'; ofType: null; }; } }; 'productVariant': { name: 'productVariant'; type: { kind: 'OBJECT'; name: 'ProductVariant'; ofType: null; } }; 'rating': { name: 'rating'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; } }; 'response': { name: 'response'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'responseCreatedAt': { name: 'responseCreatedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'state': { name: 'state'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'summary': { name: 'summary'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; 'upvotes': { name: 'upvotes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; };
|
|
295
|
-
'ProductReviewCustomFields': { kind: 'OBJECT'; name: 'ProductReviewCustomFields'; fields: { 'reviewerName': { name: 'reviewerName'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; };
|
|
296
|
-
'ProductReviewFilterParameter': { kind: 'INPUT_OBJECT'; name: 'ProductReviewFilterParameter'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'INPUT_OBJECT'; name: 'IDOperators'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateOperators'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateOperators'; ofType: null; }; defaultValue: null }, { name: 'summary'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'body'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'rating'; type: { kind: 'INPUT_OBJECT'; name: 'NumberOperators'; ofType: null; }; defaultValue: null }, { name: 'authorName'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'authorLocation'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'upvotes'; type: { kind: 'INPUT_OBJECT'; name: 'NumberOperators'; ofType: null; }; defaultValue: null }, { name: 'downvotes'; type: { kind: 'INPUT_OBJECT'; name: 'NumberOperators'; ofType: null; }; defaultValue: null }, { name: 'state'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'response'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'responseCreatedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateOperators'; ofType: null; }; defaultValue: null }, { name: '_and'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ProductReviewFilterParameter'; ofType: null; }; }; }; defaultValue: null }, { name: '_or'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ProductReviewFilterParameter'; ofType: null; }; }; }; defaultValue: null }, { name: 'reviewerName'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }]; };
|
|
294
|
+
'ProductReview': { kind: 'OBJECT'; name: 'ProductReview'; fields: { 'author': { name: 'author'; type: { kind: 'OBJECT'; name: 'Customer'; ofType: null; } }; 'authorLocation': { name: 'authorLocation'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'authorName': { name: 'authorName'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'body': { name: 'body'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'createdAt': { name: 'createdAt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; 'customFields': { name: 'customFields'; type: { kind: 'OBJECT'; name: 'ProductReviewCustomFields'; ofType: null; } }; 'downvotes': { name: 'downvotes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'product': { name: 'product'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Product'; ofType: null; }; } }; 'productVariant': { name: 'productVariant'; type: { kind: 'OBJECT'; name: 'ProductVariant'; ofType: null; } }; 'rating': { name: 'rating'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; } }; 'response': { name: 'response'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'responseCreatedAt': { name: 'responseCreatedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'state': { name: 'state'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'summary': { name: 'summary'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'translations': { name: 'translations'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ProductReviewTranslation'; ofType: null; }; }; }; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; 'upvotes': { name: 'upvotes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; };
|
|
295
|
+
'ProductReviewCustomFields': { kind: 'OBJECT'; name: 'ProductReviewCustomFields'; fields: { 'reviewerName': { name: 'reviewerName'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'verifiedReviewerName': { name: 'verifiedReviewerName'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; };
|
|
296
|
+
'ProductReviewFilterParameter': { kind: 'INPUT_OBJECT'; name: 'ProductReviewFilterParameter'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'INPUT_OBJECT'; name: 'IDOperators'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateOperators'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateOperators'; ofType: null; }; defaultValue: null }, { name: 'summary'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'body'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'rating'; type: { kind: 'INPUT_OBJECT'; name: 'NumberOperators'; ofType: null; }; defaultValue: null }, { name: 'authorName'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'authorLocation'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'upvotes'; type: { kind: 'INPUT_OBJECT'; name: 'NumberOperators'; ofType: null; }; defaultValue: null }, { name: 'downvotes'; type: { kind: 'INPUT_OBJECT'; name: 'NumberOperators'; ofType: null; }; defaultValue: null }, { name: 'state'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'response'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'responseCreatedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateOperators'; ofType: null; }; defaultValue: null }, { name: '_and'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ProductReviewFilterParameter'; ofType: null; }; }; }; defaultValue: null }, { name: '_or'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ProductReviewFilterParameter'; ofType: null; }; }; }; defaultValue: null }, { name: 'reviewerName'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'verifiedReviewerName'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }]; };
|
|
297
297
|
'ProductReviewHistogramItem': { kind: 'OBJECT'; name: 'ProductReviewHistogramItem'; fields: { 'bin': { name: 'bin'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'frequency': { name: 'frequency'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; };
|
|
298
298
|
'ProductReviewList': { kind: 'OBJECT'; name: 'ProductReviewList'; fields: { 'items': { name: 'items'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ProductReview'; ofType: null; }; }; }; } }; 'totalItems': { name: 'totalItems'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; };
|
|
299
299
|
'ProductReviewListOptions': { kind: 'INPUT_OBJECT'; name: 'ProductReviewListOptions'; isOneOf: false; inputFields: [{ name: 'skip'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'take'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'sort'; type: { kind: 'INPUT_OBJECT'; name: 'ProductReviewSortParameter'; ofType: null; }; defaultValue: null }, { name: 'filter'; type: { kind: 'INPUT_OBJECT'; name: 'ProductReviewFilterParameter'; ofType: null; }; defaultValue: null }, { name: 'filterOperator'; type: { kind: 'ENUM'; name: 'LogicalOperator'; ofType: null; }; defaultValue: null }]; };
|
|
300
|
-
'ProductReviewSortParameter': { kind: 'INPUT_OBJECT'; name: 'ProductReviewSortParameter'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'summary'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'body'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'rating'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'authorName'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'authorLocation'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'upvotes'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'downvotes'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'state'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'response'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'responseCreatedAt'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'reviewerName'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }]; };
|
|
300
|
+
'ProductReviewSortParameter': { kind: 'INPUT_OBJECT'; name: 'ProductReviewSortParameter'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'summary'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'body'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'rating'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'authorName'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'authorLocation'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'upvotes'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'downvotes'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'state'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'response'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'responseCreatedAt'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'reviewerName'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'verifiedReviewerName'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }]; };
|
|
301
|
+
'ProductReviewTranslation': { kind: 'OBJECT'; name: 'ProductReviewTranslation'; fields: { 'customFields': { name: 'customFields'; type: { kind: 'OBJECT'; name: 'ProductReviewTranslationCustomFields'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'languageCode': { name: 'languageCode'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'LanguageCode'; ofType: null; }; } }; 'text': { name: 'text'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; };
|
|
302
|
+
'ProductReviewTranslationCustomFields': { kind: 'OBJECT'; name: 'ProductReviewTranslationCustomFields'; fields: { 'reviewerName': { name: 'reviewerName'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; };
|
|
301
303
|
'ProductSortParameter': { kind: 'INPUT_OBJECT'; name: 'ProductSortParameter'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'name'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'slug'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'description'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'infoUrl'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'downloadable'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'shortName'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'lastUpdated'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'reviewRating'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'reviewCount'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'featuredReview'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }]; };
|
|
302
304
|
'ProductTranslation': { kind: 'OBJECT'; name: 'ProductTranslation'; fields: { 'createdAt': { name: 'createdAt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; 'customFields': { name: 'customFields'; type: { kind: 'OBJECT'; name: 'ProductTranslationCustomFields'; ofType: null; } }; 'description': { name: 'description'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'languageCode': { name: 'languageCode'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'LanguageCode'; ofType: null; }; } }; 'name': { name: 'name'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'slug': { name: 'slug'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; }; };
|
|
303
305
|
'ProductTranslationCustomFields': { kind: 'OBJECT'; name: 'ProductTranslationCustomFields'; fields: { 'shortName': { name: 'shortName'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; };
|
|
@@ -465,8 +467,7 @@ export type introspection_types = {
|
|
|
465
467
|
'UpdateProductInput': { kind: 'INPUT_OBJECT'; name: 'UpdateProductInput'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'enabled'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'featuredAssetId'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'assetIds'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; }; defaultValue: null }, { name: 'facetValueIds'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; }; defaultValue: null }, { name: 'translations'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ProductTranslationInput'; ofType: null; }; }; }; defaultValue: null }, { name: 'customFields'; type: { kind: 'INPUT_OBJECT'; name: 'UpdateProductCustomFieldsInput'; ofType: null; }; defaultValue: null }]; };
|
|
466
468
|
'UpdateProductOptionGroupInput': { kind: 'INPUT_OBJECT'; name: 'UpdateProductOptionGroupInput'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'code'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'translations'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ProductOptionGroupTranslationInput'; ofType: null; }; }; }; defaultValue: null }, { name: 'customFields'; type: { kind: 'SCALAR'; name: 'JSON'; ofType: null; }; defaultValue: null }]; };
|
|
467
469
|
'UpdateProductOptionInput': { kind: 'INPUT_OBJECT'; name: 'UpdateProductOptionInput'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'code'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'translations'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ProductOptionGroupTranslationInput'; ofType: null; }; }; }; defaultValue: null }, { name: 'customFields'; type: { kind: 'SCALAR'; name: 'JSON'; ofType: null; }; defaultValue: null }]; };
|
|
468
|
-
'
|
|
469
|
-
'UpdateProductReviewInput': { kind: 'INPUT_OBJECT'; name: 'UpdateProductReviewInput'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'summary'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'body'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'response'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'customFields'; type: { kind: 'INPUT_OBJECT'; name: 'UpdateProductReviewCustomFieldsInput'; ofType: null; }; defaultValue: null }]; };
|
|
470
|
+
'UpdateProductReviewInput': { kind: 'INPUT_OBJECT'; name: 'UpdateProductReviewInput'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'summary'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'body'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'response'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'customFields'; type: { kind: 'SCALAR'; name: 'JSON'; ofType: null; }; defaultValue: null }]; };
|
|
470
471
|
'UpdateProductVariantInput': { kind: 'INPUT_OBJECT'; name: 'UpdateProductVariantInput'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'enabled'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'translations'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ProductVariantTranslationInput'; ofType: null; }; }; }; defaultValue: null }, { name: 'facetValueIds'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; }; defaultValue: null }, { name: 'optionIds'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; }; defaultValue: null }, { name: 'sku'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'taxCategoryId'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'price'; type: { kind: 'SCALAR'; name: 'Money'; ofType: null; }; defaultValue: null }, { name: 'prices'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'UpdateProductVariantPriceInput'; ofType: null; }; }; }; defaultValue: null }, { name: 'featuredAssetId'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'assetIds'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; }; defaultValue: null }, { name: 'stockOnHand'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'stockLevels'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'StockLevelInput'; ofType: null; }; }; }; defaultValue: null }, { name: 'outOfStockThreshold'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'useGlobalOutOfStockThreshold'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'trackInventory'; type: { kind: 'ENUM'; name: 'GlobalFlag'; ofType: null; }; defaultValue: null }, { name: 'customFields'; type: { kind: 'SCALAR'; name: 'JSON'; ofType: null; }; defaultValue: null }]; };
|
|
471
472
|
'UpdateProductVariantPriceInput': { kind: 'INPUT_OBJECT'; name: 'UpdateProductVariantPriceInput'; isOneOf: false; inputFields: [{ name: 'currencyCode'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'CurrencyCode'; ofType: null; }; }; defaultValue: null }, { name: 'price'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Money'; ofType: null; }; }; defaultValue: null }, { name: 'delete'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'customFields'; type: { kind: 'SCALAR'; name: 'JSON'; ofType: null; }; defaultValue: null }]; };
|
|
472
473
|
'UpdatePromotionInput': { kind: 'INPUT_OBJECT'; name: 'UpdatePromotionInput'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'enabled'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'startsAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; defaultValue: null }, { name: 'endsAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; defaultValue: null }, { name: 'couponCode'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'perCustomerUsageLimit'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'usageLimit'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'conditions'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ConfigurableOperationInput'; ofType: null; }; }; }; defaultValue: null }, { name: 'actions'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ConfigurableOperationInput'; ofType: null; }; }; }; defaultValue: null }, { name: 'translations'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'PromotionTranslationInput'; ofType: null; }; }; }; defaultValue: null }, { name: 'customFields'; type: { kind: 'SCALAR'; name: 'JSON'; ofType: null; }; defaultValue: null }]; };
|
package/src/lib/index.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// This file is auto-generated. Do not edit manually.
|
|
2
|
-
// Generated on: 2025-03-28T08:23:55.325Z
|
|
3
2
|
|
|
4
3
|
export * from './components/data-display/boolean.js';
|
|
5
4
|
export * from './components/data-display/date-time.js';
|
|
5
|
+
export * from './components/data-display/json.js';
|
|
6
6
|
export * from './components/data-display/money.js';
|
|
7
7
|
export * from './components/data-input/affixed-input.js';
|
|
8
8
|
export * from './components/data-input/customer-group-input.js';
|
|
@@ -10,12 +10,22 @@ export * from './components/data-input/datetime-input.js';
|
|
|
10
10
|
export * from './components/data-input/facet-value-input.js';
|
|
11
11
|
export * from './components/data-input/money-input.js';
|
|
12
12
|
export * from './components/data-input/richt-text-input.js';
|
|
13
|
+
export * from './components/data-table/add-filter-menu.js';
|
|
13
14
|
export * from './components/data-table/data-table-column-header.js';
|
|
14
15
|
export * from './components/data-table/data-table-faceted-filter.js';
|
|
16
|
+
export * from './components/data-table/data-table-filter-badge.js';
|
|
15
17
|
export * from './components/data-table/data-table-filter-dialog.js';
|
|
16
18
|
export * from './components/data-table/data-table-pagination.js';
|
|
19
|
+
export * from './components/data-table/data-table-types.js';
|
|
17
20
|
export * from './components/data-table/data-table-view-options.js';
|
|
18
21
|
export * from './components/data-table/data-table.js';
|
|
22
|
+
export * from './components/data-table/filters/data-table-boolean-filter.js';
|
|
23
|
+
export * from './components/data-table/filters/data-table-datetime-filter.js';
|
|
24
|
+
export * from './components/data-table/filters/data-table-id-filter.js';
|
|
25
|
+
export * from './components/data-table/filters/data-table-number-filter.js';
|
|
26
|
+
export * from './components/data-table/filters/data-table-string-filter.js';
|
|
27
|
+
export * from './components/data-table/human-readable-operator.js';
|
|
28
|
+
export * from './components/data-table/refresh-button.js';
|
|
19
29
|
export * from './components/layout/app-layout.js';
|
|
20
30
|
export * from './components/layout/app-sidebar.js';
|
|
21
31
|
export * from './components/layout/channel-switcher.js';
|
|
@@ -25,13 +35,17 @@ export * from './components/layout/language-dialog.js';
|
|
|
25
35
|
export * from './components/layout/nav-main.js';
|
|
26
36
|
export * from './components/layout/nav-projects.js';
|
|
27
37
|
export * from './components/layout/nav-user.js';
|
|
38
|
+
export * from './components/layout/prerelease-popup.js';
|
|
28
39
|
export * from './components/login/login-form.js';
|
|
29
40
|
export * from './components/shared/alerts.js';
|
|
30
41
|
export * from './components/shared/animated-number.js';
|
|
42
|
+
export * from './components/shared/asset/asset-focal-point-editor.js';
|
|
31
43
|
export * from './components/shared/asset/asset-gallery.js';
|
|
32
44
|
export * from './components/shared/asset/asset-picker-dialog.js';
|
|
33
45
|
export * from './components/shared/asset/asset-preview-dialog.js';
|
|
46
|
+
export * from './components/shared/asset/asset-preview-selector.js';
|
|
34
47
|
export * from './components/shared/asset/asset-preview.js';
|
|
48
|
+
export * from './components/shared/asset/asset-properties.js';
|
|
35
49
|
export * from './components/shared/asset/focal-point-control.js';
|
|
36
50
|
export * from './components/shared/assigned-facet-values.js';
|
|
37
51
|
export * from './components/shared/channel-code-label.js';
|
|
@@ -43,6 +57,7 @@ export * from './components/shared/copyable-text.js';
|
|
|
43
57
|
export * from './components/shared/country-selector.js';
|
|
44
58
|
export * from './components/shared/currency-selector.js';
|
|
45
59
|
export * from './components/shared/custom-fields-form.js';
|
|
60
|
+
export * from './components/shared/customer-address-form.js';
|
|
46
61
|
export * from './components/shared/customer-group-chip.js';
|
|
47
62
|
export * from './components/shared/customer-group-selector.js';
|
|
48
63
|
export * from './components/shared/customer-selector.js';
|
|
@@ -61,8 +76,11 @@ export * from './components/shared/icon-mark.js';
|
|
|
61
76
|
export * from './components/shared/language-selector.js';
|
|
62
77
|
export * from './components/shared/logo-mark.js';
|
|
63
78
|
export * from './components/shared/multi-select.js';
|
|
79
|
+
export * from './components/shared/navigation-confirmation.js';
|
|
80
|
+
export * from './components/shared/option-value-input.js';
|
|
64
81
|
export * from './components/shared/paginated-list-data-table.js';
|
|
65
82
|
export * from './components/shared/permission-guard.js';
|
|
83
|
+
export * from './components/shared/product-variant-selector.js';
|
|
66
84
|
export * from './components/shared/rich-text-editor.js';
|
|
67
85
|
export * from './components/shared/role-code-label.js';
|
|
68
86
|
export * from './components/shared/role-selector.js';
|
|
@@ -103,6 +121,10 @@ export * from './components/ui/table.js';
|
|
|
103
121
|
export * from './components/ui/tabs.js';
|
|
104
122
|
export * from './components/ui/textarea.js';
|
|
105
123
|
export * from './components/ui/tooltip.js';
|
|
124
|
+
export * from './framework/alert/alert-extensions.js';
|
|
125
|
+
export * from './framework/alert/alert-item.js';
|
|
126
|
+
export * from './framework/alert/alerts-indicator.js';
|
|
127
|
+
export * from './framework/alert/types.js';
|
|
106
128
|
export * from './framework/component-registry/component-registry.js';
|
|
107
129
|
export * from './framework/component-registry/dynamic-component.js';
|
|
108
130
|
export * from './framework/dashboard-widget/base-widget.js';
|
|
@@ -122,11 +144,14 @@ export * from './framework/document-introspection/hooks.js';
|
|
|
122
144
|
export * from './framework/extension-api/define-dashboard-extension.js';
|
|
123
145
|
export * from './framework/extension-api/extension-api-types.js';
|
|
124
146
|
export * from './framework/extension-api/use-dashboard-extensions.js';
|
|
147
|
+
export * from './framework/form-engine/custom-form-component-extensions.js';
|
|
148
|
+
export * from './framework/form-engine/custom-form-component.js';
|
|
125
149
|
export * from './framework/form-engine/form-schema-tools.js';
|
|
126
150
|
export * from './framework/form-engine/use-generated-form.js';
|
|
127
151
|
export * from './framework/layout-engine/layout-extensions.js';
|
|
128
152
|
export * from './framework/layout-engine/location-wrapper.js';
|
|
129
153
|
export * from './framework/layout-engine/page-layout.js';
|
|
154
|
+
export * from './framework/layout-engine/page-provider.js';
|
|
130
155
|
export * from './framework/nav-menu/nav-menu-extensions.js';
|
|
131
156
|
export * from './framework/page/detail-page-route-loader.js';
|
|
132
157
|
export * from './framework/page/detail-page.js';
|
|
@@ -135,6 +160,8 @@ export * from './framework/page/page-api.js';
|
|
|
135
160
|
export * from './framework/page/page-types.js';
|
|
136
161
|
export * from './framework/page/use-detail-page.js';
|
|
137
162
|
export * from './framework/page/use-extended-router.js';
|
|
163
|
+
export * from './framework/registry/global-registry.js';
|
|
164
|
+
export * from './framework/registry/registry-types.js';
|
|
138
165
|
export * from './hooks/use-auth.js';
|
|
139
166
|
export * from './hooks/use-channel.js';
|
|
140
167
|
export * from './hooks/use-custom-field-config.js';
|
|
@@ -146,4 +173,5 @@ export * from './hooks/use-permissions.js';
|
|
|
146
173
|
export * from './hooks/use-server-config.js';
|
|
147
174
|
export * from './hooks/use-theme.js';
|
|
148
175
|
export * from './hooks/use-user-settings.js';
|
|
176
|
+
export * from './lib/trans.js';
|
|
149
177
|
export * from './lib/utils.js';
|
package/src/lib/lib/utils.ts
CHANGED
|
@@ -58,3 +58,52 @@ export function normalizeString(input: string, spaceReplacer = ' '): string {
|
|
|
58
58
|
.replace(/\s+/g, spaceReplacer)
|
|
59
59
|
.replace(multipleSequentialReplacerRegex, spaceReplacer);
|
|
60
60
|
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Removes any readonly custom fields from form values before submission.
|
|
64
|
+
* This prevents errors when submitting readonly custom field values to mutations.
|
|
65
|
+
*
|
|
66
|
+
* @param values - The form values that may contain custom fields
|
|
67
|
+
* @param customFieldConfigs - Array of custom field configurations for the entity
|
|
68
|
+
* @returns The values with readonly custom fields removed
|
|
69
|
+
*/
|
|
70
|
+
export function removeReadonlyCustomFields<T extends Record<string, any>>(
|
|
71
|
+
values: T,
|
|
72
|
+
customFieldConfigs: Array<{ name: string; readonly?: boolean | null }> = [],
|
|
73
|
+
): T {
|
|
74
|
+
if (!values || !customFieldConfigs?.length) {
|
|
75
|
+
return values;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Create a deep copy to avoid mutating the original values
|
|
79
|
+
const result = structuredClone(values);
|
|
80
|
+
|
|
81
|
+
// Get readonly field names
|
|
82
|
+
const readonlyFieldNames = customFieldConfigs
|
|
83
|
+
.filter(config => config.readonly === true)
|
|
84
|
+
.map(config => config.name);
|
|
85
|
+
|
|
86
|
+
if (readonlyFieldNames.length === 0) {
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Remove readonly fields from main customFields
|
|
91
|
+
if (result.customFields && typeof result.customFields === 'object') {
|
|
92
|
+
for (const fieldName of readonlyFieldNames) {
|
|
93
|
+
delete result.customFields[fieldName];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Remove readonly fields from translations customFields
|
|
98
|
+
if (Array.isArray(result.translations)) {
|
|
99
|
+
for (const translation of result.translations) {
|
|
100
|
+
if (translation?.customFields && typeof translation.customFields === 'object') {
|
|
101
|
+
for (const fieldName of readonlyFieldNames) {
|
|
102
|
+
delete translation.customFields[fieldName];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return result;
|
|
109
|
+
}
|