@vendure/dashboard 3.5.2-master-202512040233 → 3.5.2-master-202512180239

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 (69) hide show
  1. package/dist/plugin/constants.js +2 -2
  2. package/dist/plugin/dashboard.plugin.js +1 -1
  3. package/dist/vite/constants.js +1 -0
  4. package/lingui.config.js +1 -0
  5. package/package.json +7 -7
  6. package/src/app/routeTree.gen.ts +1221 -0
  7. package/src/app/routes/_authenticated/_collections/collections.graphql.ts +1 -0
  8. package/src/app/routes/_authenticated/_collections/collections.tsx +249 -167
  9. package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +8 -0
  10. package/src/app/routes/_authenticated/_collections/components/move-collections-dialog.tsx +4 -0
  11. package/src/app/routes/_authenticated/_customers/components/customer-history/index.ts +0 -1
  12. package/src/app/routes/_authenticated/_global-settings/global-settings.tsx +1 -1
  13. package/src/app/routes/_authenticated/_orders/components/add-surcharge-form.tsx +139 -0
  14. package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +3 -0
  15. package/src/app/routes/_authenticated/_orders/components/order-address.tsx +3 -3
  16. package/src/app/routes/_authenticated/_orders/components/order-modification-summary.tsx +49 -11
  17. package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +9 -0
  18. package/src/app/routes/_authenticated/_orders/utils/use-modify-order.ts +23 -0
  19. package/src/app/routes/_authenticated/_product-variants/components/add-currency-dropdown.tsx +3 -3
  20. package/src/app/routes/_authenticated/_product-variants/components/add-stock-location-dropdown.tsx +2 -2
  21. package/src/app/routes/_authenticated/_products/products.graphql.ts +1 -0
  22. package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +2 -9
  23. package/src/i18n/locales/bg.po +3436 -0
  24. package/src/lib/components/data-input/datetime-input.tsx +1 -1
  25. package/src/lib/components/data-input/number-input.tsx +24 -5
  26. package/src/lib/components/data-input/relation-selector.tsx +1 -1
  27. package/src/lib/components/data-input/struct-form-input.tsx +175 -174
  28. package/src/lib/components/data-table/data-table-utils.ts +241 -1
  29. package/src/lib/components/data-table/data-table.tsx +190 -60
  30. package/src/lib/components/layout/manage-languages-dialog.tsx +2 -25
  31. package/src/lib/components/shared/custom-fields-form.tsx +13 -8
  32. package/src/lib/components/shared/paginated-list-data-table.tsx +19 -0
  33. package/src/lib/components/ui/alert.tsx +1 -1
  34. package/src/lib/components/ui/carousel.tsx +2 -2
  35. package/src/lib/components/ui/chart.tsx +1 -1
  36. package/src/lib/components/ui/context-menu.tsx +1 -1
  37. package/src/lib/components/ui/drawer.tsx +1 -1
  38. package/src/lib/components/ui/grid-layout.tsx +1 -1
  39. package/src/lib/components/ui/input-group.tsx +1 -0
  40. package/src/lib/components/ui/input-otp.tsx +1 -1
  41. package/src/lib/components/ui/menubar.tsx +1 -1
  42. package/src/lib/components/ui/navigation-menu.tsx +1 -1
  43. package/src/lib/components/ui/progress.tsx +1 -1
  44. package/src/lib/components/ui/radio-group.tsx +1 -1
  45. package/src/lib/components/ui/resizable.tsx +1 -1
  46. package/src/lib/components/ui/select.tsx +1 -1
  47. package/src/lib/components/ui/slider.tsx +1 -1
  48. package/src/lib/components/ui/toggle-group.tsx +2 -2
  49. package/src/lib/components/ui/toggle.tsx +1 -1
  50. package/src/lib/framework/component-registry/component-registry.tsx +2 -6
  51. package/src/lib/framework/extension-api/display-component-extensions.tsx +4 -3
  52. package/src/lib/framework/extension-api/logic/detail-forms.ts +0 -13
  53. package/src/lib/framework/extension-api/types/data-table.ts +4 -2
  54. package/src/lib/framework/extension-api/types/navigation.ts +2 -2
  55. package/src/lib/framework/form-engine/use-generated-form.tsx +7 -1
  56. package/src/lib/framework/layout-engine/page-layout.tsx +1 -1
  57. package/src/lib/framework/nav-menu/nav-menu-extensions.ts +1 -1
  58. package/src/lib/framework/page/detail-page-route-loader.tsx +1 -1
  59. package/src/lib/framework/page/list-page.tsx +62 -38
  60. package/src/lib/framework/page/page-api.ts +1 -1
  61. package/src/lib/framework/page/use-detail-page.ts +4 -2
  62. package/src/lib/framework/page/use-extended-router.tsx +20 -16
  63. package/src/lib/framework/registry/registry-types.ts +2 -1
  64. package/src/lib/graphql/graphql-env.d.ts +8 -12
  65. package/src/lib/hooks/use-drag-and-drop.ts +86 -0
  66. package/src/lib/providers/channel-provider.tsx +11 -7
  67. package/LICENSE.md +0 -42
  68. package/src/app/routes/_authenticated/_facets/components/edit-facet-value.tsx +0 -129
  69. /package/src/{app/routes/_authenticated/_global-settings → lib}/utils/global-languages.ts +0 -0
