@vendure/dashboard 3.5.0-minor-202510071456 → 3.5.0-minor-202510201346

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 (204) hide show
  1. package/dist/plugin/dashboard.plugin.js +1 -1
  2. package/dist/vite/utils/ast-utils.spec.js +3 -3
  3. package/dist/vite/vite-plugin-hmr.d.ts +8 -0
  4. package/dist/vite/vite-plugin-hmr.js +34 -0
  5. package/dist/vite/vite-plugin-theme.js +6 -6
  6. package/dist/vite/vite-plugin-transform-index.js +6 -1
  7. package/dist/vite/vite-plugin-vendure-dashboard.d.ts +31 -4
  8. package/dist/vite/vite-plugin-vendure-dashboard.js +89 -34
  9. package/package.json +17 -5
  10. package/src/app/app-providers.tsx +4 -1
  11. package/src/app/common/map-faceted-filter-fields.ts +21 -0
  12. package/src/app/main.tsx +3 -1
  13. package/src/app/routes/_authenticated/_administrators/administrators.graphql.ts +2 -2
  14. package/src/app/routes/_authenticated/_administrators/administrators.tsx +13 -3
  15. package/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx +6 -13
  16. package/src/app/routes/_authenticated/_administrators/components/role-permissions-display.tsx +1 -1
  17. package/src/app/routes/_authenticated/_assets/assets.tsx +17 -1
  18. package/src/app/routes/_authenticated/_collections/collections.graphql.ts +1 -0
  19. package/src/app/routes/_authenticated/_collections/collections.tsx +5 -0
  20. package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +0 -1
  21. package/src/app/routes/_authenticated/_customers/customers.tsx +9 -5
  22. package/src/app/routes/_authenticated/_facets/components/facet-bulk-actions.tsx +0 -6
  23. package/src/app/routes/_authenticated/_facets/components/facet-value-bulk-actions.tsx +16 -0
  24. package/src/app/routes/_authenticated/_facets/components/facet-values-table.tsx +43 -12
  25. package/src/app/routes/_authenticated/_facets/facets_.$facetId.values_.$id.tsx +14 -5
  26. package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +117 -92
  27. package/src/app/routes/_authenticated/_orders/components/order-detail-shared.tsx +1 -1
  28. package/src/app/routes/_authenticated/_orders/components/order-modification-summary.tsx +2 -1
  29. package/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx +26 -27
  30. package/src/app/routes/_authenticated/_orders/components/order-table.tsx +5 -3
  31. package/src/app/routes/_authenticated/_orders/components/state-transition-control.tsx +6 -9
  32. package/src/app/routes/_authenticated/_orders/orders.graphql.ts +17 -1
  33. package/src/app/routes/_authenticated/_orders/orders.tsx +2 -0
  34. package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +48 -281
  35. package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +59 -40
  36. package/src/app/routes/_authenticated/_orders/utils/order-utils.ts +73 -0
  37. package/src/app/routes/_authenticated/_orders/utils/use-modify-order.ts +312 -0
  38. package/src/app/routes/_authenticated/_payment-methods/payment-methods.graphql.ts +2 -2
  39. package/src/app/routes/_authenticated/_payment-methods/payment-methods.tsx +4 -0
  40. package/src/app/routes/_authenticated/_product-variants/product-variants.tsx +2 -0
  41. package/src/app/routes/_authenticated/_products/components/product-bulk-actions.tsx +0 -6
  42. package/src/app/routes/_authenticated/_products/products.tsx +6 -2
  43. package/src/app/routes/_authenticated/_products/products_.$productId.option-groups.$productOptionGroupId.options_.$id.tsx +4 -8
  44. package/src/app/routes/_authenticated/_promotions/components/promotion-bulk-actions.tsx +0 -10
  45. package/src/app/routes/_authenticated/_promotions/promotions.graphql.ts +2 -2
  46. package/src/app/routes/_authenticated/_promotions/promotions.tsx +12 -0
  47. package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +6 -2
  48. package/src/app/routes/_authenticated/_sellers/sellers.graphql.ts +2 -2
  49. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.graphql.ts +2 -2
  50. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.tsx +4 -0
  51. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +4 -10
  52. package/src/app/routes/_authenticated/_stock-locations/stock-locations.graphql.ts +2 -2
  53. package/src/app/routes/_authenticated/_tax-categories/tax-categories.graphql.ts +2 -2
  54. package/src/app/routes/_authenticated/_tax-rates/tax-rates.tsx +9 -0
  55. package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +1 -0
  56. package/src/app/routes/_authenticated/_zones/zones.graphql.ts +2 -2
  57. package/src/app/routes/login.tsx +2 -2
  58. package/src/i18n/locales/ar.po +420 -289
  59. package/src/i18n/locales/cs.po +420 -289
  60. package/src/i18n/locales/de.po +420 -289
  61. package/src/i18n/locales/en.po +420 -289
  62. package/src/i18n/locales/es.po +420 -289
  63. package/src/i18n/locales/fa.po +420 -289
  64. package/src/i18n/locales/fr.po +468 -337
  65. package/src/i18n/locales/he.po +420 -289
  66. package/src/i18n/locales/hr.po +420 -289
  67. package/src/i18n/locales/it.po +420 -289
  68. package/src/i18n/locales/ja.po +420 -289
  69. package/src/i18n/locales/nb.po +420 -289
  70. package/src/i18n/locales/ne.po +420 -289
  71. package/src/i18n/locales/pl.po +420 -289
  72. package/src/i18n/locales/pt_BR.po +420 -289
  73. package/src/i18n/locales/pt_PT.po +420 -289
  74. package/src/i18n/locales/ru.po +420 -289
  75. package/src/i18n/locales/sv.po +420 -289
  76. package/src/i18n/locales/tr.po +420 -289
  77. package/src/i18n/locales/uk.po +420 -289
  78. package/src/i18n/locales/zh_Hans.po +420 -289
  79. package/src/i18n/locales/zh_Hant.po +420 -289
  80. package/src/lib/components/data-input/affixed-input.stories.tsx +93 -0
  81. package/src/lib/components/data-input/affixed-input.tsx +5 -2
  82. package/src/lib/components/data-input/boolean-input.stories.tsx +102 -0
  83. package/src/lib/components/data-input/checkbox-input.stories.tsx +61 -0
  84. package/src/lib/components/data-input/datetime-input.stories.tsx +62 -0
  85. package/src/lib/components/data-input/datetime-input.tsx +27 -13
  86. package/src/lib/components/data-input/default-relation-input.tsx +18 -12
  87. package/src/lib/components/data-input/money-input.stories.tsx +88 -0
  88. package/src/lib/components/data-input/number-input.stories.tsx +103 -0
  89. package/src/lib/components/data-input/number-input.tsx +10 -4
  90. package/src/lib/components/data-input/password-form-input.stories.tsx +65 -0
  91. package/src/lib/components/data-input/{password-input.tsx → password-form-input.tsx} +1 -1
  92. package/src/lib/components/data-input/rich-text-input.stories.tsx +92 -0
  93. package/src/lib/components/data-input/slug-input.stories.tsx +232 -0
  94. package/src/lib/components/data-input/slug-input.tsx +9 -10
  95. package/src/lib/components/data-input/text-input.stories.tsx +52 -0
  96. package/src/lib/components/data-input/textarea-input.stories.tsx +55 -0
  97. package/src/lib/components/data-table/add-filter-menu.tsx +6 -1
  98. package/src/lib/components/data-table/column-header-wrapper.tsx +106 -0
  99. package/src/lib/components/data-table/data-table-bulk-action-item.tsx +11 -9
  100. package/src/lib/components/data-table/data-table-bulk-actions.tsx +4 -4
  101. package/src/lib/components/data-table/data-table-column-header.tsx +17 -14
  102. package/src/lib/components/data-table/data-table-faceted-filter.tsx +33 -11
  103. package/src/lib/components/data-table/data-table-filter-badge-editable.tsx +35 -0
  104. package/src/lib/components/data-table/data-table-filter-badge.tsx +23 -16
  105. package/src/lib/components/data-table/data-table-filter-dialog.tsx +28 -8
  106. package/src/lib/components/data-table/data-table-pagination.tsx +23 -7
  107. package/src/lib/components/data-table/data-table.stories.tsx +249 -0
  108. package/src/lib/components/data-table/data-table.tsx +37 -9
  109. package/src/lib/components/data-table/filters/data-table-datetime-filter.tsx +79 -34
  110. package/src/lib/components/data-table/use-generated-columns.tsx +55 -27
  111. package/src/lib/components/layout/nav-user.tsx +19 -13
  112. package/src/lib/components/login/login-form.tsx +39 -123
  113. package/src/lib/components/shared/alerts.tsx +29 -17
  114. package/src/lib/components/shared/asset/asset-bulk-actions.tsx +3 -3
  115. package/src/lib/components/shared/asset/asset-gallery.stories.tsx +76 -0
  116. package/src/lib/components/shared/asset/asset-gallery.tsx +147 -113
  117. package/src/lib/components/shared/asset/asset-picker-dialog.stories.tsx +58 -0
  118. package/src/lib/components/shared/customer-group-selector.tsx +5 -2
  119. package/src/lib/components/shared/detail-page-button.stories.tsx +52 -0
  120. package/src/lib/components/shared/facet-value-selector.stories.tsx +48 -0
  121. package/src/lib/components/shared/facet-value-selector.tsx +130 -34
  122. package/src/lib/components/shared/paginated-list-data-table.stories.tsx +212 -0
  123. package/src/lib/components/shared/paginated-list-data-table.tsx +12 -12
  124. package/src/lib/components/shared/permission-guard.stories.tsx +46 -0
  125. package/src/lib/components/shared/remove-from-channel-bulk-action.tsx +2 -0
  126. package/src/lib/components/shared/rich-text-editor/responsive-toolbar.tsx +8 -4
  127. package/src/lib/components/shared/rich-text-editor/rich-text-editor.tsx +1 -0
  128. package/src/lib/components/shared/table-cell/order-table-cell-components.tsx +40 -0
  129. package/src/lib/components/shared/vendure-image.stories.tsx +167 -0
  130. package/src/lib/components/shared/vendure-image.tsx +6 -7
  131. package/src/lib/components/ui/accordion.stories.tsx +33 -0
  132. package/src/lib/components/ui/alert-dialog.stories.tsx +48 -0
  133. package/src/lib/components/ui/alert.stories.tsx +35 -0
  134. package/src/lib/components/ui/aspect-ratio.stories.tsx +28 -0
  135. package/src/lib/components/ui/badge.stories.tsx +28 -0
  136. package/src/lib/components/ui/breadcrumb.stories.tsx +41 -0
  137. package/src/lib/components/ui/button.stories.tsx +38 -0
  138. package/src/lib/components/ui/calendar.stories.tsx +22 -0
  139. package/src/lib/components/ui/card.stories.tsx +28 -0
  140. package/src/lib/components/ui/carousel.stories.tsx +34 -0
  141. package/src/lib/components/ui/checkbox.stories.tsx +31 -0
  142. package/src/lib/components/ui/collapsible.stories.tsx +39 -0
  143. package/src/lib/components/ui/command.stories.tsx +44 -0
  144. package/src/lib/components/ui/context-menu.stories.tsx +38 -0
  145. package/src/lib/components/ui/dialog.stories.tsx +52 -0
  146. package/src/lib/components/ui/drawer.stories.tsx +50 -0
  147. package/src/lib/components/ui/dropdown-menu.stories.tsx +41 -0
  148. package/src/lib/components/ui/hover-card.stories.tsx +38 -0
  149. package/src/lib/components/ui/input-group.tsx +148 -0
  150. package/src/lib/components/ui/input-otp.stories.tsx +30 -0
  151. package/src/lib/components/ui/input.stories.tsx +38 -0
  152. package/src/lib/components/ui/label.stories.tsx +24 -0
  153. package/src/lib/components/ui/menubar.stories.tsx +53 -0
  154. package/src/lib/components/ui/navigation-menu.stories.tsx +54 -0
  155. package/src/lib/components/ui/pagination.stories.tsx +51 -0
  156. package/src/lib/components/ui/password-input.stories.tsx +32 -0
  157. package/src/lib/components/ui/password-input.tsx +29 -0
  158. package/src/lib/components/ui/popover.stories.tsx +33 -0
  159. package/src/lib/components/ui/progress.stories.tsx +27 -0
  160. package/src/lib/components/ui/radio-group.stories.tsx +34 -0
  161. package/src/lib/components/ui/resizable.stories.tsx +32 -0
  162. package/src/lib/components/ui/scroll-area.stories.tsx +31 -0
  163. package/src/lib/components/ui/select.stories.tsx +36 -0
  164. package/src/lib/components/ui/separator.stories.tsx +35 -0
  165. package/src/lib/components/ui/sheet.stories.tsx +50 -0
  166. package/src/lib/components/ui/sidebar-context.ts +16 -0
  167. package/src/lib/components/ui/sidebar.tsx +2 -13
  168. package/src/lib/components/ui/skeleton.stories.tsx +26 -0
  169. package/src/lib/components/ui/slider.stories.tsx +37 -0
  170. package/src/lib/components/ui/switch.stories.tsx +31 -0
  171. package/src/lib/components/ui/table.stories.tsx +52 -0
  172. package/src/lib/components/ui/tabs.stories.tsx +29 -0
  173. package/src/lib/components/ui/textarea.stories.tsx +32 -0
  174. package/src/lib/components/ui/toggle-group.stories.tsx +31 -0
  175. package/src/lib/components/ui/toggle.stories.tsx +39 -0
  176. package/src/lib/components/ui/tooltip.stories.tsx +30 -0
  177. package/src/lib/components/ui/tooltip.tsx +2 -2
  178. package/src/lib/framework/alert/alert-extensions.tsx +0 -11
  179. package/src/lib/framework/alert/alert-item.tsx +14 -19
  180. package/src/lib/framework/alert/alerts-indicator.tsx +14 -15
  181. package/src/lib/framework/alert/search-index-buffer-alert/search-index-buffer-alert.ts +41 -0
  182. package/src/lib/framework/component-registry/component-registry.tsx +3 -14
  183. package/src/lib/framework/dashboard-widget/base-widget.tsx +18 -9
  184. package/src/lib/framework/dashboard-widget/widget-filters-context.tsx +12 -11
  185. package/src/lib/framework/defaults.ts +9 -13
  186. package/src/lib/framework/extension-api/input-component-extensions.tsx +8 -3
  187. package/src/lib/framework/extension-api/logic/alerts.ts +3 -2
  188. package/src/lib/framework/extension-api/types/alerts.ts +12 -6
  189. package/src/lib/framework/extension-api/types/data-table.ts +5 -2
  190. package/src/lib/framework/extension-api/types/login.ts +0 -21
  191. package/src/lib/framework/layout-engine/custom-form-page.stories.tsx +344 -0
  192. package/src/lib/framework/layout-engine/page-layout.tsx +11 -9
  193. package/src/lib/framework/layout-engine/page.stories.tsx +275 -0
  194. package/src/lib/framework/nav-menu/nav-menu-extensions.ts +32 -19
  195. package/src/lib/framework/page/detail-page.stories.tsx +151 -0
  196. package/src/lib/framework/page/list-page.stories.tsx +217 -0
  197. package/src/lib/framework/page/list-page.tsx +8 -1
  198. package/src/lib/graphql/api.ts +18 -1
  199. package/src/lib/graphql/graphql-env.d.ts +1 -1
  200. package/src/lib/hooks/use-alerts.ts +84 -0
  201. package/src/lib/hooks/use-floating-bulk-actions.ts +2 -3
  202. package/src/lib/index.ts +14 -1
  203. package/src/lib/providers/alerts-provider.tsx +60 -0
  204. package/src/lib/providers/theme-provider.tsx +6 -3
