@vendure/dashboard 3.3.8-master-202507260236 → 3.3.8-master-202507300243
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/app/routes/_authenticated/_collections/components/collection-contents-preview-table.tsx +1 -1
- package/src/app/routes/_authenticated/_collections/components/collection-filters-selector.tsx +11 -78
- package/src/app/routes/_authenticated/_payment-methods/components/payment-eligibility-checker-selector.tsx +11 -81
- package/src/app/routes/_authenticated/_payment-methods/components/payment-handler-selector.tsx +10 -77
- package/src/app/routes/_authenticated/_promotions/components/promotion-actions-selector.tsx +12 -87
- package/src/app/routes/_authenticated/_promotions/components/promotion-conditions-selector.tsx +12 -87
- package/src/app/routes/_authenticated/_shipping-methods/components/shipping-calculator-selector.tsx +10 -80
- package/src/app/routes/_authenticated/_shipping-methods/components/shipping-eligibility-checker-selector.tsx +10 -79
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +8 -6
- package/src/lib/components/data-input/combination-mode-input.tsx +52 -0
- package/src/lib/components/data-input/configurable-operation-list-input.tsx +433 -0
- package/src/lib/components/data-input/custom-field-list-input.tsx +297 -0
- package/src/lib/components/data-input/datetime-input.tsx +5 -2
- package/src/lib/components/data-input/default-relation-input.tsx +599 -0
- package/src/lib/components/data-input/index.ts +6 -0
- package/src/lib/components/data-input/product-multi-selector.tsx +426 -0
- package/src/lib/components/data-input/relation-selector.tsx +7 -6
- package/src/lib/components/data-input/select-with-options.tsx +84 -0
- package/src/lib/components/data-input/struct-form-input.tsx +324 -0
- package/src/lib/components/shared/configurable-operation-arg-input.tsx +365 -21
- package/src/lib/components/shared/configurable-operation-input.tsx +81 -41
- package/src/lib/components/shared/configurable-operation-multi-selector.tsx +260 -0
- package/src/lib/components/shared/configurable-operation-selector.tsx +156 -0
- package/src/lib/components/shared/custom-fields-form.tsx +207 -36
- package/src/lib/components/shared/multi-select.tsx +1 -1
- package/src/lib/components/ui/form.tsx +4 -4
- package/src/lib/framework/extension-api/input-component-extensions.tsx +5 -1
- package/src/lib/framework/form-engine/form-schema-tools.spec.ts +472 -0
- package/src/lib/framework/form-engine/form-schema-tools.ts +340 -5
- package/src/lib/framework/form-engine/use-generated-form.tsx +24 -8
- package/src/lib/framework/form-engine/utils.ts +3 -9
- package/src/lib/framework/layout-engine/page-layout.tsx +11 -3
- package/src/lib/framework/page/use-detail-page.ts +3 -3
- package/src/lib/lib/utils.ts +26 -24
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { CustomFieldListInput } from '@/vdb/components/data-input/custom-field-list-input.js';
|
|
2
|
+
import { DateTimeInput } from '@/vdb/components/data-input/datetime-input.js';
|
|
3
|
+
import { DefaultRelationInput } from '@/vdb/components/data-input/default-relation-input.js';
|
|
4
|
+
import { SelectWithOptions } from '@/vdb/components/data-input/select-with-options.js';
|
|
5
|
+
import { StructFormInput } from '@/vdb/components/data-input/struct-form-input.js';
|
|
1
6
|
import {
|
|
2
7
|
FormControl,
|
|
3
8
|
FormDescription,
|
|
@@ -13,6 +18,7 @@ import { useCustomFieldConfig } from '@/vdb/hooks/use-custom-field-config.js';
|
|
|
13
18
|
import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
|
|
14
19
|
import { useLingui } from '@/vdb/lib/trans.js';
|
|
15
20
|
import { customFieldConfigFragment } from '@/vdb/providers/server-config.js';
|
|
21
|
+
import { StringCustomFieldConfig } from '@vendure/common/lib/generated-types';
|
|
16
22
|
import { CustomFieldType } from '@vendure/common/lib/shared-types';
|
|
17
23
|
import { ResultOf } from 'gql.tada';
|
|
18
24
|
import React, { useMemo } from 'react';
|
|
@@ -40,13 +46,15 @@ export function CustomFieldsForm({ entityType, control, formPathPrefix }: Readon
|
|
|
40
46
|
|
|
41
47
|
const customFields = useCustomFieldConfig(entityType);
|
|
42
48
|
|
|
49
|
+
const getCustomFieldBaseName = (fieldDef: CustomFieldConfig) => {
|
|
50
|
+
if (fieldDef.type !== 'relation') {
|
|
51
|
+
return fieldDef.name;
|
|
52
|
+
}
|
|
53
|
+
return fieldDef.list ? fieldDef.name + 'Ids' : fieldDef.name + 'Id';
|
|
54
|
+
};
|
|
55
|
+
|
|
43
56
|
const getFieldName = (fieldDef: CustomFieldConfig) => {
|
|
44
|
-
const name =
|
|
45
|
-
fieldDef.type === 'relation'
|
|
46
|
-
? fieldDef.list
|
|
47
|
-
? fieldDef.name + 'Ids'
|
|
48
|
-
: fieldDef.name + 'Id'
|
|
49
|
-
: fieldDef.name;
|
|
57
|
+
const name = getCustomFieldBaseName(fieldDef);
|
|
50
58
|
return formPathPrefix ? `${formPathPrefix}.customFields.${name}` : `customFields.${name}`;
|
|
51
59
|
};
|
|
52
60
|
|
|
@@ -84,7 +92,7 @@ export function CustomFieldsForm({ entityType, control, formPathPrefix }: Readon
|
|
|
84
92
|
if (!shouldShowTabs) {
|
|
85
93
|
// Single tab view - use the original grid layout
|
|
86
94
|
return (
|
|
87
|
-
<div className="grid grid-cols-2 gap-
|
|
95
|
+
<div className="grid @md:grid-cols-2 gap-6">
|
|
88
96
|
{customFields?.map(fieldDef => (
|
|
89
97
|
<CustomFieldItem
|
|
90
98
|
key={fieldDef.name}
|
|
@@ -110,7 +118,7 @@ export function CustomFieldsForm({ entityType, control, formPathPrefix }: Readon
|
|
|
110
118
|
</TabsList>
|
|
111
119
|
{groupedFields.map(group => (
|
|
112
120
|
<TabsContent key={group.tabName} value={group.tabName} className="mt-4">
|
|
113
|
-
<div className="grid grid-cols-2 gap-
|
|
121
|
+
<div className="grid @md:grid-cols-2 gap-6">
|
|
114
122
|
{group.customFields.map(fieldDef => (
|
|
115
123
|
<CustomFieldItem
|
|
116
124
|
key={fieldDef.name}
|
|
@@ -136,11 +144,12 @@ interface CustomFieldItemProps {
|
|
|
136
144
|
) => string | undefined;
|
|
137
145
|
}
|
|
138
146
|
|
|
139
|
-
function CustomFieldItem({ fieldDef, control, fieldName, getTranslation }: CustomFieldItemProps) {
|
|
140
|
-
const hasCustomFormComponent = fieldDef.ui
|
|
147
|
+
function CustomFieldItem({ fieldDef, control, fieldName, getTranslation }: Readonly<CustomFieldItemProps>) {
|
|
148
|
+
const hasCustomFormComponent = fieldDef.ui?.component;
|
|
141
149
|
const isLocaleField = fieldDef.type === 'localeString' || fieldDef.type === 'localeText';
|
|
142
150
|
const shouldBeFullWidth = fieldDef.ui?.fullWidth === true;
|
|
143
151
|
const containerClassName = shouldBeFullWidth ? 'col-span-2' : '';
|
|
152
|
+
const isReadonly = fieldDef.readonly ?? false;
|
|
144
153
|
|
|
145
154
|
// For locale fields, always use TranslatableFormField regardless of custom components
|
|
146
155
|
if (isLocaleField) {
|
|
@@ -207,6 +216,71 @@ function CustomFieldItem({ fieldDef, control, fieldName, getTranslation }: Custo
|
|
|
207
216
|
);
|
|
208
217
|
}
|
|
209
218
|
|
|
219
|
+
// For struct fields, use the special struct component
|
|
220
|
+
if (fieldDef.type === 'struct') {
|
|
221
|
+
const isList = fieldDef.list ?? false;
|
|
222
|
+
|
|
223
|
+
// Handle struct lists - entire struct objects in a list
|
|
224
|
+
if (isList) {
|
|
225
|
+
return (
|
|
226
|
+
<div className={containerClassName}>
|
|
227
|
+
<FormField
|
|
228
|
+
control={control}
|
|
229
|
+
name={fieldName}
|
|
230
|
+
render={({ field }) => (
|
|
231
|
+
<FormItem>
|
|
232
|
+
<FormLabel>{getTranslation(fieldDef.label) ?? field.name}</FormLabel>
|
|
233
|
+
<FormControl>
|
|
234
|
+
<CustomFieldListInput
|
|
235
|
+
field={field}
|
|
236
|
+
disabled={isReadonly}
|
|
237
|
+
renderInput={(index, inputField) => (
|
|
238
|
+
<StructFormInput
|
|
239
|
+
field={inputField}
|
|
240
|
+
fieldDef={fieldDef as any}
|
|
241
|
+
control={control}
|
|
242
|
+
getTranslation={getTranslation}
|
|
243
|
+
/>
|
|
244
|
+
)}
|
|
245
|
+
defaultValue={{}} // Empty struct object as default
|
|
246
|
+
isFullWidth={true} // Structs should always be full-width
|
|
247
|
+
/>
|
|
248
|
+
</FormControl>
|
|
249
|
+
<FormDescription>{getTranslation(fieldDef.description)}</FormDescription>
|
|
250
|
+
<FormMessage />
|
|
251
|
+
</FormItem>
|
|
252
|
+
)}
|
|
253
|
+
/>
|
|
254
|
+
</div>
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Handle single struct fields
|
|
259
|
+
return (
|
|
260
|
+
<div className={containerClassName}>
|
|
261
|
+
<FormField
|
|
262
|
+
control={control}
|
|
263
|
+
name={fieldName}
|
|
264
|
+
render={({ field }) => (
|
|
265
|
+
<FormItem>
|
|
266
|
+
<FormLabel>{getTranslation(fieldDef.label) ?? field.name}</FormLabel>
|
|
267
|
+
<FormControl>
|
|
268
|
+
<StructFormInput
|
|
269
|
+
field={field}
|
|
270
|
+
fieldDef={fieldDef as any}
|
|
271
|
+
control={control}
|
|
272
|
+
getTranslation={getTranslation}
|
|
273
|
+
/>
|
|
274
|
+
</FormControl>
|
|
275
|
+
<FormDescription>{getTranslation(fieldDef.description)}</FormDescription>
|
|
276
|
+
<FormMessage />
|
|
277
|
+
</FormItem>
|
|
278
|
+
)}
|
|
279
|
+
/>
|
|
280
|
+
</div>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
210
284
|
// For regular fields without custom components
|
|
211
285
|
return (
|
|
212
286
|
<div className={containerClassName}>
|
|
@@ -236,7 +310,12 @@ interface CustomFieldFormItemProps {
|
|
|
236
310
|
children: React.ReactNode;
|
|
237
311
|
}
|
|
238
312
|
|
|
239
|
-
function CustomFieldFormItem({
|
|
313
|
+
function CustomFieldFormItem({
|
|
314
|
+
fieldDef,
|
|
315
|
+
getTranslation,
|
|
316
|
+
fieldName,
|
|
317
|
+
children,
|
|
318
|
+
}: Readonly<CustomFieldFormItemProps>) {
|
|
240
319
|
return (
|
|
241
320
|
<FormItem>
|
|
242
321
|
<FormLabel>{getTranslation(fieldDef.label) ?? fieldName}</FormLabel>
|
|
@@ -250,40 +329,132 @@ function CustomFieldFormItem({ fieldDef, getTranslation, fieldName, children }:
|
|
|
250
329
|
function FormInputForType({
|
|
251
330
|
fieldDef,
|
|
252
331
|
field,
|
|
253
|
-
}: {
|
|
332
|
+
}: Readonly<{
|
|
254
333
|
fieldDef: CustomFieldConfig;
|
|
255
334
|
field: ControllerRenderProps<any, any>;
|
|
256
|
-
}) {
|
|
335
|
+
}>) {
|
|
257
336
|
const isReadonly = fieldDef.readonly ?? false;
|
|
337
|
+
const isList = fieldDef.list ?? false;
|
|
338
|
+
|
|
339
|
+
// Helper function to render individual input components
|
|
340
|
+
const renderSingleInput = (inputField: ControllerRenderProps<any, any>) => {
|
|
341
|
+
switch (fieldDef.type as CustomFieldType) {
|
|
342
|
+
case 'float':
|
|
343
|
+
case 'int': {
|
|
344
|
+
const numericFieldDef = fieldDef as any;
|
|
345
|
+
const isFloat = fieldDef.type === 'float';
|
|
346
|
+
const min = isFloat ? numericFieldDef.floatMin : numericFieldDef.intMin;
|
|
347
|
+
const max = isFloat ? numericFieldDef.floatMax : numericFieldDef.intMax;
|
|
348
|
+
const step = isFloat ? numericFieldDef.floatStep : numericFieldDef.intStep;
|
|
258
349
|
|
|
259
|
-
switch (fieldDef.type as CustomFieldType) {
|
|
260
|
-
case 'string':
|
|
261
|
-
return <Input {...field} disabled={isReadonly} />;
|
|
262
|
-
case 'float':
|
|
263
|
-
case 'int':
|
|
264
|
-
return (
|
|
265
|
-
<Input
|
|
266
|
-
type="number"
|
|
267
|
-
{...field}
|
|
268
|
-
disabled={isReadonly}
|
|
269
|
-
onChange={e => field.onChange(e.target.valueAsNumber)}
|
|
270
|
-
/>
|
|
271
|
-
);
|
|
272
|
-
case 'boolean':
|
|
273
|
-
return <Switch checked={field.value} onCheckedChange={field.onChange} disabled={isReadonly} />;
|
|
274
|
-
case 'relation':
|
|
275
|
-
if (fieldDef.list) {
|
|
276
350
|
return (
|
|
277
351
|
<Input
|
|
278
|
-
|
|
279
|
-
|
|
352
|
+
type="number"
|
|
353
|
+
value={inputField.value ?? ''}
|
|
354
|
+
onChange={e => {
|
|
355
|
+
const value = e.target.valueAsNumber;
|
|
356
|
+
inputField.onChange(isNaN(value) ? undefined : value);
|
|
357
|
+
}}
|
|
358
|
+
onBlur={inputField.onBlur}
|
|
359
|
+
name={inputField.name}
|
|
360
|
+
disabled={isReadonly}
|
|
361
|
+
min={min}
|
|
362
|
+
max={max}
|
|
363
|
+
step={step}
|
|
364
|
+
/>
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
case 'boolean':
|
|
368
|
+
return (
|
|
369
|
+
<Switch
|
|
370
|
+
checked={inputField.value}
|
|
371
|
+
onCheckedChange={inputField.onChange}
|
|
372
|
+
disabled={isReadonly}
|
|
373
|
+
/>
|
|
374
|
+
);
|
|
375
|
+
case 'datetime': {
|
|
376
|
+
return (
|
|
377
|
+
<DateTimeInput
|
|
378
|
+
value={inputField.value}
|
|
379
|
+
onChange={inputField.onChange}
|
|
280
380
|
disabled={isReadonly}
|
|
281
381
|
/>
|
|
282
382
|
);
|
|
283
|
-
} else {
|
|
284
|
-
return <Input {...field} disabled={isReadonly} />;
|
|
285
383
|
}
|
|
286
|
-
|
|
287
|
-
|
|
384
|
+
case 'struct':
|
|
385
|
+
// Struct fields need special handling and can't be rendered as simple inputs
|
|
386
|
+
return null;
|
|
387
|
+
case 'string':
|
|
388
|
+
default:
|
|
389
|
+
return (
|
|
390
|
+
<Input
|
|
391
|
+
value={inputField.value ?? ''}
|
|
392
|
+
onChange={e => inputField.onChange(e.target.value)}
|
|
393
|
+
onBlur={inputField.onBlur}
|
|
394
|
+
name={inputField.name}
|
|
395
|
+
disabled={isReadonly}
|
|
396
|
+
/>
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
// Handle struct fields with special component
|
|
402
|
+
if (fieldDef.type === 'struct') {
|
|
403
|
+
// We need access to the control and getTranslation function
|
|
404
|
+
// This will need to be passed down from the parent component
|
|
405
|
+
return null; // Placeholder - struct fields are handled differently in the parent
|
|
288
406
|
}
|
|
407
|
+
|
|
408
|
+
// Handle relation fields directly (they handle list/single internally)
|
|
409
|
+
if (fieldDef.type === 'relation') {
|
|
410
|
+
return <DefaultRelationInput fieldDef={fieldDef as any} field={field} disabled={isReadonly} />;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Handle string fields with options (dropdown) - already handles list case with multi-select
|
|
414
|
+
if (fieldDef.type === 'string') {
|
|
415
|
+
const options = (fieldDef as StringCustomFieldConfig).options;
|
|
416
|
+
if (options && options.length > 0) {
|
|
417
|
+
return (
|
|
418
|
+
<SelectWithOptions
|
|
419
|
+
field={field}
|
|
420
|
+
options={options}
|
|
421
|
+
disabled={isReadonly}
|
|
422
|
+
isListField={isList}
|
|
423
|
+
/>
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// For list fields (except string with options and relations which are handled above), wrap with list input
|
|
429
|
+
if (isList) {
|
|
430
|
+
const getDefaultValue = () => {
|
|
431
|
+
switch (fieldDef.type as CustomFieldType) {
|
|
432
|
+
case 'string':
|
|
433
|
+
return '';
|
|
434
|
+
case 'int':
|
|
435
|
+
case 'float':
|
|
436
|
+
return 0;
|
|
437
|
+
case 'boolean':
|
|
438
|
+
return false;
|
|
439
|
+
case 'datetime':
|
|
440
|
+
return '';
|
|
441
|
+
case 'relation':
|
|
442
|
+
return '';
|
|
443
|
+
default:
|
|
444
|
+
return '';
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
return (
|
|
449
|
+
<CustomFieldListInput
|
|
450
|
+
field={field}
|
|
451
|
+
disabled={isReadonly}
|
|
452
|
+
renderInput={(index, inputField) => renderSingleInput(inputField)}
|
|
453
|
+
defaultValue={getDefaultValue()}
|
|
454
|
+
/>
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// For non-list fields, render directly
|
|
459
|
+
return renderSingleInput(field);
|
|
289
460
|
}
|
|
@@ -3,12 +3,12 @@ import { Slot } from '@radix-ui/react-slot';
|
|
|
3
3
|
import * as React from 'react';
|
|
4
4
|
import {
|
|
5
5
|
Controller,
|
|
6
|
-
FormProvider,
|
|
7
|
-
useFormContext,
|
|
8
|
-
useFormState,
|
|
9
6
|
type ControllerProps,
|
|
10
7
|
type FieldPath,
|
|
11
8
|
type FieldValues,
|
|
9
|
+
FormProvider,
|
|
10
|
+
useFormContext,
|
|
11
|
+
useFormState,
|
|
12
12
|
} from 'react-hook-form';
|
|
13
13
|
|
|
14
14
|
import { Label } from '@/vdb/components/ui/label.js';
|
|
@@ -112,7 +112,7 @@ function FormDescription({ className, ...props }: React.ComponentProps<'p'>) {
|
|
|
112
112
|
<p
|
|
113
113
|
data-slot="form-description"
|
|
114
114
|
id={formDescriptionId}
|
|
115
|
-
className={cn('text-muted-foreground text-
|
|
115
|
+
className={cn('text-muted-foreground text-xs', className)}
|
|
116
116
|
{...props}
|
|
117
117
|
/>
|
|
118
118
|
);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { CombinationModeInput } from '@/vdb/components/data-input/combination-mode-input.js';
|
|
1
2
|
import { DateTimeInput } from '@/vdb/components/data-input/datetime-input.js';
|
|
2
3
|
import { FacetValueInput } from '@/vdb/components/data-input/facet-value-input.js';
|
|
3
4
|
import { MoneyInput } from '@/vdb/components/data-input/money-input.js';
|
|
5
|
+
import { ProductMultiInput } from '@/vdb/components/data-input/product-multi-selector.js';
|
|
4
6
|
import { Checkbox } from '@/vdb/components/ui/checkbox.js';
|
|
5
7
|
import { Input } from '@/vdb/components/ui/input.js';
|
|
6
8
|
import { DataInputComponent } from '../component-registry/component-registry.js';
|
|
@@ -31,6 +33,8 @@ inputComponents.set('vendure:numberInput', NumberInput);
|
|
|
31
33
|
inputComponents.set('vendure:dateTimeInput', DateTimeInput);
|
|
32
34
|
inputComponents.set('vendure:checkboxInput', CheckboxInput);
|
|
33
35
|
inputComponents.set('vendure:facetValueInput', FacetValueInput);
|
|
36
|
+
inputComponents.set('vendure:combinationModeInput', CombinationModeInput);
|
|
37
|
+
inputComponents.set('vendure:productMultiInput', ProductMultiInput);
|
|
34
38
|
|
|
35
39
|
export function getInputComponent(id: string): DataInputComponent | undefined {
|
|
36
40
|
return globalRegistry.get('inputComponents').get(id);
|
|
@@ -54,7 +58,7 @@ export function addInputComponent({
|
|
|
54
58
|
pageId: string;
|
|
55
59
|
blockId: string;
|
|
56
60
|
field: string;
|
|
57
|
-
component:
|
|
61
|
+
component: DataInputComponent;
|
|
58
62
|
}) {
|
|
59
63
|
const inputComponents = globalRegistry.get('inputComponents');
|
|
60
64
|
|