@@ -0,0 +1,139 @@
1
+ import { AffixedInput } from '@/vdb/components/data-input/affixed-input.js';
2
+ import { MoneyInput } from '@/vdb/components/data-input/money-input.js';
3
+ import { FormFieldWrapper } from '@/vdb/components/shared/form-field-wrapper.js';
4
+ import { Button } from '@/vdb/components/ui/button.js';
5
+ import { Form } from '@/vdb/components/ui/form.js';
6
+ import { Input } from '@/vdb/components/ui/input.js';
7
+ import { Switch } from '@/vdb/components/ui/switch.js';
8
+ import { DetailFormGrid } from '@/vdb/framework/layout-engine/page-layout.js';
9
+ import { useChannel } from '@/vdb/hooks/use-channel.js';
10
+ import { zodResolver } from '@hookform/resolvers/zod';
11
+ import { Trans } from '@lingui/react/macro';
12
+ import { VariablesOf } from 'gql.tada';
13
+ import { Plus } from 'lucide-react';
14
+ import { useForm } from 'react-hook-form';
15
+ import { z } from 'zod';
16
+ import { modifyOrderDocument } from '../orders.graphql.js';
17
+
18
+ type ModifyOrderInput = VariablesOf<typeof modifyOrderDocument>['input'];
19
+ type SurchargeInput = NonNullable<ModifyOrderInput['surcharges']>[number];
20
+
21
+ const surchargeFormSchema = z.object({
22
+ description: z.string().min(1),
23
+ sku: z.string().optional(),
24
+ price: z.string().refine(val => !isNaN(Number(val)) && Number(val) > 0, {
25
+ message: 'Price must be a positive number',
26
+ }),
27
+ priceIncludesTax: z.boolean().default(false),
28
+ taxRate: z.number().nonnegative().max(100),
29
+ taxDescription: z.string().optional(),
30
+ });
31
+
32
+ type SurchargeFormValues = z.infer<typeof surchargeFormSchema>;
33
+
34
+ export interface AddSurchargeFormProps {
35
+ onAddSurcharge: (surcharge: SurchargeInput) => void;
36
+ }
37
+
38
+ export function AddSurchargeForm({ onAddSurcharge }: Readonly<AddSurchargeFormProps>) {
39
+ const { activeChannel } = useChannel();
40
+
41
+ const surchargeForm = useForm<SurchargeFormValues>({
42
+ resolver: zodResolver(surchargeFormSchema),
43
+ mode: 'onChange',
44
+ defaultValues: {
45
+ description: '',
46
+ sku: '',
47
+ price: '0',
48
+ priceIncludesTax: false,
49
+ taxRate: 0,
50
+ taxDescription: '',
51
+ },
52
+ });
53
+
54
+ const taxRate = surchargeForm.watch('taxRate') || 0;
55
+
56
+ const handleAddSurcharge = () => {
57
+ surchargeForm.handleSubmit(values => {
58
+ onAddSurcharge({
59
+ description: values.description,
60
+ sku: values.sku || undefined,
61
+ price: Number(values.price), // already in minor units from MoneyInput
62
+ priceIncludesTax: values.priceIncludesTax,
63
+ taxRate: values.taxRate ?? undefined,
64
+ taxDescription: values.taxDescription || undefined,
65
+ });
66
+ surchargeForm.reset();
67
+ })();
68
+ };
69
+
70
+ return (
71
+ <Form {...surchargeForm}>
72
+ <div className="space-y-4">
73
+ <DetailFormGrid>
74
+ <FormFieldWrapper
75
+ control={surchargeForm.control}
76
+ name="description"
77
+ label={<Trans>Description</Trans>}
78
+ render={({ field }) => <Input {...field} />}
79
+ />
80
+ <FormFieldWrapper
81
+ control={surchargeForm.control}
82
+ name="sku"
83
+ label={<Trans>SKU</Trans>}
84
+ render={({ field }) => <Input {...field} />}
85
+ />
86
+ <FormFieldWrapper
87
+ control={surchargeForm.control}
88
+ name="price"
89
+ label={<Trans>Price</Trans>}
90
+ render={({ field }) => (
91
+ <MoneyInput
92
+ {...field}
93
+ value={Number(field.value) || 0}
94
+ onChange={value => field.onChange(value.toString())}
95
+ currency={activeChannel?.defaultCurrencyCode}
96
+ />
97
+ )}
98
+ />
99
+ <FormFieldWrapper
100
+ control={surchargeForm.control}
101
+ name="priceIncludesTax"
102
+ label={<Trans>Includes tax at {taxRate}%</Trans>}
103
+ render={({ field }) => (
104
+ <Switch checked={field.value} onCheckedChange={field.onChange} />
105
+ )}
106
+ />
107
+ <FormFieldWrapper
108
+ control={surchargeForm.control}
109
+ name="taxRate"
110
+ label={<Trans>Tax rate</Trans>}
111
+ render={({ field }) => (
112
+ <AffixedInput
113
+ {...field}
114
+ type="number"
115
+ suffix="%"
116
+ value={field.value}
117
+ onChange={e => field.onChange(e.target.valueAsNumber)}
118
+ />
119
+ )}
120
+ />
121
+ <FormFieldWrapper
122
+ control={surchargeForm.control}
123
+ name="taxDescription"
124
+ label={<Trans>Tax description</Trans>}
125
+ render={({ field }) => <Input {...field} />}
126
+ />
127
+ </DetailFormGrid>
128
+ <Button
129
+ type="button"
130
+ onClick={handleAddSurcharge}
131
+ disabled={!surchargeForm.formState.isValid}
132
+ >
133
+ <Plus className="w-4 h-4 mr-2" />
134
+ <Trans>Add surcharge</Trans>
135
+ </Button>
136
+ </div>
137
+ </Form>
138
+ );
139
+ }
@@ -270,6 +270,9 @@ export function EditOrderTable({
270
270
  }}
