@vendure/dashboard 3.6.0-minor-202511061550 → 3.6.0-minor-202512161252

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 (152) hide show
  1. package/dist/plugin/constants.js +2 -2
  2. package/dist/vite/constants.js +1 -0
  3. package/dist/vite/utils/compiler.d.ts +1 -0
  4. package/dist/vite/utils/compiler.js +5 -4
  5. package/dist/vite/utils/get-dashboard-paths.d.ts +5 -0
  6. package/dist/vite/utils/get-dashboard-paths.js +20 -0
  7. package/dist/vite/vite-plugin-dashboard-metadata.js +2 -1
  8. package/dist/vite/vite-plugin-tailwind-source.js +2 -15
  9. package/dist/vite/vite-plugin-translations.d.ts +10 -1
  10. package/dist/vite/vite-plugin-translations.js +156 -45
  11. package/dist/vite/vite-plugin-vendure-dashboard.d.ts +12 -0
  12. package/dist/vite/vite-plugin-vendure-dashboard.js +1 -0
  13. package/lingui.config.js +1 -0
  14. package/package.json +7 -7
  15. package/src/app/routeTree.gen.ts +1221 -0
  16. package/src/app/routes/_authenticated/_administrators/administrators.tsx +9 -12
  17. package/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx +9 -12
  18. package/src/app/routes/_authenticated/_assets/assets_.$id.tsx +6 -9
  19. package/src/app/routes/_authenticated/_channels/channels.tsx +9 -12
  20. package/src/app/routes/_authenticated/_channels/channels_.$id.tsx +9 -12
  21. package/src/app/routes/_authenticated/_collections/collections.tsx +9 -12
  22. package/src/app/routes/_authenticated/_collections/collections_.$id.tsx +9 -12
  23. package/src/app/routes/_authenticated/_countries/countries.tsx +9 -12
  24. package/src/app/routes/_authenticated/_countries/countries_.$id.tsx +9 -12
  25. package/src/app/routes/_authenticated/_customer-groups/customer-groups.tsx +9 -12
  26. package/src/app/routes/_authenticated/_customer-groups/customer-groups_.$id.tsx +9 -12
  27. package/src/app/routes/_authenticated/_customers/components/customer-history/index.ts +0 -1
  28. package/src/app/routes/_authenticated/_customers/customers.tsx +9 -12
  29. package/src/app/routes/_authenticated/_customers/customers_.$id.tsx +9 -12
  30. package/src/app/routes/_authenticated/_facets/facets.tsx +9 -12
  31. package/src/app/routes/_authenticated/_facets/facets_.$facetId.values_.$id.tsx +9 -12
  32. package/src/app/routes/_authenticated/_facets/facets_.$id.tsx +9 -12
  33. package/src/app/routes/_authenticated/_global-settings/global-settings.tsx +10 -13
  34. package/src/app/routes/_authenticated/_orders/components/add-surcharge-form.tsx +139 -0
  35. package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +3 -0
  36. package/src/app/routes/_authenticated/_orders/components/fulfill-order-dialog.tsx +3 -1
  37. package/src/app/routes/_authenticated/_orders/components/order-address.tsx +3 -3
  38. package/src/app/routes/_authenticated/_orders/components/order-detail-shared.tsx +41 -41
  39. package/src/app/routes/_authenticated/_orders/components/order-history/order-history-utils.tsx +1 -1
  40. package/src/app/routes/_authenticated/_orders/components/order-modification-summary.tsx +49 -11
  41. package/src/app/routes/_authenticated/_orders/components/order-table.tsx +4 -1
  42. package/src/app/routes/_authenticated/_orders/components/use-transition-order-to-state.tsx +2 -3
  43. package/src/app/routes/_authenticated/_orders/orders.tsx +3 -3
  44. package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +12 -3
  45. package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +27 -30
  46. package/src/app/routes/_authenticated/_orders/utils/use-modify-order.ts +23 -0
  47. package/src/app/routes/_authenticated/_payment-methods/payment-methods.tsx +9 -12
  48. package/src/app/routes/_authenticated/_payment-methods/payment-methods_.$id.tsx +9 -12
  49. package/src/app/routes/_authenticated/_product-variants/components/add-currency-dropdown.tsx +3 -3
  50. package/src/app/routes/_authenticated/_product-variants/components/add-stock-location-dropdown.tsx +2 -2
  51. package/src/app/routes/_authenticated/_product-variants/product-variants.graphql.ts +1 -0
  52. package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +10 -12
  53. package/src/app/routes/_authenticated/_products/products.graphql.ts +1 -0
  54. package/src/app/routes/_authenticated/_products/products.tsx +15 -18
  55. package/src/app/routes/_authenticated/_products/products_.$id.tsx +9 -12
  56. package/src/app/routes/_authenticated/_products/products_.$productId.option-groups.$id.tsx +9 -12
  57. package/src/app/routes/_authenticated/_products/products_.$productId.option-groups.$productOptionGroupId.options_.$id.tsx +9 -12
  58. package/src/app/routes/_authenticated/_profile/profile.tsx +3 -3
  59. package/src/app/routes/_authenticated/_promotions/promotions.tsx +9 -12
  60. package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +9 -12
  61. package/src/app/routes/_authenticated/_roles/roles.tsx +9 -12
  62. package/src/app/routes/_authenticated/_roles/roles_.$id.tsx +9 -12
  63. package/src/app/routes/_authenticated/_sellers/sellers.tsx +9 -12
  64. package/src/app/routes/_authenticated/_sellers/sellers_.$id.tsx +9 -12
  65. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.tsx +11 -12
  66. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +19 -20
  67. package/src/app/routes/_authenticated/_stock-locations/stock-locations.tsx +9 -12
  68. package/src/app/routes/_authenticated/_stock-locations/stock-locations_.$id.tsx +9 -12
  69. package/src/app/routes/_authenticated/_system/healthchecks.tsx +2 -3
  70. package/src/app/routes/_authenticated/_system/job-queue.tsx +3 -3
  71. package/src/app/routes/_authenticated/_tax-categories/tax-categories.tsx +9 -12
  72. package/src/app/routes/_authenticated/_tax-categories/tax-categories_.$id.tsx +9 -12
  73. package/src/app/routes/_authenticated/_tax-rates/tax-rates.tsx +9 -12
  74. package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +9 -12
  75. package/src/app/routes/_authenticated/_zones/components/zone-bulk-actions.tsx +49 -1
  76. package/src/app/routes/_authenticated/_zones/components/zone-countries-table.tsx +34 -16
  77. package/src/app/routes/_authenticated/_zones/zones.tsx +9 -12
  78. package/src/app/routes/_authenticated/_zones/zones_.$id.tsx +9 -12
  79. package/src/app/routes/_authenticated/index.tsx +5 -3
  80. package/src/i18n/locales/bg.po +3436 -0
  81. package/src/lib/components/data-input/datetime-input.tsx +1 -1
  82. package/src/lib/components/data-input/default-relation-input.tsx +1 -1
  83. package/src/lib/components/data-input/relation-selector.tsx +1 -1
  84. package/src/lib/components/data-input/string-list-input.tsx +188 -26
  85. package/src/lib/components/data-input/struct-form-input.tsx +175 -174
  86. package/src/lib/components/data-table/column-header-wrapper.tsx +1 -1
  87. package/src/lib/components/data-table/data-table-filter-badge.tsx +2 -2
  88. package/src/lib/components/data-table/data-table.tsx +1 -1
  89. package/src/lib/components/data-table/use-generated-columns.tsx +1 -1
  90. package/src/lib/components/layout/channel-switcher.tsx +6 -2
  91. package/src/lib/components/layout/content-language-selector.tsx +6 -7
  92. package/src/lib/components/layout/dev-mode-indicator.tsx +7 -3
  93. package/src/lib/components/layout/language-dialog.tsx +26 -13
  94. package/src/lib/components/layout/manage-languages-dialog.tsx +10 -29
  95. package/src/lib/components/layout/nav-item-wrapper.tsx +1 -1
  96. package/src/lib/components/shared/asset/asset-gallery.tsx +8 -3
  97. package/src/lib/components/shared/configurable-operation-multi-selector.tsx +14 -16
  98. package/src/lib/components/shared/custom-fields-form.tsx +14 -9
  99. package/src/lib/components/shared/language-selector.tsx +14 -6
  100. package/src/lib/components/shared/multi-select.tsx +1 -1
  101. package/src/lib/components/shared/navigation-confirmation.tsx +1 -1
  102. package/src/lib/components/shared/table-cell/order-table-cell-components.tsx +4 -4
  103. package/src/lib/components/ui/carousel.tsx +2 -2
  104. package/src/lib/components/ui/chart.tsx +1 -1
  105. package/src/lib/components/ui/context-menu.tsx +1 -1
  106. package/src/lib/components/ui/drawer.tsx +1 -1
  107. package/src/lib/components/ui/grid-layout.tsx +1 -1
  108. package/src/lib/components/ui/input-group.tsx +1 -0
  109. package/src/lib/components/ui/input-otp.tsx +1 -1
  110. package/src/lib/components/ui/menubar.tsx +1 -1
  111. package/src/lib/components/ui/navigation-menu.tsx +1 -1
  112. package/src/lib/components/ui/progress.tsx +1 -1
  113. package/src/lib/components/ui/radio-group.tsx +1 -1
  114. package/src/lib/components/ui/resizable.tsx +1 -1
  115. package/src/lib/components/ui/select.tsx +1 -1
  116. package/src/lib/components/ui/slider.tsx +1 -1
  117. package/src/lib/components/ui/toggle-group.tsx +2 -2
  118. package/src/lib/components/ui/toggle.tsx +1 -1
  119. package/src/lib/framework/component-registry/component-registry.tsx +2 -6
  120. package/src/lib/framework/document-introspection/add-custom-fields.spec.ts +907 -1
  121. package/src/lib/framework/document-introspection/add-custom-fields.ts +248 -119
  122. package/src/lib/framework/extension-api/display-component-extensions.tsx +4 -3
  123. package/src/lib/framework/extension-api/logic/detail-forms.ts +0 -13
  124. package/src/lib/framework/extension-api/logic/navigation.ts +1 -1
  125. package/src/lib/framework/extension-api/types/data-table.ts +4 -2
  126. package/src/lib/framework/extension-api/types/layout.ts +34 -1
  127. package/src/lib/framework/extension-api/types/navigation.ts +7 -2
  128. package/src/lib/framework/form-engine/use-generated-form.tsx +7 -1
  129. package/src/lib/framework/history-entry/history-entry.tsx +1 -1
  130. package/src/lib/framework/layout-engine/action-bar-item-wrapper.tsx +185 -0
  131. package/src/lib/framework/layout-engine/dev-mode-button.tsx +15 -13
  132. package/src/lib/framework/layout-engine/location-wrapper.tsx +3 -1
  133. package/src/lib/framework/layout-engine/page-layout.spec.tsx +138 -0
  134. package/src/lib/framework/layout-engine/page-layout.tsx +294 -69
  135. package/src/lib/framework/nav-menu/nav-menu-extensions.ts +1 -1
  136. package/src/lib/framework/page/detail-page-route-loader.tsx +1 -1
  137. package/src/lib/framework/page/page-api.ts +1 -1
  138. package/src/lib/framework/page/use-detail-page.ts +4 -2
  139. package/src/lib/framework/page/use-extended-router.tsx +20 -16
  140. package/src/lib/framework/registry/registry-types.ts +2 -1
  141. package/src/lib/graphql/api.ts +3 -8
  142. package/src/lib/graphql/graphql-env.d.ts +29 -10
  143. package/src/lib/hooks/use-permissions.ts +3 -3
  144. package/src/lib/hooks/use-sorted-languages.ts +41 -0
  145. package/src/lib/index.ts +1 -0
  146. package/src/lib/lib/load-i18n-messages.ts +4 -1
  147. package/src/lib/providers/channel-provider.tsx +11 -7
  148. package/src/lib/utils/config-utils.ts +19 -0
  149. package/src/lib/virtual.d.ts +3 -0
  150. package/LICENSE.md +0 -42
  151. package/src/app/routes/_authenticated/_facets/components/edit-facet-value.tsx +0 -129
  152. /package/src/{app/routes/_authenticated/_global-settings → lib}/utils/global-languages.ts +0 -0