@@ -13,9 +13,8 @@ import { api } from '@/vdb/graphql/api.js';
13
13
  import { Trans, useLingui } from '@lingui/react/macro';
14
14
  import { useQuery, useQueryClient } from '@tanstack/react-query';
15
15
  import { createFileRoute, Link, useNavigate } from '@tanstack/react-router';
16
- import { VariablesOf } from 'gql.tada';
17
16
  import { User } from 'lucide-react';
18
- import { useEffect, useState } from 'react';
17
+ import { useState } from 'react';
19
18
  import { toast } from 'sonner';
20
19
  import { CustomerAddressSelector } from './components/customer-address-selector.js';
21
20
  import { EditOrderTable } from './components/edit-order-table.js';
@@ -23,16 +22,13 @@ import { OrderAddress } from './components/order-address.js';
23
22
  import { OrderModificationPreviewDialog } from './components/order-modification-preview-dialog.js';
24
23
  import { OrderModificationSummary } from './components/order-modification-summary.js';
25
24
  import { useTransitionOrderToState } from './components/use-transition-order-to-state.js';
26
- import {
27
- draftOrderEligibleShippingMethodsDocument,
28
- modifyOrderDocument,
29
- orderDetailDocument,
30
- } from './orders.graphql.js';
25
+ import { draftOrderEligibleShippingMethodsDocument, orderDetailDocument } from './orders.graphql.js';
31
26
  import { loadModifyingOrder } from './utils/order-detail-loaders.js';