271
271
  value={''}
272
272
  selectorLabel={<Trans>Add coupon code</Trans>}
273
+ onBlur={() => {}}
274
+ name={'couponCode'}
275
+ ref={() => {}}
273
276
  onChange={code => onApplyCouponCode({ couponCode: code })}
274
277
  />
275
278
  </div>
@@ -3,8 +3,8 @@ import { ResultOf } from 'gql.tada';
3
3
  import { Globe, Phone } from 'lucide-react';
4
4
  import { orderAddressFragment } from '../orders.graphql.js';
5
5
 
6
- type OrderAddress = Omit<ResultOf<typeof orderAddressFragment>, 'country'> & {
7
- country: string | { code: string; name: string } | null;
6
+ type OrderAddress = Partial<Omit<ResultOf<typeof orderAddressFragment>, 'country'>> & {
7
+ country?: string | { code: string; name: string } | null;
8
8
  };
9
9
 
10
10
  export function OrderAddress({ address }: Readonly<{ address?: OrderAddress }>) {
@@ -19,7 +19,7 @@ export function OrderAddress({ address }: Readonly<{ address?: OrderAddress }>)
19
19
  country,
20
20
  countryCode,
21
21
  phoneNumber,
22
- } = address;
22
+ } = address ?? {};
23
23
 
