@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.
Files changed (35) hide show
  1. package/package.json +4 -4
  2. package/src/app/routes/_authenticated/_collections/components/collection-contents-preview-table.tsx +1 -1
  3. package/src/app/routes/_authenticated/_collections/components/collection-filters-selector.tsx +11 -78
  4. package/src/app/routes/_authenticated/_payment-methods/components/payment-eligibility-checker-selector.tsx +11 -81
  5. package/src/app/routes/_authenticated/_payment-methods/components/payment-handler-selector.tsx +10 -77
  6. package/src/app/routes/_authenticated/_promotions/components/promotion-actions-selector.tsx +12 -87
  7. package/src/app/routes/_authenticated/_promotions/components/promotion-conditions-selector.tsx +12 -87
  8. package/src/app/routes/_authenticated/_shipping-methods/components/shipping-calculator-selector.tsx +10 -80
  9. package/src/app/routes/_authenticated/_shipping-methods/components/shipping-eligibility-checker-selector.tsx +10 -79
  10. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +8 -6
  11. package/src/lib/components/data-input/combination-mode-input.tsx +52 -0
  12. package/src/lib/components/data-input/configurable-operation-list-input.tsx +433 -0
  13. package/src/lib/components/data-input/custom-field-list-input.tsx +297 -0
  14. package/src/lib/components/data-input/datetime-input.tsx +5 -2
  15. package/src/lib/components/data-input/default-relation-input.tsx +599 -0
  16. package/src/lib/components/data-input/index.ts +6 -0
  17. package/src/lib/components/data-input/product-multi-selector.tsx +426 -0
  18. package/src/lib/components/data-input/relation-selector.tsx +7 -6
  19. package/src/lib/components/data-input/select-with-options.tsx +84 -0
  20. package/src/lib/components/data-input/struct-form-input.tsx +324 -0
  21. package/src/lib/components/shared/configurable-operation-arg-input.tsx +365 -21
  22. package/src/lib/components/shared/configurable-operation-input.tsx +81 -41
  23. package/src/lib/components/shared/configurable-operation-multi-selector.tsx +260 -0
  24. package/src/lib/components/shared/configurable-operation-selector.tsx +156 -0
  25. package/src/lib/components/shared/custom-fields-form.tsx +207 -36
  26. package/src/lib/components/shared/multi-select.tsx +1 -1
  27. package/src/lib/components/ui/form.tsx +4 -4
  28. package/src/lib/framework/extension-api/input-component-extensions.tsx +5 -1
  29. package/src/lib/framework/form-engine/form-schema-tools.spec.ts +472 -0
  30. package/src/lib/framework/form-engine/form-schema-tools.ts +340 -5
  31. package/src/lib/framework/form-engine/use-generated-form.tsx +24 -8
  32. package/src/lib/framework/form-engine/utils.ts +3 -9
  33. package/src/lib/framework/layout-engine/page-layout.tsx +11 -3
  34. package/src/lib/framework/page/use-detail-page.ts +3 -3
  35. package/src/lib/lib/utils.ts +26 -24
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vendure/dashboard",
3
3
  "private": false,
4
- "version": "3.3.8-master-202507260236",
4
+ "version": "3.3.8-master-202507300243",
5
5
  "type": "module",
6
6
  "repository": {
7
7
  "type": "git",
@@ -95,8 +95,8 @@
95
95
  "@types/react-dom": "^19.0.4",
96
96
  "@types/react-grid-layout": "^1.3.5",
97
97
  "@uidotdev/usehooks": "^2.4.1",
98
- "@vendure/common": "^3.3.8-master-202507260236",
99
- "@vendure/core": "^3.3.8-master-202507260236",
98
+ "@vendure/common": "^3.3.8-master-202507300243",
99
+ "@vendure/core": "^3.3.8-master-202507300243",
100
100
  "@vitejs/plugin-react": "^4.3.4",
101
101
  "acorn": "^8.11.3",
102
102
  "acorn-walk": "^8.3.2",
@@ -146,5 +146,5 @@
146
146
  "lightningcss-linux-arm64-musl": "^1.29.3",
147
147
  "lightningcss-linux-x64-musl": "^1.29.1"
148
148
  },