32
- import { AddressFragment, Order } from './utils/order-types.js';
27
+ import { AddressFragment } from './utils/order-types.js';
28
+ import { computePendingOrder } from './utils/order-utils.js';
29
+ import { useModifyOrder } from './utils/use-modify-order.js';
33
30
 
34
31
  const pageId = 'order-modify';
35
- type ModifyOrderInput = VariablesOf<typeof modifyOrderDocument>['input'];
36
32
 
37
33
  export const Route = createFileRoute('/_authenticated/_orders/orders_/$id_/modify')({
38
34
  component: ModifyOrderPage,
@@ -40,34 +36,6 @@ export const Route = createFileRoute('/_authenticated/_orders/orders_/$id_/modif
40
36
  errorComponent: ({ error }) => <ErrorPage message={error.message} />,
41
37
  });
42
38
 
43
- // --- AddedLine type for added items ---
44
- interface AddedLine {
45
- id: string;
46
- featuredAsset?: any;
47
- productVariant: {
48
- id: string;
49
- name: string;
50
- sku: string;
51
- };
52
- unitPrice: number;
53
- unitPriceWithTax: number;
54
- quantity: number;
55
- linePrice: number;
56
- linePriceWithTax: number;
57
- }
58
-
59
- // --- ProductVariantInfo type ---
60
- type ProductVariantInfo = {
61
- productVariantId: string;
62
- productVariantName: string;
63
- sku: string;
64
- productAsset: {
65
- preview: string;
66
- };
67
- price?: number;
68
- priceWithTax?: number;
69
- };
70
-
71
39
  function ModifyOrderPage() {
72
40
  const params = Route.useParams();
73
41
  const navigate = useNavigate({ from: '/orders/$id/modify' });
@@ -103,254 +71,48 @@ function ModifyOrderPage() {
103
71
  const { transitionToPreModifyingState, ManuallySelectNextState, selectNextState, transitionToState } =
104
72
  useTransitionOrderToState(entity?.id ?? '');
105
73
 
106
- // --- Modification intent state ---
107
-
108
- const [modifyOrderInput, setModifyOrderInput] = useState<ModifyOrderInput>({
109
- orderId: '',
110
- addItems: [],
111
- adjustOrderLines: [],
112
- surcharges: [],
113
- note: '',
114
- couponCodes: [],
115
- options: {
116
- recalculateShipping: true,
117
- },
118
- dryRun: true,
119
- } satisfies ModifyOrderInput);
120
-
121
- useEffect(() => {
122
- setModifyOrderInput(prev => ({
123
- ...prev,
124
- orderId: entity?.id ?? '',
125
- couponCodes: entity?.couponCodes ?? [],
126
- }));
127
- }, [entity?.id]);
128
-
129
- // --- Added variants info state ---
130
- const [addedVariants, setAddedVariants] = useState<Map<string, ProductVariantInfo>>(new Map());
131
-
132
- // --- Handlers update modifyOrderInput ---
133
- function handleAddItem(variant: ProductVariantInfo) {
134
- setModifyOrderInput(prev => ({
135
- ...prev,
136
- addItems: [...(prev.addItems ?? []), { productVariantId: variant.productVariantId, quantity: 1 }],
137
- }));
138
- setAddedVariants(prev => {
139
- const newMap = new Map(prev);
140
- newMap.set(variant.productVariantId, variant);
141
- return newMap;
142
- });
143
- }
144
-
145
- function handleAdjustLine({
146
- lineId,
147
- quantity,
148
- customFields,
149
- }: {
150
- lineId: string;
151
- quantity: number;
152
- customFields: Record<string, any>;
153
- }) {
154
- // Check if this is an added line
155
- if (lineId.startsWith('added-')) {
156
- const productVariantId = lineId.replace('added-', '');
157
- setModifyOrderInput(prev => ({
158
- ...prev,
159
- addItems: (prev.addItems ?? []).map(item =>
160
- item.productVariantId === productVariantId ? { ...item, quantity } : item,
161
- ),
162
- }));
163
- } else {
164
- let normalizedCustomFields: any = customFields;
165
- delete normalizedCustomFields.__entityId__;
166
- if (Object.keys(normalizedCustomFields).length === 0) {
167
- normalizedCustomFields = undefined;
168
- }
169
- setModifyOrderInput(prev => {
170
- const existing = (prev.adjustOrderLines ?? []).find(l => l.orderLineId === lineId);
171
- const adjustOrderLines = existing
172
- ? (prev.adjustOrderLines ?? []).map(l =>
173
- l.orderLineId === lineId
174
- ? { ...l, quantity, customFields: normalizedCustomFields }
175
- : l,
176
- )
177
- : [
178
- ...(prev.adjustOrderLines ?? []),
179
- { orderLineId: lineId, quantity, customFields: normalizedCustomFields },
180
- ];
181
- return { ...prev, adjustOrderLines };
182
- });
183
- }
184
- }
185
-
186
- function handleRemoveLine({ lineId }: { lineId: string }) {
187
- if (lineId.startsWith('added-')) {
188
- const productVariantId = lineId.replace('added-', '');
189
- setModifyOrderInput(prev => ({
190
- ...prev,
191
- addItems: (prev.addItems ?? []).filter(item => item.productVariantId !== productVariantId),
192
- }));
193
- setAddedVariants(prev => {
194
- const newMap = new Map(prev);
195
- newMap.delete(productVariantId);
196
- return newMap;
197
- });
198
- } else {
199
- setModifyOrderInput(prev => {
200
- const existingAdjustment = (prev.adjustOrderLines ?? []).find(l => l.orderLineId === lineId);
201
- const adjustOrderLines = existingAdjustment
202
- ? (prev.adjustOrderLines ?? []).map(l =>
203
- l.orderLineId === lineId ? { ...l, quantity: 0 } : l,
204
- )
205
- : [...(prev.adjustOrderLines ?? []), { orderLineId: lineId, quantity: 0 }];
206
- return {
207
- ...prev,
208
- adjustOrderLines,
209
- };
210
- });
211
- }
212
- }
213
-
214
- function handleSetShippingMethod({ shippingMethodId }: { shippingMethodId: string }) {
215
- setModifyOrderInput(prev => ({
216
- ...prev,
217
- shippingMethodIds: [shippingMethodId],
218
- }));
219
- }
220
-
221
- function handleApplyCouponCode({ couponCode }: { couponCode: string }) {
222
- setModifyOrderInput(prev => ({
223
- ...prev,
224
- couponCodes: prev.couponCodes?.includes(couponCode)
225
- ? prev.couponCodes
226
- : [...(prev.couponCodes ?? []), couponCode],
227
- }));
228
- }
229
-
230
- function handleRemoveCouponCode({ couponCode }: { couponCode: string }) {
231
- setModifyOrderInput(prev => ({
232
- ...prev,
233
- couponCodes: (prev.couponCodes ?? []).filter(code => code !== couponCode),
234
- }));
235
- }
74
+ // Use the custom hook for order modification logic
75
+ const {
76
+ modifyOrderInput,
77
+ addedVariants,
78
+ addItem,
79
+ adjustLine,
80
+ removeLine,
81
+ setShippingMethod,
82
+ applyCouponCode,
83
+ removeCouponCode,
84
+ updateShippingAddress: updateShippingAddressInInput,
85
+ updateBillingAddress: updateBillingAddressInInput,
86
+ hasModifications,
87
+ } = useModifyOrder(entity);
236
88
 
237
89
  // --- Address editing state ---
238
90
  const [editingShippingAddress, setEditingShippingAddress] = useState(false);
239
91
  const [editingBillingAddress, setEditingBillingAddress] = useState(false);
240
92
 
241
- function orderAddressToModifyOrderInput(
242
- address: AddressFragment,
243
- ): ModifyOrderInput['updateShippingAddress'] {
244
- return {
245
- streetLine1: address.streetLine1,
246
- streetLine2: address.streetLine2,
247
- city: address.city,
248
- countryCode: address.country.code,
249
- fullName: address.fullName,
250
- postalCode: address.postalCode,
251
- province: address.province,
252
- company: address.company,
253
- phoneNumber: address.phoneNumber,
254
- };
255
- }
256
-
257
93
  // --- Address selection handlers ---
258
94
  function handleSelectShippingAddress(address: AddressFragment) {
259
- setModifyOrderInput(prev => ({
260
- ...prev,
261
- updateShippingAddress: orderAddressToModifyOrderInput(address),
262
- }));
95
+ updateShippingAddressInInput(address);
263
96
  setEditingShippingAddress(false);
264
97
  }
265
98
 
266
99
  function handleSelectBillingAddress(address: AddressFragment) {
267
- setModifyOrderInput(prev => ({
268
- ...prev,
269
- updateBillingAddress: orderAddressToModifyOrderInput(address),
270
- }));
100
+ updateBillingAddressInInput(address);
271
101
  setEditingBillingAddress(false);
272
102
  }
273
103
 
274
- // --- Utility: compute pending order for display ---
275
- function computePendingOrder(input: ModifyOrderInput): Order | null {
276
- if (!entity) {
277
- return null;
278
- }
279
- // Adjust lines
280
- const lines = entity.lines.map(line => {
281
- const adjust = input.adjustOrderLines?.find(l => l.orderLineId === line.id);
282
- return adjust
283
- ? { ...line, quantity: adjust.quantity, customFields: (adjust as any).customFields }
284
- : line;
285
- });
286
- // Add new items (as AddedLine)
287
- const addedLines = input.addItems
288
- ?.map(item => {
289
- const variantInfo = addedVariants.get(item.productVariantId);
290
- return variantInfo
291
- ? ({
292
- id: `added-${item.productVariantId}`,
293
- featuredAsset: variantInfo.productAsset ?? null,
294
- productVariant: {
295
- id: variantInfo.productVariantId,
296
- name: variantInfo.productVariantName,
297
- sku: variantInfo.sku,
298
- },
299
- unitPrice: variantInfo.price ?? 0,
300
- unitPriceWithTax: variantInfo.priceWithTax ?? 0,
301
- quantity: item.quantity,
302
- linePrice: (variantInfo.price ?? 0) * item.quantity,
303
- linePriceWithTax: (variantInfo.priceWithTax ?? 0) * item.quantity,
304
- } as unknown as Order['lines'][number])
305
- : null;
306
- })
307
- .filter(x => x != null);
308
- return {
309
- ...entity,
310
- lines: [...lines, ...(addedLines ?? [])],
311
- couponCodes: input.couponCodes ?? [],
312
- shippingLines: input.shippingMethodIds
313
- ? input.shippingMethodIds
314
- .map(shippingMethodId => {
315
- const shippingMethod =
316
- eligibleShippingMethods?.eligibleShippingMethodsForDraftOrder.find(
317
- method => method.id === shippingMethodId,
318
- );
319
- if (!shippingMethod) {
320
- return;
321
- }
322
- return {
323
- shippingMethod: {
324
- ...shippingMethod,
325
- fulfillmentHandlerCode: 'manual',
326
- },
327
- discountedPriceWithTax: shippingMethod?.priceWithTax ?? 0,
328
- id: shippingMethodId,
329
- };
330
- })
331
- .filter(x => x !== undefined)
332
- : entity.shippingLines,
333
- };
334
- }
335
-
336
104
  const [previewOpen, setPreviewOpen] = useState(false);
337
105
 
338
106
  if (!entity) {
339
107
  return null;
340
108
  }
341
109
 
342
- const pendingOrder = computePendingOrder(modifyOrderInput);
343
- const hasModifications =
344
- (modifyOrderInput.addItems?.length ?? 0) > 0 ||
345
- (modifyOrderInput.adjustOrderLines?.length ?? 0) > 0 ||
346
- (modifyOrderInput.couponCodes?.length ?? 0) > 0 ||
347
- (modifyOrderInput.shippingMethodIds?.length ?? 0) > 0 ||
348
- modifyOrderInput.updateShippingAddress ||
349
- modifyOrderInput.updateBillingAddress;
350
-
351
- if (!pendingOrder) {
352
- return null;
353
- }
110
+ const pendingOrder = computePendingOrder(
111
+ entity,
112
+ modifyOrderInput,
113
+ addedVariants,
114
+ eligibleShippingMethods?.eligibleShippingMethodsForDraftOrder,
115
+ );
354
116
 
355
117
  // On successful state transition, invalidate the order detail query and navigate to the order detail page
356
118
  const onSuccess = async () => {
@@ -369,17 +131,22 @@ function ModifyOrderPage() {
369
131
  };
370
132
 
371
133
  const handleModificationSubmit = async (priceDifference?: number) => {
372
- const transitionError =
373
- priceDifference && priceDifference > 0
374
- ? await transitionToState('ArrangingAdditionalPayment')
375
- : await transitionToPreModifyingState();
376
- if (!transitionError) {
377
- const queryKey = getDetailQueryOptions(orderDetailDocument, { id: entity.id }).queryKey;
378
- await queryClient.invalidateQueries({ queryKey });
134
+ if (priceDifference === undefined) {
135
+ // the preview was cancelled
379
136
  setPreviewOpen(false);
380
- await navigate({ to: `/orders/$id`, params: { id: entity?.id } });
381
137
  } else {
382
- selectNextState({ onSuccess });
138
+ const transitionError =
139
+ priceDifference > 0
140
+ ? await transitionToState('ArrangingAdditionalPayment')
141
+ : await transitionToPreModifyingState();
142
+ if (!transitionError) {
143
+ const queryKey = getDetailQueryOptions(orderDetailDocument, { id: entity.id }).queryKey;
144
+ await queryClient.invalidateQueries({ queryKey });
145
+ setPreviewOpen(false);
146
+ await navigate({ to: `/orders/$id`, params: { id: entity?.id } });
147
+ } else {
148
+ selectNextState({ onSuccess });
149
+ }
383
150
  }
384
151
  };
385
152
 
@@ -405,12 +172,12 @@ function ModifyOrderPage() {
405
172
  eligibleShippingMethods={
406
173
  eligibleShippingMethods?.eligibleShippingMethodsForDraftOrder ?? []
407
174
  }
408
- onAddItem={handleAddItem}
409
- onAdjustLine={handleAdjustLine}
410
- onRemoveLine={handleRemoveLine}
411
- onSetShippingMethod={handleSetShippingMethod}
412
- onApplyCouponCode={handleApplyCouponCode}
413
- onRemoveCouponCode={handleRemoveCouponCode}
175
+ onAddItem={addItem}
176
+ onAdjustLine={adjustLine}
177
+ onRemoveLine={removeLine}
178
+ onSetShippingMethod={setShippingMethod}
179
+ onApplyCouponCode={applyCouponCode}
180
+ onRemoveCouponCode={removeCouponCode}
414
181
  displayTotals={false}
415
182
  />
416
183
  </PageBlock>
@@ -449,7 +216,7 @@ function ModifyOrderPage() {
449
216
  </PageBlock>
450
217
  <PageBlock column="side" blockId="customer" title={<Trans>Customer</Trans>}>
451
218
  {entity.customer ? (
452
- <Button variant="ghost" asChild>
219
+ <Button variant="outline" asChild>
453
220
  <Link to={`/customers/${entity?.customer?.id}`}>
454
221
  <User className="w-4 h-4" />
455
222
  {entity?.customer?.firstName} {entity?.customer?.lastName}
@@ -86,6 +86,7 @@ function DraftOrderPage() {
86
86
  const { mutate: setDraftOrderCustomFields } = useMutation({
87
87
  mutationFn: api.mutate(setDraftOrderCustomFieldsDocument),
88
88
  onSuccess: (result: ResultOf<typeof setDraftOrderCustomFieldsDocument>) => {
89
+ toast.success(t`Order custom fields updated`);
89
90
  refreshEntity();
90
91
  },
91
92
  });
@@ -110,6 +111,11 @@ function DraftOrderPage() {
110
111
  break;
111
112
  }
112
113
  },
114
+ onError: error => {
115
+ if ((error as any).extensions?.code === 'ENTITY_NOT_FOUND') {
116
+ toast.error(t`The variant could not be added. Ensure the parent product is enabled.`);
117
+ }
118
+ },
113
119
  });
114
120
 
115
121
  const { mutate: adjustDraftOrderLine } = useMutation({
@@ -151,6 +157,15 @@ function DraftOrderPage() {
151
157
  switch (order.__typename) {
152
158
  case 'Order':
153
159
  toast.success(t`Customer set for order`);
160
+
161
+ // When we change the customer, we should clear
162
+ // any selected shipping/billing address
163
+ if (entity?.shippingAddress) {
164
+ unsetShippingAddressForDraftOrder({ orderId: entity.id });
165
+ }
166
+ if (entity?.billingAddress) {
167
+ unsetBillingAddressForDraftOrder({ orderId: entity.id });
168
+ }
154
169
  refreshEntity();
155
170
  break;
156
171
  default:
@@ -377,7 +392,7 @@ function DraftOrderPage() {
377
392
  onClick={e => {
378
393
  e.preventDefault();
379
394
  e.stopPropagation();
380
- orderCustomFieldsForm.handleSubmit(onSaveCustomFields)();
395
+ onSaveCustomFields(orderCustomFieldsForm.getValues());
381
396
  }}
382
397
  >
383
398
  <Trans>Set custom fields</Trans>
@@ -387,7 +402,7 @@ function DraftOrderPage() {
387
402
  </PageBlock>
388
403
  <PageBlock column="side" blockId="customer" title={<Trans>Customer</Trans>}>
389
404
  {entity?.customer?.id ? (
390
- <Button variant="ghost" asChild className="mb-4">
405
+ <Button variant="outline" asChild className="mb-4">
391
406
  <Link to={`/customers/${entity?.customer?.id}`}>
392
407
  <User className="w-4 h-4" />
393
408
  {entity?.customer?.firstName} {entity?.customer?.lastName}
@@ -408,25 +423,27 @@ function DraftOrderPage() {
408
423
  onClick={() => unsetShippingAddressForDraftOrder({ orderId: entity.id })}
409
424
  />
410
425
  ) : (
411
- <CustomerAddressSelector
412
- customerId={entity.customer?.id}
413
- onSelect={address => {
414
- setShippingAddressForDraftOrder({
415
- orderId: entity.id,
416
- input: {
417
- fullName: address.fullName,
418
- company: address.company,
419
- streetLine1: address.streetLine1,
420
- streetLine2: address.streetLine2,
421
- city: address.city,
422
- province: address.province,
423
- postalCode: address.postalCode,
424
- countryCode: address.country.code,
425
- phoneNumber: address.phoneNumber,
426
- },
427
- });
428
- }}
429
- />
426
+ <div className="mt-4">
427
+ <CustomerAddressSelector
428
+ customerId={entity.customer?.id}
429
+ onSelect={address => {
430
+ setShippingAddressForDraftOrder({
431
+ orderId: entity.id,
432
+ input: {
433
+ fullName: address.fullName,
434
+ company: address.company,
435
+ streetLine1: address.streetLine1,
436
+ streetLine2: address.streetLine2,
437
+ city: address.city,
438
+ province: address.province,
439
+ postalCode: address.postalCode,
440
+ countryCode: address.country.code,
441
+ phoneNumber: address.phoneNumber,
442
+ },
443
+ });
444
+ }}
445
+ />
446
+ </div>
430
447
  )}
431
448
  </div>
432
449
  </PageBlock>
@@ -438,25 +455,27 @@ function DraftOrderPage() {
438
455
  onClick={() => unsetBillingAddressForDraftOrder({ orderId: entity.id })}
439
456
  />
440
457
  ) : (
441
- <CustomerAddressSelector
442
- customerId={entity.customer?.id}
443
- onSelect={address => {
444
- setBillingAddressForDraftOrder({
445
- orderId: entity.id,
446
- input: {
447
- fullName: address.fullName,
448
- company: address.company,
449
- streetLine1: address.streetLine1,
450
- streetLine2: address.streetLine2,
451
- city: address.city,
452
- province: address.province,
453
- postalCode: address.postalCode,
454
- countryCode: address.country.code,
455
- phoneNumber: address.phoneNumber,
456
- },
457
- });
458
- }}
459
- />
458
+ <div className="mt-4">
459
+ <CustomerAddressSelector
460
+ customerId={entity.customer?.id}
461
+ onSelect={address => {
462
+ setBillingAddressForDraftOrder({
463
+ orderId: entity.id,
464
+ input: {
465
+ fullName: address.fullName,
466
+ company: address.company,
467
+ streetLine1: address.streetLine1,
468
+ streetLine2: address.streetLine2,
469
+ city: address.city,
470
+ province: address.province,
471
+ postalCode: address.postalCode,
472
+ countryCode: address.country.code,
473
+ phoneNumber: address.phoneNumber,
474
+ },
475
+ });
476
+ }}
477
+ />
478
+ </div>
460
479
  )}
461
480
  </div>
462
481
  </PageBlock>
@@ -1,6 +1,12 @@
1
1
  import { DEFAULT_CHANNEL_CODE } from '@/vdb/constants.js';
2
+ import { VariablesOf } from 'gql.tada';
3
+
4
+ import { modifyOrderDocument } from '../orders.graphql.js';
2
5
 
3
6
  import { Fulfillment, Order, Payment } from './order-types.js';
7
+ import { ProductVariantInfo } from './use-modify-order.js';
8
+
9
+ type ModifyOrderInput = VariablesOf<typeof modifyOrderDocument>['input'];
4
10
 
5
11
  /**
6
12
  * Calculates the outstanding payment amount for an order
@@ -84,3 +90,70 @@ export function getSeller<T>(order: { channels: Array<{ code: string; seller: T
84
90
  const sellerChannel = order.channels.find(channel => channel.code !== DEFAULT_CHANNEL_CODE);
85
91
  return sellerChannel?.seller;
86
92
  }
93
+
94
+ /**
95
+ * Computes a pending order based on the current order and modification input
96
+ */
97
+ export function computePendingOrder(
98
+ order: Order,
99
+ input: ModifyOrderInput,
100
+ addedVariants: Map<string, ProductVariantInfo>,
101
+ eligibleShippingMethods?: Array<{ id: string; name: string; priceWithTax: number }>,
102
+ ): Order {
103
+ // Adjust lines
104
+ const lines = order.lines.map(line => {
105
+ const adjust = input.adjustOrderLines?.find(l => l.orderLineId === line.id);
106
+ return adjust
107
+ ? { ...line, quantity: adjust.quantity, customFields: (adjust as any).customFields }
108
+ : line;
109
+ });
110
+
111
+ // Add new items (as AddedLine)
112
+ const addedLines = input.addItems
113
+ ?.map(item => {
114
+ const variantInfo = addedVariants.get(item.productVariantId);
115
+ return variantInfo
116
+ ? ({
117
+ id: `added-${item.productVariantId}`,
118
+ featuredAsset: variantInfo.productAsset ?? null,
119
+ productVariant: {
120
+ id: variantInfo.productVariantId,
121
+ name: variantInfo.productVariantName,
122
+ sku: variantInfo.sku,
123
+ },
124
+ unitPrice: variantInfo.price ?? 0,
125
+ unitPriceWithTax: variantInfo.priceWithTax ?? 0,
126
+ quantity: item.quantity,
127
+ linePrice: (variantInfo.price ?? 0) * item.quantity,
128
+ linePriceWithTax: (variantInfo.priceWithTax ?? 0) * item.quantity,
129
+ } as unknown as Order['lines'][number])
130
+ : null;
131
+ })
132
+ .filter(x => x != null);
133
+
134
+ return {
135
+ ...order,
136
+ lines: [...lines, ...(addedLines ?? [])],
137
+ couponCodes: input.couponCodes ?? [],
138
+ shippingLines: input.shippingMethodIds
139
+ ? input.shippingMethodIds
140
+ .map(shippingMethodId => {
141
+ const shippingMethod = eligibleShippingMethods?.find(
142
+ method => method.id === shippingMethodId,
143
+ );
144
+ if (!shippingMethod) {
145
+ return;
146
+ }
147
+ return {
148
+ shippingMethod: {
149
+ ...shippingMethod,
150
+ fulfillmentHandlerCode: 'manual',
151
+ },
152
+ discountedPriceWithTax: shippingMethod?.priceWithTax ?? 0,
153
+ id: shippingMethodId,
154
+ } as any;
155
+ })
156
+ .filter(x => x !== undefined)
157
+ : order.shippingLines,
158
+ };
159
+ }