24
24
  const countryName = typeof country === 'string' ? country : country?.name;
25
25
  const countryCodeString = country && typeof country !== 'string' ? country?.code : countryCode;
@@ -1,3 +1,5 @@
1
+ import { Money } from '@/vdb/components/data-display/money.js';
2
+ import { Textarea } from '@/vdb/components/ui/textarea.js';
1
3
  import { Trans } from '@lingui/react/macro';
2
4
  import { ResultOf, VariablesOf } from 'gql.tada';
3
5
  import { modifyOrderDocument, orderDetailDocument } from '../orders.graphql.js';
@@ -15,6 +17,7 @@ interface OrderModificationSummaryProps {
15
17
  id: string;
16
18
  name: string;
17
19
  }>;
20
+ onNoteChange?: (note: string) => void;
18
21
  }
19
22
 
20
23
  interface LineAdjustment {
@@ -31,6 +34,7 @@ export function OrderModificationSummary({
31
34
  modifyOrderInput,
32
35
  addedVariants,
33
36
  eligibleShippingMethods,
37
+ onNoteChange,
34
38
  }: Readonly<OrderModificationSummaryProps>) {
35
39
  // Map by line id for quick lookup
36
40
  const originalLineMap = new Map(originalOrder.lines.map(line => [line.id, line]));
@@ -101,6 +105,20 @@ export function OrderModificationSummary({
101
105
  modifiedShippingMethodId;
102
106
  }
103
107
 
108
+ // Added surcharges
109
+ const addedSurcharges = modifyOrderInput.surcharges ?? [];
110
+
111
+ const hasNoModifications =
112
+ adjustedLines.length === 0 &&
113
+ addedLines.length === 0 &&
114
+ removedLines.length === 0 &&
115
+ addedCouponCodes.length === 0 &&
116
+ removedCouponCodes.length === 0 &&
117
+ addedSurcharges.length === 0 &&
118
+ !modifyOrderInput.updateShippingAddress &&
119
+ !modifyOrderInput.updateBillingAddress &&
120
+ !shippingMethodChanged;
121
+
104
122
  return (
105
123
  <div className="text-sm">
106
124
  {/* Address changes */}
@@ -206,18 +224,38 @@ export function OrderModificationSummary({
206
224
  </ul>
207
225
  </div>
208
226
  )}
209
- {adjustedLines.length === 0 &&
210
- addedLines.length === 0 &&
211
- removedLines.length === 0 &&
212
- addedCouponCodes.length === 0 &&
213
- removedCouponCodes.length === 0 &&
214
- !modifyOrderInput.updateShippingAddress &&
215
- !modifyOrderInput.updateBillingAddress &&
216
- !shippingMethodChanged && (
217
- <div className="text-muted-foreground">
218
- <Trans>No modifications made</Trans>
227
+ {addedSurcharges.length > 0 && (
228
+ <div className="mb-2">
229
+ <div className="font-medium">
230
+ <Trans>Adding {addedSurcharges.length} surcharge(s)</Trans>
219
231
  </div>
220
- )}
232
+ <ul className="list-disc ml-4">
233
+ {addedSurcharges.map((surcharge, index) => (
234
+ <li key={`surcharge-${index}`}>
235
+ <div className="flex items-center gap-1">
236
+ <span>{surcharge.description}:</span>
237
+ <Money value={surcharge.price} currency={originalOrder.currencyCode} />
238
+ </div>
239
+ </li>
240
+ ))}
241
+ </ul>
242
+ </div>
243
+ )}
244
+ {hasNoModifications && (
245
+ <div className="text-muted-foreground">
246
+ <Trans>No modifications made</Trans>
247
+ </div>
248
+ )}
249
+ <div className="mb-4 mt-4">
250
+ <div className="font-medium mb-2">
251
+ <Trans>Note</Trans>
252
+ </div>
253
+ <Textarea
254
+ disabled={hasNoModifications}
255
+ value={modifyOrderInput.note ?? ''}
256
+ onChange={e => onNoteChange?.(e.target.value)}
257
+ />
258
+ </div>
221
259
  </div>
222
260
  );
223
261
  }
@@ -16,6 +16,7 @@ import { createFileRoute, Link, useNavigate } from '@tanstack/react-router';
16
16
  import { User } from 'lucide-react';
17
17
  import { useState } from 'react';
18
18
  import { toast } from 'sonner';
19
+ import { AddSurchargeForm } from './components/add-surcharge-form.js';
19
20
  import { CustomerAddressSelector } from './components/customer-address-selector.js';
20
21
  import { EditOrderTable } from './components/edit-order-table.js';
21
22
  import { OrderAddress } from './components/order-address.js';
@@ -83,6 +84,8 @@ function ModifyOrderPage() {
83
84
  removeCouponCode,
84
85
  updateShippingAddress: updateShippingAddressInInput,
85
86
  updateBillingAddress: updateBillingAddressInInput,
87
+ addSurcharge,
88
+ setNote,
86
89
  hasModifications,
87
90
  } = useModifyOrder(entity);
88
91
 
@@ -181,6 +184,11 @@ function ModifyOrderPage() {
181
184
  displayTotals={false}
182
185
  />
183
186
  </PageBlock>
187
+
188
+ <PageBlock column="main" blockId="add-surcharge" title={<Trans>Add surcharge</Trans>}>
189
+ <AddSurchargeForm onAddSurcharge={addSurcharge} />
190
+ </PageBlock>
191
+
184
192
  <PageBlock
185
193
  column="side"
186
194
  blockId="modification-summary"
@@ -196,6 +204,7 @@ function ModifyOrderPage() {
196
204
  name: m.name,
197
205
  })) ?? []
198
206
  }