149
- "gitHead": "083e73889906a155b1f3959815db713e557898c9"
149
+ "gitHead": "3679d74357c979aa9c60d8f8b575190723e2bb8f"
150
150
  }
@@ -61,7 +61,7 @@ export function CollectionContentsPreviewTable({
61
61
 
62
62
  return (
63
63
  <div>
64
- <Alert>
64
+ <Alert className="mb-4">
65
65
  <Eye className="h-4 w-4" />
66
66
  <AlertTitle>Preview</AlertTitle>
67
67
  <AlertDescription>
@@ -1,17 +1,5 @@
1
- import { ConfigurableOperationInput } from '@/vdb/components/shared/configurable-operation-input.js';
2
- import { Button } from '@/vdb/components/ui/button.js';
3
- import {
4
- DropdownMenu,
5
- DropdownMenuContent,
6
- DropdownMenuItem,
7
- DropdownMenuTrigger,
8
- } from '@/vdb/components/ui/dropdown-menu.js';
9
- import { Separator } from '@/vdb/components/ui/separator.js';
10
- import { ConfigurableOperationDefFragment } from '@/vdb/graphql/fragments.js';
11
- import { Trans } from '@/vdb/lib/trans.js';
12
- import { useQuery } from '@tanstack/react-query';
1
+ import { ConfigurableOperationMultiSelector } from '@/vdb/components/shared/configurable-operation-multi-selector.js';
13
2
  import { ConfigurableOperationInput as ConfigurableOperationInputType } from '@vendure/common/lib/generated-types';
14
- import { Plus } from 'lucide-react';
15
3
  import { getCollectionFiltersQueryOptions } from '../collections.graphql.js';
16
4
 
17
5
  export interface CollectionFiltersSelectorProps {
@@ -20,72 +8,17 @@ export interface CollectionFiltersSelectorProps {
20
8
  }
21
9
 
22
10
  export function CollectionFiltersSelector({ value, onChange }: Readonly<CollectionFiltersSelectorProps>) {
23
- const { data: filtersData } = useQuery(getCollectionFiltersQueryOptions);
24
-
25
- const filters = filtersData?.collectionFilters;
26
-
27
- const onFilterSelected = (filter: ConfigurableOperationDefFragment) => {
28
- const filterDef = filters?.find(f => f.code === filter.code);
29
- if (!filterDef) {
30
- return;
31
- }
32
- onChange([
33
- ...value,
34
- {
35
- code: filter.code,
36
- arguments: filterDef.args.map(arg => ({
37
- name: arg.name,
38
- value: arg.defaultValue != null ? arg.defaultValue.toString() : '',
39
- })),
40
- },
41
- ]);
42
- };
43
-
44
- const onOperationValueChange = (
45
- filter: ConfigurableOperationInputType,
46
- newVal: ConfigurableOperationInputType,
47
- ) => {
48
- onChange(value.map(f => (f.code === filter.code ? newVal : f)));
49
- };
50
-
51
- const onOperationRemove = (index: number) => {
52
- onChange(value.filter((_, i) => i !== index));
53
- };
54
-
55
11
  return (
56
- <div className="flex flex-col gap-2 mt-4">
57
- {(value ?? []).map((filter, index) => {
58
- const filterDef = filters?.find(f => f.code === filter.code);
59
- if (!filterDef) {
60
- return null;
61
- }
62
- return (
63
- <div key={index} className="flex flex-col gap-2">
64
- <ConfigurableOperationInput
65
- operationDefinition={filterDef}
66
- value={filter}
67
- onChange={value => onOperationValueChange(filter, value)}
68
- onRemove={() => onOperationRemove(index)}
69
- />
70
- <Separator className="my-2" />
71
- </div>
72
- );
73
- })}
74
- <DropdownMenu>
75
- <DropdownMenuTrigger asChild>
76
- <Button variant="outline">
77
- <Plus />
78
- <Trans context="Add new collection filter">Add condition</Trans>
79
- </Button>
80
- </DropdownMenuTrigger>
81
- <DropdownMenuContent className="w-96">
82
- {filters?.map(filter => (
83
- <DropdownMenuItem key={filter.code} onClick={() => onFilterSelected(filter)}>
84
- {filter.description}
85
- </DropdownMenuItem>
86
- ))}
87
- </DropdownMenuContent>
88
- </DropdownMenu>
12
+ <div className="mt-4">
13
+ <ConfigurableOperationMultiSelector
14
+ value={value}
15
+ onChange={onChange}
16
+ queryOptions={getCollectionFiltersQueryOptions}
17
+ queryKey="getCollectionFilters"
18
+ dataPath="collectionFilters"
19
+ buttonText="Add collection filter"
20
+ showEnhancedDropdown={false}
21
+ />
89
22
  </div>
90
23
  );
91
24
  }
@@ -1,21 +1,7 @@
1
- import { ConfigurableOperationInput } from '@/vdb/components/shared/configurable-operation-input.js';
2
- import { Button } from '@/vdb/components/ui/button.js';
3
- import {
4
- DropdownMenu,
5
- DropdownMenuContent,
6
- DropdownMenuItem,
7
- DropdownMenuTrigger,
8
- } from '@/vdb/components/ui/dropdown-menu.js';
9
- import { api } from '@/vdb/graphql/api.js';
10
- import {
11
- configurableOperationDefFragment,
12
- ConfigurableOperationDefFragment,
13
- } from '@/vdb/graphql/fragments.js';
1
+ import { ConfigurableOperationSelector } from '@/vdb/components/shared/configurable-operation-selector.js';
2
+ import { configurableOperationDefFragment } from '@/vdb/graphql/fragments.js';
14
3
  import { graphql } from '@/vdb/graphql/graphql.js';
15
- import { Trans } from '@/vdb/lib/trans.js';
16
- import { useQuery } from '@tanstack/react-query';
17
4
  import { ConfigurableOperationInput as ConfigurableOperationInputType } from '@vendure/common/lib/generated-types';
18
- import { Plus } from 'lucide-react';
19
5
 
20
6
  export const paymentEligibilityCheckersDocument = graphql(
21
7
  `
@@ -37,71 +23,15 @@ export function PaymentEligibilityCheckerSelector({
37
23
  value,
38
24
  onChange,
39
25
  }: PaymentEligibilityCheckerSelectorProps) {
40
- const { data: checkersData } = useQuery({
41
- queryKey: ['paymentMethodEligibilityCheckers'],
42
- queryFn: () => api.query(paymentEligibilityCheckersDocument),
43
- staleTime: 1000 * 60 * 60 * 5,
44
- });
45
-
46
- const checkers = checkersData?.paymentMethodEligibilityCheckers;
47
-
48
- const onCheckerSelected = (checker: ConfigurableOperationDefFragment) => {
49
- const checkerDef = checkers?.find(c => c.code === checker.code);
50
- if (!checkerDef) {
51
- return;
52
- }
53
- onChange({
54
- code: checker.code,
55
- arguments: checkerDef.args.map(arg => ({
56
- name: arg.name,
57
- value: arg.defaultValue != null ? arg.defaultValue.toString() : '',
58
- })),
59
- });
60
- };
61
-
62
- const onOperationValueChange = (newVal: ConfigurableOperationInputType) => {
63
- onChange(newVal);
64
- };
65
-
66
- const onOperationRemove = () => {
67
- onChange(undefined);
68
- };
69
-
70
- const checkerDef = checkers?.find(c => c.code === value?.code);
71
-
72
26
  return (
73
- <div className="flex flex-col gap-2 mt-4">
74
- {value && checkerDef && (
75
- <div className="flex flex-col gap-2">
76
- <ConfigurableOperationInput
77
- operationDefinition={checkerDef}
78
- value={value}
79
- onChange={value => onOperationValueChange(value)}
80
- onRemove={() => onOperationRemove()}
81
- />
82
- </div>
83
- )}
84
- <DropdownMenu>
85
- {!value?.code && (
86
- <DropdownMenuTrigger asChild>
87
- <Button variant="outline">
88
- <Plus />
89
- <Trans>Select Payment Eligibility Checker</Trans>
90
- </Button>
91
- </DropdownMenuTrigger>
92
- )}
93
- <DropdownMenuContent className="w-96">
94
- {checkers?.length ? (
95
- checkers?.map(checker => (
96
- <DropdownMenuItem key={checker.code} onClick={() => onCheckerSelected(checker)}>
97
- {checker.description}
98
- </DropdownMenuItem>
99
- ))
100
- ) : (
101
- <DropdownMenuItem>No checkers found</DropdownMenuItem>
102
- )}
103
- </DropdownMenuContent>
104
- </DropdownMenu>
105
- </div>
27
+ <ConfigurableOperationSelector
28
+ value={value}
29
+ onChange={onChange}
30
+ queryDocument={paymentEligibilityCheckersDocument}
31
+ queryKey="paymentMethodEligibilityCheckers"
32
+ dataPath="paymentMethodEligibilityCheckers"
33
+ buttonText="Select Payment Eligibility Checker"
34
+ emptyText="No checkers found"
35
+ />
106
36
  );
107
37
  }
@@ -1,21 +1,7 @@
1
- import { ConfigurableOperationInput } from '@/vdb/components/shared/configurable-operation-input.js';
2
- import { Button } from '@/vdb/components/ui/button.js';
3
- import {
4
- DropdownMenu,
5
- DropdownMenuContent,
6
- DropdownMenuItem,
7
- DropdownMenuTrigger,
8
- } from '@/vdb/components/ui/dropdown-menu.js';
9
- import { api } from '@/vdb/graphql/api.js';
10
- import {
11
- configurableOperationDefFragment,
12
- ConfigurableOperationDefFragment,
13
- } from '@/vdb/graphql/fragments.js';
1
+ import { ConfigurableOperationSelector } from '@/vdb/components/shared/configurable-operation-selector.js';
2
+ import { configurableOperationDefFragment } from '@/vdb/graphql/fragments.js';
14
3
  import { graphql } from '@/vdb/graphql/graphql.js';
15
- import { Trans } from '@/vdb/lib/trans.js';
16
- import { useQuery } from '@tanstack/react-query';
17
4
  import { ConfigurableOperationInput as ConfigurableOperationInputType } from '@vendure/common/lib/generated-types';
18
- import { Plus } from 'lucide-react';
19
5
 
20
6
  export const paymentHandlersDocument = graphql(
21
7
  `
@@ -34,67 +20,14 @@ interface PaymentHandlerSelectorProps {
34
20
  }
35
21
 
36
22
  export function PaymentHandlerSelector({ value, onChange }: Readonly<PaymentHandlerSelectorProps>) {
37
- const { data: handlersData } = useQuery({
38
- queryKey: ['paymentMethodHandlers'],
39
- queryFn: () => api.query(paymentHandlersDocument),
40
- staleTime: 1000 * 60 * 60 * 5,
41
- });
42
-
43
- const handlers = handlersData?.paymentMethodHandlers;
44
-
45
- const onHandlerSelected = (handler: ConfigurableOperationDefFragment) => {
46
- const handlerDef = handlers?.find(h => h.code === handler.code);
47
- if (!handlerDef) {
48
- return;
49
- }
50
- onChange({
51
- code: handler.code,
52
- arguments: handlerDef.args.map(arg => ({
53
- name: arg.name,
54
- value: arg.defaultValue != null ? arg.defaultValue.toString() : '',
55
- })),
56
- });
57
- };
58
-
59
- const onOperationValueChange = (newVal: ConfigurableOperationInputType) => {
60
- onChange(newVal);
61
- };
62
-
63
- const onOperationRemove = () => {
64
- onChange(undefined);
65
- };
66
-
67
- const handlerDef = handlers?.find(h => h.code === value?.code);
68
-
69
23
  return (
70
- <div className="flex flex-col gap-2 mt-4">
71
- {value && handlerDef && (
72
- <div className="flex flex-col gap-2">
73
- <ConfigurableOperationInput
74
- operationDefinition={handlerDef}
75
- value={value}
76
- onChange={value => onOperationValueChange(value)}
77
- onRemove={() => onOperationRemove()}
78
- />
79
- </div>
80
- )}
81
- <DropdownMenu>
82
- {!value?.code && (
83
- <DropdownMenuTrigger asChild>
84
- <Button variant="outline">
85
- <Plus />
86
- <Trans>Select Payment Handler</Trans>
87
- </Button>
88
- </DropdownMenuTrigger>
89
- )}
90
- <DropdownMenuContent className="w-96">
91
- {handlers?.map(handler => (
92
- <DropdownMenuItem key={handler.code} onClick={() => onHandlerSelected(handler)}>
93
- {handler.description}
94
- </DropdownMenuItem>
95
- ))}
96
- </DropdownMenuContent>
97
- </DropdownMenu>
98
- </div>
24
+ <ConfigurableOperationSelector
25
+ value={value}
26
+ onChange={onChange}
27
+ queryDocument={paymentHandlersDocument}
28
+ queryKey="paymentMethodHandlers"
29
+ dataPath="paymentMethodHandlers"
30
+ buttonText="Select Payment Handler"
31
+ />
99
32
  );
100
33
  }
@@ -1,22 +1,7 @@
1
- import { ConfigurableOperationInput } from '@/vdb/components/shared/configurable-operation-input.js';
2
- import { Button } from '@/vdb/components/ui/button.js';
3
- import {
4
- DropdownMenu,
5
- DropdownMenuContent,
6
- DropdownMenuItem,
7
- DropdownMenuTrigger,
8
- } from '@/vdb/components/ui/dropdown-menu.js';
9
- import { Separator } from '@/vdb/components/ui/separator.js';
10
- import { api } from '@/vdb/graphql/api.js';
11
- import {
12
- configurableOperationDefFragment,
13
- ConfigurableOperationDefFragment,
14
- } from '@/vdb/graphql/fragments.js';
1
+ import { ConfigurableOperationMultiSelector } from '@/vdb/components/shared/configurable-operation-multi-selector.js';
2
+ import { configurableOperationDefFragment } from '@/vdb/graphql/fragments.js';
15
3
  import { graphql } from '@/vdb/graphql/graphql.js';
16
- import { Trans } from '@/vdb/lib/trans.js';
17
- import { useQuery } from '@tanstack/react-query';
18
4
  import { ConfigurableOperationInput as ConfigurableOperationInputType } from '@vendure/common/lib/generated-types';
19
- import { Plus } from 'lucide-react';
20
5
 
21
6
  export const promotionActionsDocument = graphql(
22
7
  `
@@ -35,76 +20,16 @@ interface PromotionActionsSelectorProps {
35
20
  }
36
21
 
37
22
  export function PromotionActionsSelector({ value, onChange }: Readonly<PromotionActionsSelectorProps>) {
38
- const { data: actionsData } = useQuery({
39
- queryKey: ['promotionActions'],
40
- queryFn: () => api.query(promotionActionsDocument),
41
- staleTime: 1000 * 60 * 60 * 5,
42
- });
43
-
44
- const actions = actionsData?.promotionActions;
45
-
46
- const onActionSelected = (action: ConfigurableOperationDefFragment) => {
47
- const actionDef = actions?.find(a => a.code === action.code);
48
- if (!actionDef) {
49
- return;
50
- }
51
- onChange([
52
- ...value,
53
- {
54
- code: action.code,
55
- arguments: actionDef.args.map(arg => ({
56
- name: arg.name,
57
- value: arg.defaultValue != null ? arg.defaultValue.toString() : '',
58
- })),
59
- },
60
- ]);
61
- };
62
-
63
- const onOperationValueChange = (
64
- action: ConfigurableOperationInputType,
65
- newVal: ConfigurableOperationInputType,
66
- ) => {
67
- onChange(value.map(a => (a.code === action.code ? newVal : a)));
68
- };
69
-
70
- const onOperationRemove = (index: number) => {
71
- onChange(value.filter((_, i) => i !== index));
72
- };
73
-
74
23
  return (
75
- <div className="flex flex-col gap-2 mt-4">
76
- {(value ?? []).map((action, index) => {
77
- const actionDef = actions?.find(a => a.code === action.code);
78
- if (!actionDef) {
79
- return null;
80
- }
81
- return (
82
- <div key={index} className="flex flex-col gap-2">
83
- <ConfigurableOperationInput
84
- operationDefinition={actionDef}
85
- value={action}
86
- onChange={value => onOperationValueChange(action, value)}
87
- onRemove={() => onOperationRemove(index)}
88
- />
89
- <Separator className="my-2" />
90
- </div>
91
- );
92
- })}
93
- <DropdownMenu>
94
- <DropdownMenuTrigger asChild>
95
- <Button variant="outline">
96
- <Plus />
97
- <Trans context="Add new promotion action">Add action</Trans>
98
- </Button>
99
- </DropdownMenuTrigger>
100
- <DropdownMenuContent className="w-96">
101
- {actions?.map(action => (
102
- <DropdownMenuItem key={action.code} onClick={() => onActionSelected(action)}>
103
- {action.description}
104
- </DropdownMenuItem>
105
- ))}
106
- </DropdownMenuContent>
107
- </DropdownMenu>
108
- </div>
24
+ <ConfigurableOperationMultiSelector
25
+ value={value}
26
+ onChange={onChange}
27
+ queryDocument={promotionActionsDocument}
28
+ queryKey="promotionActions"
29
+ dataPath="promotionActions"
30
+ buttonText="Add action"
31
+ dropdownTitle="Available Actions"
32
+ showEnhancedDropdown={true}
33
+ />
109
34
  );
110
35
  }
@@ -1,22 +1,7 @@
1
- import { ConfigurableOperationInput } from '@/vdb/components/shared/configurable-operation-input.js';
2
- import { Button } from '@/vdb/components/ui/button.js';
3
- import {
4
- DropdownMenu,
5
- DropdownMenuContent,
6
- DropdownMenuItem,
7
- DropdownMenuTrigger,
8
- } from '@/vdb/components/ui/dropdown-menu.js';
9
- import { Separator } from '@/vdb/components/ui/separator.js';
10
- import { api } from '@/vdb/graphql/api.js';
11
- import {
12
- configurableOperationDefFragment,
13
- ConfigurableOperationDefFragment,
14
- } from '@/vdb/graphql/fragments.js';
1
+ import { ConfigurableOperationMultiSelector } from '@/vdb/components/shared/configurable-operation-multi-selector.js';
2
+ import { configurableOperationDefFragment } from '@/vdb/graphql/fragments.js';
15
3
  import { graphql } from '@/vdb/graphql/graphql.js';
16
- import { Trans } from '@/vdb/lib/trans.js';
17
- import { useQuery } from '@tanstack/react-query';
18
4
  import { ConfigurableOperationInput as ConfigurableOperationInputType } from '@vendure/common/lib/generated-types';
19
- import { Plus } from 'lucide-react';
20
5
 
21
6
  export const promotionConditionsDocument = graphql(
22
7
  `
@@ -35,76 +20,16 @@ interface PromotionConditionsSelectorProps {
35
20
  }
36
21
 
37
22
  export function PromotionConditionsSelector({ value, onChange }: Readonly<PromotionConditionsSelectorProps>) {
38
- const { data: conditionsData } = useQuery({
39
- queryKey: ['promotionConditions'],
40
- queryFn: () => api.query(promotionConditionsDocument),
41
- staleTime: 1000 * 60 * 60 * 5,
42
- });
43
-
44
- const conditions = conditionsData?.promotionConditions;
45
-
46
- const onConditionSelected = (condition: ConfigurableOperationDefFragment) => {
47
- const conditionDef = conditions?.find(c => c.code === condition.code);
48
- if (!conditionDef) {
49
- return;
50
- }
51
- onChange([
52
- ...value,
53
- {
54
- code: condition.code,
55
- arguments: conditionDef.args.map(arg => ({
56
- name: arg.name,
57
- value: arg.defaultValue != null ? arg.defaultValue.toString() : '',
58
- })),
59
- },
60
- ]);
61
- };
62
-
63
- const onOperationValueChange = (
64
- condition: ConfigurableOperationInputType,
65
- newVal: ConfigurableOperationInputType,
66
- ) => {
67
- onChange(value.map(c => (c.code === condition.code ? newVal : c)));
68
- };
69
-
70
- const onOperationRemove = (index: number) => {
71
- onChange(value.filter((_, i) => i !== index));
72
- };
73
-
74
23
  return (
75
- <div className="flex flex-col gap-2 mt-4">
76
- {(value ?? []).map((condition, index) => {
77
- const conditionDef = conditions?.find(c => c.code === condition.code);
78
- if (!conditionDef) {
79
- return null;
80
- }
81
- return (
82
- <div key={index} className="flex flex-col gap-2">
83
- <ConfigurableOperationInput
84
- operationDefinition={conditionDef}
85
- value={condition}
86
- onChange={value => onOperationValueChange(condition, value)}
87
- onRemove={() => onOperationRemove(index)}
88
- />
89
- <Separator className="my-2" />
90
- </div>
91
- );
92
- })}
93
- <DropdownMenu>
94
- <DropdownMenuTrigger asChild>
95
- <Button variant="outline">
96
- <Plus />
97
- <Trans context="Add new promotion condition">Add condition</Trans>
98
- </Button>
99
- </DropdownMenuTrigger>
100
- <DropdownMenuContent className="w-96">
101
- {conditions?.map(condition => (
102
- <DropdownMenuItem key={condition.code} onClick={() => onConditionSelected(condition)}>
103
- {condition.description}
104
- </DropdownMenuItem>
105
- ))}
106
- </DropdownMenuContent>
107
- </DropdownMenu>
108
- </div>
24
+ <ConfigurableOperationMultiSelector
25
+ value={value}
26
+ onChange={onChange}
27
+ queryDocument={promotionConditionsDocument}
28
+ queryKey="promotionConditions"
29
+ dataPath="promotionConditions"
30
+ buttonText="Add condition"
31
+ dropdownTitle="Available Conditions"
32
+ showEnhancedDropdown={true}
33
+ />
109
34
  );
110
35
  }
@@ -1,21 +1,7 @@
1
- import { ConfigurableOperationInput } from '@/vdb/components/shared/configurable-operation-input.js';
2
- import { Button } from '@/vdb/components/ui/button.js';
3
- import {
4
- DropdownMenu,
5
- DropdownMenuContent,
6
- DropdownMenuItem,
7
- DropdownMenuTrigger,
8
- } from '@/vdb/components/ui/dropdown-menu.js';
9
- import { api } from '@/vdb/graphql/api.js';
10
- import {
11
- configurableOperationDefFragment,
12
- ConfigurableOperationDefFragment,
13
- } from '@/vdb/graphql/fragments.js';
1
+ import { ConfigurableOperationSelector } from '@/vdb/components/shared/configurable-operation-selector.js';
2
+ import { configurableOperationDefFragment } from '@/vdb/graphql/fragments.js';
14
3
  import { graphql } from '@/vdb/graphql/graphql.js';
15
- import { Trans } from '@/vdb/lib/trans.js';
16
- import { useQuery } from '@tanstack/react-query';
17
4
  import { ConfigurableOperationInput as ConfigurableOperationInputType } from '@vendure/common/lib/generated-types';
18
- import { Plus } from 'lucide-react';
19
5
 
20
6
  export const shippingCalculatorsDocument = graphql(
21
7
  `
@@ -34,70 +20,14 @@ interface ShippingCalculatorSelectorProps {
34
20
  }
35
21
 
36
22
  export function ShippingCalculatorSelector({ value, onChange }: Readonly<ShippingCalculatorSelectorProps>) {
37
- const { data: calculatorsData } = useQuery({
38
- queryKey: ['shippingCalculators'],
39
- queryFn: () => api.query(shippingCalculatorsDocument),
40
- staleTime: 1000 * 60 * 60 * 5,
41
- });
42
-
43
- const calculators = calculatorsData?.shippingCalculators;
44
-
45
- const onCalculatorSelected = (calculator: ConfigurableOperationDefFragment) => {
46
- const calculatorDef = calculators?.find(c => c.code === calculator.code);
47
- if (!calculatorDef) {
48
- return;
49
- }
50
- onChange({
51
- code: calculator.code,
52
- arguments: calculatorDef.args.map(arg => ({
53
- name: arg.name,
54
- value: arg.defaultValue != null ? arg.defaultValue.toString() : '',
55
- })),
56
- });
57
- };
58
-
59
- const onOperationValueChange = (newVal: ConfigurableOperationInputType) => {
60
- onChange(newVal);
61
- };
62
-
63
- const onOperationRemove = () => {
64
- onChange(undefined);
65
- };
66
-
67
- const calculatorDef = calculators?.find(c => c.code === value?.code);
68
-
69
23
  return (
70
- <div className="flex flex-col gap-2 mt-4">
71
- {value && calculatorDef && (
72
- <div className="flex flex-col gap-2">
73
- <ConfigurableOperationInput
74
- operationDefinition={calculatorDef}
75
- value={value}
76
- onChange={value => onOperationValueChange(value)}
77
- onRemove={() => onOperationRemove()}
78
- />
79
- </div>
80
- )}
81
- <DropdownMenu>
82
- {!value && (
83
- <DropdownMenuTrigger asChild>
84
- <Button variant="outline">
85
- <Plus />
86
- <Trans context="Add new promotion action">Select Shipping Calculator</Trans>
87
- </Button>
88
- </DropdownMenuTrigger>
89
- )}
90
- <DropdownMenuContent className="w-96">
91
- {calculators?.map(calculator => (
92
- <DropdownMenuItem
93
- key={calculator.code}
94
- onClick={() => onCalculatorSelected(calculator)}
95
- >
96
- {calculator.description}
97
- </DropdownMenuItem>
98
- ))}
99
- </DropdownMenuContent>
100
- </DropdownMenu>
101
- </div>
24
+ <ConfigurableOperationSelector
25
+ value={value}
26
+ onChange={onChange}
27
+ queryDocument={shippingCalculatorsDocument}
28
+ queryKey="shippingCalculators"
29
+ dataPath="shippingCalculators"
30
+ buttonText="Select Shipping Calculator"
31
+ />
102
32
  );
103
33
  }