@@ -1,18 +1,17 @@
1
1
  import { SlugInput } from '@/vdb/components/data-input/index.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
- import { PermissionGuard } from '@/vdb/components/shared/permission-guard.js';
5
4
  import { TranslatableFormFieldWrapper } from '@/vdb/components/shared/translatable-form-field.js';
6
5
  import { Button } from '@/vdb/components/ui/button.js';
7
6
  import { Input } from '@/vdb/components/ui/input.js';
8
7
  import { Switch } from '@/vdb/components/ui/switch.js';
9
8
  import { NEW_ENTITY_PATH } from '@/vdb/constants.js';
10
9
  import {
10
+ ActionBarItem,
11
11
  CustomFieldsPageBlock,
12
12
  DetailFormGrid,
13
13
  Page,
14
14
  PageActionBar,
15
- PageActionBarRight,
16
15
  PageBlock,
17
16
  PageLayout,
18
17
  PageTitle,
@@ -93,16 +92,14 @@ function FacetDetailPage() {
93
92
  <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
94
93
  <PageTitle>{creatingNewEntity ? <Trans>New facet</Trans> : (entity?.name ?? '')}</PageTitle>
95
94
  <PageActionBar>
96
- <PageActionBarRight>
97
- <PermissionGuard requires={['UpdateProduct', 'UpdateCatalog']}>
98
- <Button
99
- type="submit"
100
- disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
101
- >
102
- {creatingNewEntity ? <Trans>Create</Trans> : <Trans>Update</Trans>}
103
- </Button>
104
- </PermissionGuard>
105
- </PageActionBarRight>
95
+ <ActionBarItem itemId="save-button" requiresPermission={['UpdateProduct', 'UpdateCatalog']}>
96
+ <Button
97
+ type="submit"
98
+ disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
99
+ >
100
+ {creatingNewEntity ? <Trans>Create</Trans> : <Trans>Update</Trans>}
101
+ </Button>
102
+ </ActionBarItem>
106
103
  </PageActionBar>
107
104
  <PageLayout>
108
105
  <PageBlock column="side" blockId="privacy">
@@ -2,18 +2,17 @@ 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 { LanguageSelector } from '@/vdb/components/shared/language-selector.js';
5
- import { PermissionGuard } from '@/vdb/components/shared/permission-guard.js';
6
5
  import { Button } from '@/vdb/components/ui/button.js';
7
6
  import { Switch } from '@/vdb/components/ui/switch.js';
8
7
  import { NEW_ENTITY_PATH } from '@/vdb/constants.js';
9
8
  import { extendDetailFormQuery } from '@/vdb/framework/document-extension/extend-detail-form-query.js';
10
9
  import { addCustomFields } from '@/vdb/framework/document-introspection/add-custom-fields.js';
11
10
  import {
11
+ ActionBarItem,
12
12
  CustomFieldsPageBlock,
13
13
  DetailFormGrid,
14
14
  Page,
15
15
  PageActionBar,
16
- PageActionBarRight,
17
16
  PageBlock,
18
17
  PageLayout,
19
18
  PageTitle,
@@ -23,7 +22,7 @@ import { Trans, useLingui } from '@lingui/react/macro';
23
22
  import { createFileRoute, useNavigate } from '@tanstack/react-router';
24
23
  import { toast } from 'sonner';
25
24
  import { globalSettingsDocument, updateGlobalSettingsDocument } from './global-settings.graphql.js';
26
- import { globalLanguageCodes } from './utils/global-languages.js';
25
+ import { globalLanguageCodes } from '@/vdb/utils/global-languages.js';
27
26
 
28
27
  const pageId = 'global-settings';
29
28
 
@@ -92,16 +91,14 @@ function GlobalSettingsPage() {
92
91
  <Trans>Global Settings</Trans>
93
92
  </PageTitle>
94
93
  <PageActionBar>
95
- <PageActionBarRight>
96
- <PermissionGuard requires={['UpdateSettings', 'UpdateGlobalSettings']}>
97
- <Button
98
- type="submit"
99
- disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
100
- >
101
- <Trans>Update</Trans>
102
- </Button>
103
- </PermissionGuard>
104
- </PageActionBarRight>
94
+ <ActionBarItem itemId="save-button" requiresPermission={['UpdateSettings', 'UpdateGlobalSettings']}>
95
+ <Button
96
+ type="submit"
97
+ disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
98
+ >
99
+ <Trans>Update</Trans>
100
+ </Button>
101
+ </ActionBarItem>
105
102
  </PageActionBar>
106
103
  <PageLayout>
107
104
  <PageBlock column="main" blockId="main-form">
@@ -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>
@@ -290,7 +290,9 @@ export function FulfillOrderDialog({ order, onSuccess }: Readonly<FulfillOrderDi
290
290
  render={({ field }) => (
291
291
  <ConfigurableOperationInput
292
292
  operationDefinition={selectedHandler}
293
- value={field.value}
293
+ value={
294
+ field.value ?? { code: selectedHandler.code, arguments: [] }
295
+ }
294
296
  onChange={field.onChange}
295
297
  readonly={false}
296
298
  removable={false}
@@ -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,11 +1,11 @@
1
1
  import { CustomFieldsForm } from '@/vdb/components/shared/custom-fields-form.js';
2
- import { PermissionGuard } from '@/vdb/components/shared/permission-guard.js';
3
2
  import { Button } from '@/vdb/components/ui/button.js';
4
3
  import { DropdownMenuItem } from '@/vdb/components/ui/dropdown-menu.js';
4
+ import { addCustomFields } from '@/vdb/framework/document-introspection/add-custom-fields.js';
5
5
  import {
6
+ ActionBarItem,
6
7
  Page,
7
8
  PageActionBar,
8
- PageActionBarRight,
9
9
  PageBlock,
10
10
  PageLayout,
11
11
  PageTitle,
@@ -72,7 +72,9 @@ export function OrderDetailShared({
72
72
 
73
73
  const { form, submitHandler, entity, refreshEntity } = useDetailPage({
74
74
  pageId,
75
- queryDocument: orderDetailDocument,
75
+ queryDocument: addCustomFields(orderDetailDocument, {
76
+ includeNestedFragments: ['OrderLine'],
77
+ }),
76
78
  updateDocument: setOrderCustomFieldsDocument,
77
79
  setValuesForUpdate: (entity: any) => {
78
80
  return {
@@ -154,44 +156,42 @@ export function OrderDetailShared({
154
156
  return (
155
157
  <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
156
158
  <PageTitle>{titleSlot?.(entity) || <DefaultOrderTitle entity={entity} />}</PageTitle>
157
- <PageActionBar>
158
- <PageActionBarRight
159
- dropdownMenuItems={[
160
- ...(nextStates.includes('Modifying')
161
- ? [
162
- {
163
- component: () => (
164
- <DropdownMenuItem onClick={handleModifyClick}>
165
- <Pencil className="w-4 h-4" />
166
- <Trans>Modify</Trans>
167
- </DropdownMenuItem>
168
- ),
169
- },
170
- ]
171
- : []),
172
- ]}
173
- >
174
- {showAddPaymentButton && (
175
- <PermissionGuard requires={['UpdateOrder']}>
176
- <AddManualPaymentDialog
177
- order={entity}
178
- onSuccess={() => {
179
- refreshEntity();
180
- }}
181
- />
182
- </PermissionGuard>
183
- )}
184
- {showFulfillButton && (
185
- <PermissionGuard requires={['UpdateOrder']}>
186
- <FulfillOrderDialog
187
- order={entity}
188
- onSuccess={() => {
189
- refreshOrderAndHistory();
190
- }}
191
- />
192
- </PermissionGuard>
193
- )}
194
- </PageActionBarRight>
159
+ <PageActionBar
160
+ dropdownMenuItems={[
161
+ ...(nextStates.includes('Modifying')
162
+ ? [
163
+ {
164
+ component: () => (
165
+ <DropdownMenuItem onClick={handleModifyClick}>
166
+ <Pencil className="w-4 h-4" />
167
+ <Trans>Modify</Trans>
168
+ </DropdownMenuItem>
169
+ ),
170
+ },
171
+ ]
172
+ : []),
173
+ ]}
174
+ >
175
+ {showAddPaymentButton && (
176
+ <ActionBarItem itemId="add-payment-button" requiresPermission={['UpdateOrder']}>
177
+ <AddManualPaymentDialog
178
+ order={entity}
179
+ onSuccess={() => {
180
+ refreshEntity();
181
+ }}
182
+ />
183
+ </ActionBarItem>
184
+ )}
185
+ {showFulfillButton && (
186
+ <ActionBarItem itemId="fulfill-order-button" requiresPermission={['UpdateOrder']}>
187
+ <FulfillOrderDialog
188
+ order={entity}
189
+ onSuccess={() => {
190
+ refreshOrderAndHistory();
191
+ }}
192
+ />
193
+ </ActionBarItem>
194
+ )}
195
195
  </PageActionBar>
196
196
  <PageLayout>
197
197
  {/* Main Column Blocks */}
@@ -105,7 +105,7 @@ export function orderHistoryUtils(order: OrderHistoryOrderDetail) {
105
105
 
106
106
  const getIconColor = ({ type, data }: HistoryEntryItem) => {
107
107
  const success = 'bg-success text-success-foreground';
108
- const destructive = 'bg-danger text-danger-foreground';
108
+ const destructive = 'bg-destructive text-destructive-foreground';
109
109
  const regular = 'bg-muted text-muted-foreground';
110
110
 
111
111
  if (type === 'ORDER_PAYMENT_TRANSITION' && data.to === 'Settled') {
@@ -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
  }
@@ -147,7 +147,10 @@ export function OrderTable({ order, pageId }: Readonly<OrderTableProps>) {
147
147
  ];
148
148
  const currencyCode = order.currencyCode;
149
149
 
150
- const fields = getFieldsFromDocumentNode(addCustomFields(orderDetailDocument), ['order', 'lines']);
150
+ const fields = getFieldsFromDocumentNode(
151
+ addCustomFields(orderDetailDocument, { includeNestedFragments: ['OrderLine'] }),
152
+ ['order', 'lines'],
153
+ );
151
154
 
152
155
  const customizeColumns = useMemo(() => createCustomizeColumns(currencyCode), [currencyCode]);
153
156
 
@@ -22,8 +22,7 @@ import { orderHistoryDocument, transitionOrderToStateDocument } from '../orders.
22
22
  */
23
23
  export function useTransitionOrderToState(orderId: string | undefined) {
24
24
  const [selectStateOpen, setSelectStateOpen] = useState(false);
25
- const [onSuccessFn, setOnSuccessFn] = useState<() => void>(() => {
26
- });
25
+ const [onSuccessFn, setOnSuccessFn] = useState<() => void>(() => {});
27
26
  const { data, isLoading, error } = useQuery({
28
27
  queryKey: ['orderPreModifyingState', orderId],
29
28
  queryFn: async () => {
@@ -142,7 +141,7 @@ export function useTransitionOrderToState(orderId: string | undefined) {
142
141
  transitionToPreModifyingState,
143
142
  transitionToState,
144
143
  ManuallySelectNextState,
145
- selectNextState: ({ onSuccess }: { onSuccess?: () => void }) => {
144
+ selectNextState: ({ onSuccess }: { onSuccess?: () => void | Promise<void> }) => {
146
145
  setSelectStateOpen(true);
147
146
  if (onSuccess) {
148
147
  setOnSuccessFn(() => onSuccess);
@@ -5,7 +5,7 @@ import {
5
5
  OrderStateCell,
6
6
  } from '@/vdb/components/shared/table-cell/order-table-cell-components.js';
7
7
  import { Button } from '@/vdb/components/ui/button.js';
8
- import { PageActionBarRight } from '@/vdb/framework/layout-engine/page-layout.js';
8
+ import { ActionBarItem } from '@/vdb/framework/layout-engine/page-layout.js';
9
9
  import { ListPage } from '@/vdb/framework/page/list-page.js';
10
10
  import { api } from '@/vdb/graphql/api.js';
11
11
  import { ResultOf } from '@/vdb/graphql/graphql.js';
@@ -109,12 +109,12 @@ function OrderListPage() {
109
109
  },
110
110
  }}
111
111
  >
112
- <PageActionBarRight>
112
+ <ActionBarItem itemId="create-draft-button">
113
113
  <Button onClick={() => createDraftOrder({})}>
114
114
  <PlusIcon className="mr-2 h-4 w-4" />
115
115
  <Trans>Draft order</Trans>
116
116
  </Button>
117
- </PageActionBarRight>
117
+ </ActionBarItem>
118
118
  </ListPage>
119
119
  );
120
120
  }
@@ -1,9 +1,9 @@
1
1
  import { ErrorPage } from '@/vdb/components/shared/error-page.js';
2
2
  import { Button } from '@/vdb/components/ui/button.js';
3
3
  import {
4
+ ActionBarItem,
4
5
  Page,
5
6
  PageActionBar,
6
- PageActionBarRight,
7
7
  PageBlock,
8
8
  PageLayout,
9
9
  PageTitle,
@@ -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
 
@@ -159,11 +162,11 @@ function ModifyOrderPage() {
159
162
  <Trans>Modify order</Trans>
160
163
  </PageTitle>
161
164
  <PageActionBar>
162
- <PageActionBarRight>
165
+ <ActionBarItem itemId="cancel-modification-button">
163
166
  <Button type="button" variant="secondary" onClick={handleCancelModificationClick}>
164
167
  <Trans>Cancel modification</Trans>
165
168
  </Button>
166
- </PageActionBarRight>
169
+ </ActionBarItem>
167
170
  </PageActionBar>
168
171
  <PageLayout>
169
172
  <PageBlock column="main" blockId="order-lines" title={<Trans>Order lines</Trans>}>
@@ -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