207
+ onNoteChange={setNote}
199
208
  />
200
209
  <div className="mt-4 flex justify-end">
201
210
  <Button
@@ -18,6 +18,8 @@ export type ProductVariantInfo = {
18
18
  priceWithTax?: number;
19
19
  };
20
20
 
21
+ type SurchargeInput = NonNullable<ModifyOrderInput['surcharges']>[number];
22
+
21
23
  export interface UseModifyOrderReturn {
22
24
  modifyOrderInput: ModifyOrderInput;
23
25
  addedVariants: Map<string, ProductVariantInfo>;
@@ -33,6 +35,8 @@ export interface UseModifyOrderReturn {
33
35
  removeCouponCode: (params: { couponCode: string }) => void;
34
36
  updateShippingAddress: (address: AddressFragment) => void;
35
37
  updateBillingAddress: (address: AddressFragment) => void;
38
+ addSurcharge: (surcharge: SurchargeInput) => void;
39
+ setNote: (note: string) => void;
36
40
  hasModifications: boolean;
37
41
  }
38
42
 
@@ -284,6 +288,22 @@ export function useModifyOrder(order: Order | null | undefined): UseModifyOrderR
284
288
  }));
285
289
  }, []);
286
290
 
291
+ // Add surcharge
292
+ const addSurcharge = useCallback((surcharge: SurchargeInput) => {
293
+ setModifyOrderInput(prev => ({
294
+ ...prev,
295
+ surcharges: [...(prev.surcharges ?? []), surcharge],
296
+ }));
297
+ }, []);
298
+
299
+ // Set note
300
+ const setNote = useCallback((note: string) => {
301
+ setModifyOrderInput(prev => ({
302
+ ...prev,
303
+ note: note || '',
304
+ }));
305
+ }, []);
306
+
287
307
  // Check if there are modifications
288
308
  const hasModifications = useMemo(() => {
289
309
  return (
@@ -291,6 +311,7 @@ export function useModifyOrder(order: Order | null | undefined): UseModifyOrderR
291
311
  (modifyOrderInput.adjustOrderLines?.length ?? 0) > 0 ||
292
312
  (modifyOrderInput.couponCodes?.length ?? 0) > 0 ||
293
313
  (modifyOrderInput.shippingMethodIds?.length ?? 0) > 0 ||
314
+ (modifyOrderInput.surcharges?.length ?? 0) > 0 ||
294
315
  !!modifyOrderInput.updateShippingAddress ||
295
316
  !!modifyOrderInput.updateBillingAddress
296
317
  );
@@ -307,6 +328,8 @@ export function useModifyOrder(order: Order | null | undefined): UseModifyOrderR
307
328
  removeCouponCode,
308
329
  updateShippingAddress,
309
330
  updateBillingAddress,
331
+ addSurcharge,
332
+ setNote,
310
333
  hasModifications,
311
334
  };
312
335
  }
@@ -1,14 +1,14 @@
1
1
  import { useLingui } from '@lingui/react/macro';
2
2
  import { PlusIcon } from 'lucide-react';
3
3
 
4
- import { Button } from '@/vdb/components/ui/button';
4
+ import { Button } from '@/vdb/components/ui/button.js';
5
5
  import {
6
6
  DropdownMenu,
7
7
  DropdownMenuContent,
8
8
  DropdownMenuItem,
9
9
  DropdownMenuTrigger,
10
- } from '@/vdb/components/ui/dropdown-menu';
11
- import { useLocalFormat } from '@/vdb/hooks/use-local-format';
10
+ } from '@/vdb/components/ui/dropdown-menu.js';
11
+ import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
12
12
 
13
13
  interface AddCurrencyDropdownProps {
14
14
  unusedCurrencies: string[];
@@ -1,13 +1,13 @@
1
1
  import { useLingui } from '@lingui/react/macro';
2
2
  import { PlusIcon } from 'lucide-react';
3
3
 
4
- import { Button } from '@/vdb/components/ui/button';
4
+ import { Button } from '@/vdb/components/ui/button.js';
5
5
  import {
6
6
  DropdownMenu,
7
7
  DropdownMenuContent,
8
8
  DropdownMenuItem,
9
9
  DropdownMenuTrigger,
10
- } from '@/vdb/components/ui/dropdown-menu';
10
+ } from '@/vdb/components/ui/dropdown-menu.js';
11
11
  import { ResultOf } from 'gql.tada';
12
12
 
13
13
  import { stockLocationsQueryDocument } from '../product-variants.graphql.js';
@@ -60,6 +60,7 @@ export const productDetailFragment = graphql(
60
60
  code
61
61
  }
62
62
  }
63
+ customFields
63
64
  }
64
65
  `,
65
66
  [assetFragment],
@@ -1,4 +1,4 @@
1
- import { AffixedInput } from '@/vdb/components/data-input/affixed-input.js';
1
+ import { NumberInput } from '@/vdb/components/data-input/number-input.js';
2
2
  import { ErrorPage } from '@/vdb/components/shared/error-page.js';
3
3
  import { FormFieldWrapper } from '@/vdb/components/shared/form-field-wrapper.js';
4
4
  import { PermissionGuard } from '@/vdb/components/shared/permission-guard.js';
@@ -121,14 +121,7 @@ function TaxRateDetailPage() {
121
121
  name="value"
122
122
  label={<Trans>Rate</Trans>}
123
123
  render={({ field }) => (
124
- <AffixedInput
125
- {...field}
126
- type="number"
127
- suffix="%"
128
- min={0}
129
- value={field.value}
130
- onChange={e => field.onChange(e.target.valueAsNumber)}
131
- />
124
+ <NumberInput {...field} value={field.value} min={0} step={0.01} suffix="%" />
132
125
  )}
133
126
  />
134
127
  <FormFieldWrapper