@vendure/dashboard 3.3.6-master-202507090236 → 3.3.6-master-202507110238

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 (37) hide show
  1. package/dist/plugin/tests/barrel-exports.spec.js +1 -1
  2. package/package.json +4 -4
  3. package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +30 -37
  4. package/src/app/routes/_authenticated/_orders/components/fulfillment-details.tsx +33 -53
  5. package/src/app/routes/_authenticated/_orders/components/order-address.tsx +14 -7
  6. package/src/app/routes/_authenticated/_orders/components/order-line-custom-fields-form.tsx +23 -12
  7. package/src/app/routes/_authenticated/_orders/components/order-modification-preview-dialog.tsx +364 -0
  8. package/src/app/routes/_authenticated/_orders/components/order-modification-summary.tsx +222 -0
  9. package/src/app/routes/_authenticated/_orders/components/order-table.tsx +146 -85
  10. package/src/app/routes/_authenticated/_orders/components/payment-details.tsx +268 -31
  11. package/src/app/routes/_authenticated/_orders/components/settle-refund-dialog.tsx +80 -0
  12. package/src/app/routes/_authenticated/_orders/components/state-transition-control.tsx +102 -0
  13. package/src/app/routes/_authenticated/_orders/components/use-transition-order-to-state.tsx +144 -0
  14. package/src/app/routes/_authenticated/_orders/orders.graphql.ts +118 -2
  15. package/src/app/routes/_authenticated/_orders/orders_.$id.tsx +144 -52
  16. package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +550 -0
  17. package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +0 -17
  18. package/src/app/routes/_authenticated/_orders/utils/order-types.ts +5 -2
  19. package/src/app/routes/_authenticated/_orders/utils/order-utils.ts +4 -3
  20. package/src/app/routes/_authenticated/_products/products_.$id.tsx +0 -1
  21. package/src/lib/components/data-display/date-time.tsx +7 -1
  22. package/src/lib/components/data-input/relation-input.tsx +11 -0
  23. package/src/lib/components/data-input/relation-selector.tsx +9 -2
  24. package/src/lib/components/data-table/data-table-utils.ts +34 -0
  25. package/src/lib/components/data-table/data-table-view-options.tsx +2 -2
  26. package/src/lib/components/data-table/data-table.tsx +5 -2
  27. package/src/lib/components/data-table/use-generated-columns.tsx +307 -0
  28. package/src/lib/components/shared/paginated-list-data-table.tsx +15 -286
  29. package/src/lib/components/shared/product-variant-selector.tsx +28 -4
  30. package/src/lib/framework/component-registry/dynamic-component.tsx +3 -3
  31. package/src/lib/framework/document-introspection/get-document-structure.spec.ts +321 -2
  32. package/src/lib/framework/document-introspection/get-document-structure.ts +149 -31
  33. package/src/lib/framework/extension-api/types/layout.ts +21 -6
  34. package/src/lib/framework/layout-engine/layout-extensions.ts +1 -4
  35. package/src/lib/framework/layout-engine/page-layout.tsx +61 -10
  36. package/src/lib/framework/page/use-detail-page.ts +10 -7
  37. package/vite/tests/barrel-exports.spec.ts +1 -1
@@ -1,4 +1,4 @@
1
- import { join } from 'path';
1
+ import { join } from 'node:path';
2
2
  import { describe, expect, it } from 'vitest';
3
3
  import { loadVendureConfig } from '../utils/config-loader.js';
4
4
  describe('detecting plugins in barrel exports', () => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vendure/dashboard",
3
3
  "private": false,
4
- "version": "3.3.6-master-202507090236",
4
+ "version": "3.3.6-master-202507110238",
5
5
  "type": "module",
6
6
  "repository": {
7
7
  "type": "git",
@@ -86,8 +86,8 @@
86
86
  "@types/react-dom": "^19.0.4",
87
87
  "@types/react-grid-layout": "^1.3.5",
88
88
  "@uidotdev/usehooks": "^2.4.1",
89
- "@vendure/common": "^3.3.6-master-202507090236",
90
- "@vendure/core": "^3.3.6-master-202507090236",
89
+ "@vendure/common": "^3.3.6-master-202507110238",
90
+ "@vendure/core": "^3.3.6-master-202507110238",
91
91
  "@vitejs/plugin-react": "^4.3.4",
92
92
  "awesome-graphql-client": "^2.1.0",
93
93
  "class-variance-authority": "^0.7.1",
@@ -130,5 +130,5 @@
130
130
  "lightningcss-linux-arm64-musl": "^1.29.3",
131
131
  "lightningcss-linux-x64-musl": "^1.29.1"
132
132
  },
133
- "gitHead": "df60d656c924e7574df61467ef1bbdb9a0221dcc"
133
+ "gitHead": "48140d95d510219816f76bd5d53af779e67011f3"
134
134
  }
@@ -1,3 +1,4 @@
1
+ import { SingleRelationInput } from '@/vdb/components/data-input/relation-input.js';
1
2
  import { ProductVariantSelector } from '@/vdb/components/shared/product-variant-selector.js';
2
3
  import { VendureImage } from '@/vdb/components/shared/vendure-image.js';
3
4
  import { Button } from '@/vdb/components/ui/button.js';
@@ -14,8 +15,8 @@ import {
14
15
  } from '@tanstack/react-table';
15
16
  import { Trash2 } from 'lucide-react';
16
17
  import { useState } from 'react';
17
- import { UseFormReturn } from 'react-hook-form';
18
18
  import {
19
+ couponCodeSelectorPromotionListDocument,
19
20
  draftOrderEligibleShippingMethodsDocument,
20
21
  orderDetailDocument,
21
22
  orderLineFragment,
@@ -35,13 +36,20 @@ type ShippingMethodQuote = ResultOf<
35
36
  export interface OrderTableProps {
36
37
  order: OrderFragment;
37
38
  eligibleShippingMethods: ShippingMethodQuote[];
38
- onAddItem: (event: { productVariantId: string }) => void;
39
+ onAddItem: (variant: {
40
+ productVariantId: string;
41
+ productVariantName: string;
42
+ sku: string;
43
+ productAsset: any;
44
+ price?: any;
45
+ priceWithTax?: any;
46
+ }) => void;
39
47
  onAdjustLine: (event: { lineId: string; quantity: number; customFields: Record<string, any> }) => void;
40
48
  onRemoveLine: (event: { lineId: string }) => void;
41
49
  onSetShippingMethod: (event: { shippingMethodId: string }) => void;
42
50
  onApplyCouponCode: (event: { couponCode: string }) => void;
43
51
  onRemoveCouponCode: (event: { couponCode: string }) => void;
44
- orderLineForm: UseFormReturn<any>;
52
+ displayTotals?: boolean;
45
53
  }
46
54
 
47
55
  export function EditOrderTable({
@@ -53,14 +61,11 @@ export function EditOrderTable({
53
61
  onSetShippingMethod,
54
62
  onApplyCouponCode,
55
63
  onRemoveCouponCode,
56
- orderLineForm,
64
+ displayTotals = true,
57
65
  }: Readonly<OrderTableProps>) {
58
66
  const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
59
- const [couponCode, setCouponCode] = useState('');
60
-
61
67
  const currencyCode = order.currencyCode;
62
-
63
- const columns: ColumnDef<OrderLineFragment>[] = [
68
+ const columns: ColumnDef<OrderLineFragment & { customFields?: Record<string, any> }>[] = [
64
69
  {
65
70
  header: 'Image',
66
71
  accessorKey: 'featuredAsset',
@@ -99,7 +104,7 @@ export function EditOrderTable({
99
104
  onAdjustLine({
100
105
  lineId: row.original.id,
101
106
  quantity: e.target.valueAsNumber,
102
- customFields: row.original.customFields,
107
+ customFields: row.original.customFields ?? {},
103
108
  })
104
109
  }
105
110
  />
@@ -120,7 +125,7 @@ export function EditOrderTable({
120
125
  customFields: customFields,
121
126
  });
122
127
  }}
123
- form={orderLineForm}
128
+ value={row.original.customFields}
124
129
  />
125
130
  )}
126
131
  </div>
@@ -189,11 +194,7 @@ export function EditOrderTable({
189
194
  <TableCell colSpan={columns.length} className="h-12">
190
195
  <div className="my-4 flex justify-center">
191
196
  <div className="max-w-lg">
192
- <ProductVariantSelector
193
- onProductVariantIdChange={variantId => {
194
- onAddItem({ productVariantId: variantId });
195
- }}
196
- />
197
+ <ProductVariantSelector onProductVariantSelect={onAddItem} />
197
198
  </div>
198
199
  </div>
199
200
  </TableCell>
@@ -210,27 +211,7 @@ export function EditOrderTable({
210
211
  </TableRow>
211
212
  <TableRow>
212
213
  <TableCell colSpan={columns.length} className="h-12">
213
- <div className="flex flex-col gap-4">
214
- <div className="flex gap-2">
215
- <Input
216
- type="text"
217
- placeholder="Coupon code"
218
- value={couponCode}
219
- onChange={e => setCouponCode(e.target.value)}
220
- onKeyDown={e => {
221
- if (e.key === 'Enter') {
222
- onApplyCouponCode({ couponCode });
223
- }
224
- }}
225
- />
226
- <Button
227
- type="button"
228
- onClick={() => onApplyCouponCode({ couponCode })}
229
- disabled={!couponCode}
230
- >
231
- <Trans>Apply</Trans>
232
- </Button>
233
- </div>
214
+ <div className="flex gap-4">
234
215
  {order.couponCodes?.length > 0 && (
235
216
  <div className="flex flex-wrap gap-2">
236
217
  {order.couponCodes.map(code => (
@@ -254,10 +235,22 @@ export function EditOrderTable({
254
235
  ))}
255
236
  </div>
256
237
  )}
238
+ <SingleRelationInput
239
+ config={{
240
+ listQuery: couponCodeSelectorPromotionListDocument,
241
+ idKey: 'couponCode',
242
+ labelKey: 'couponCode',
243
+ placeholder: 'Search coupon codes...',
244
+ label: (item: any) => `${item.couponCode} (${item.name})`,
245
+ }}
246
+ value={''}
247
+ selectorLabel={<Trans>Add coupon code</Trans>}
248
+ onChange={code => onApplyCouponCode({ couponCode: code })}
249
+ />
257
250
  </div>
258
251
  </TableCell>
259
252
  </TableRow>
260
- <OrderTableTotals order={order} columnCount={columns.length} />
253
+ {displayTotals && <OrderTableTotals order={order} columnCount={columns.length} />}
261
254
  </TableBody>
262
255
  </Table>
263
256
  </div>
@@ -1,12 +1,5 @@
1
1
  import { LabeledData } from '@/vdb/components/labeled-data.js';
2
- import { Button } from '@/vdb/components/ui/button.js';
3
2
  import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/vdb/components/ui/collapsible.js';
4
- import {
5
- DropdownMenu,
6
- DropdownMenuContent,
7
- DropdownMenuItem,
8
- DropdownMenuTrigger,
9
- } from '@/vdb/components/ui/dropdown-menu.js';
10
3
  import { api } from '@/vdb/graphql/api.js';
11
4
  import { ResultOf } from '@/vdb/graphql/graphql.js';
12
5
  import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
@@ -19,6 +12,7 @@ import {
19
12
  orderDetailFragment,
20
13
  transitionFulfillmentToStateDocument,
21
14
  } from '../orders.graphql.js';
15
+ import { getTypeForState, StateTransitionControl } from './state-transition-control.js';
22
16
 
23
17
  type Order = NonNullable<ResultOf<typeof orderDetailFragment>>;
24
18
 
@@ -78,6 +72,30 @@ export function FulfillmentDetails({ order, fulfillment, onSuccess }: Readonly<F
78
72
  });
79
73
  };
80
74
 
75
+ const getFulfillmentActions = () => {
76
+ const actions = [];
77
+
78
+ const suggested = nextSuggestedState();
79
+ if (suggested) {
80
+ actions.push({
81
+ label: `Transition to ${suggested}`,
82
+ onClick: () => handleStateTransition(suggested),
83
+ disabled: transitionFulfillmentMutation.isPending,
84
+ });
85
+ }
86
+
87
+ nextOtherStates().forEach(state => {
88
+ actions.push({
89
+ label: `Transition to ${state}`,
90
+ type: getTypeForState(state),
91
+ onClick: () => handleStateTransition(state),
92
+ disabled: transitionFulfillmentMutation.isPending,
93
+ });
94
+ });
95
+
96
+ return actions;
97
+ };
98
+
81
99
  return (
82
100
  <div className="space-y-1 p-3 border rounded-md">
83
101
  <div className="space-y-1">
@@ -101,7 +119,7 @@ export function FulfillmentDetails({ order, fulfillment, onSuccess }: Readonly<F
101
119
  <ChevronDown className="h-4 w-4 transition-transform duration-200 data-[state=open]:rotate-180" />
102
120
  </CollapsibleTrigger>
103
121
  <CollapsibleContent className="mt-2 space-y-1">
104
- {fulfillment.lines.map((line) => {
122
+ {fulfillment.lines.map(line => {
105
123
  const orderLine = orderLinesMap.get(line.orderLineId);
106
124
  const productName = orderLine?.productVariant?.name ?? 'Unknown product';
107
125
  const sku = orderLine?.productVariant?.sku;
@@ -123,51 +141,13 @@ export function FulfillmentDetails({ order, fulfillment, onSuccess }: Readonly<F
123
141
  </div>
124
142
  )}
125
143
 
126
- {fulfillment.nextStates.length > 0 && (
127
- <div className="mt-3 pt-3 border-t">
128
- <div className="flex">
129
- <Button
130
- variant="outline"
131
- size="sm"
132
- disabled={transitionFulfillmentMutation.isPending}
133
- className="rounded-r-none flex-1 justify-start shadow-none"
134
- >
135
- <Trans>State: {fulfillment.state}</Trans>
136
- </Button>
137
- <DropdownMenu>
138
- <DropdownMenuTrigger asChild>
139
- <Button
140
- variant="outline"
141
- size="sm"
142
- disabled={transitionFulfillmentMutation.isPending}
143
- className="rounded-l-none border-l-0 shadow-none"
144
- >
145
- <ChevronDown className="h-4 w-4" />
146
- </Button>
147
- </DropdownMenuTrigger>
148
- <DropdownMenuContent align="end">
149
- {nextSuggestedState() && (
150
- <DropdownMenuItem
151
- onClick={() => handleStateTransition(nextSuggestedState()!)}
152
- disabled={transitionFulfillmentMutation.isPending}
153
- >
154
- <Trans>Transition to {nextSuggestedState()}</Trans>
155
- </DropdownMenuItem>
156
- )}
157
- {nextOtherStates().map(state => (
158
- <DropdownMenuItem
159
- key={state}
160
- onClick={() => handleStateTransition(state)}
161
- disabled={transitionFulfillmentMutation.isPending}
162
- >
163
- <Trans>Transition to {state}</Trans>
164
- </DropdownMenuItem>
165
- ))}
166
- </DropdownMenuContent>
167
- </DropdownMenu>
168
- </div>
169
- </div>
170
- )}
144
+ <div className="mt-3 pt-3 border-t">
145
+ <StateTransitionControl
146
+ currentState={fulfillment.state}
147
+ actions={getFulfillmentActions()}
148
+ isLoading={transitionFulfillmentMutation.isPending}
149
+ />
150
+ </div>
171
151
  </div>
172
152
  );
173
153
  }
@@ -2,13 +2,13 @@ import { Separator } from '@/vdb/components/ui/separator.js';
2
2
  import { ResultOf } from 'gql.tada';
3
3
  import { Globe, Phone } from 'lucide-react';
4
4
  import { orderAddressFragment } from '../orders.graphql.js';
5
+ import { Trans } from '@/vdb/lib/trans.js';
5
6
 
6
- type OrderAddress = ResultOf<typeof orderAddressFragment>;
7
+ type OrderAddress = Omit<ResultOf<typeof orderAddressFragment>, 'country'> & {
8
+ country: string | { code: string; name: string } | null;
9
+ };
7
10
 
8
11
  export function OrderAddress({ address }: Readonly<{ address?: OrderAddress }>) {
9
- if (!address) {
10
- return null;
11
- }
12
12
 
13
13
  const {
14
14
  fullName,
@@ -23,6 +23,13 @@ export function OrderAddress({ address }: Readonly<{ address?: OrderAddress }>)
23
23
  phoneNumber,
24
24
  } = address;
25
25
 
26
+ const countryName = typeof country === 'string' ? country : country?.name;
27
+ const countryCodeString = country && typeof country !== 'string' ? country?.code : countryCode;
28
+
29
+ if (!address || Object.values(address).every(value => !value)) {
30
+ return <div className="text-sm text-muted-foreground"><Trans>No address</Trans></div>;
31
+ }
32
+
26
33
  return (
27
34
  <div className="space-y-1 text-sm">
28
35
  {fullName && <p className="font-medium">{fullName}</p>}
@@ -36,9 +43,9 @@ export function OrderAddress({ address }: Readonly<{ address?: OrderAddress }>)
36
43
  {country && (
37
44
  <div className="flex items-center gap-1.5 mt-1">
38
45
  <Globe className="h-3 w-3 text-muted-foreground" />
39
- <span>{country}</span>
40
- {countryCode && (
41
- <span className="text-xs text-muted-foreground">({countryCode})</span>
46
+ <span>{countryName}</span>
47
+ {countryCodeString && (
48
+ <span className="text-xs text-muted-foreground">({countryCodeString})</span>
42
49
  )}
43
50
  </div>
44
51
  )}
@@ -2,17 +2,24 @@ import { CustomFieldsForm } from '@/vdb/components/shared/custom-fields-form.js'
2
2
  import { Button } from '@/vdb/components/ui/button.js';
3
3
  import { Form } from '@/vdb/components/ui/form.js';
4
4
  import { Popover, PopoverContent, PopoverTrigger } from '@/vdb/components/ui/popover.js';
5
+ import { Trans } from '@/vdb/lib/trans.js';
5
6
  import { Settings2 } from 'lucide-react';
6
- import { UseFormReturn } from 'react-hook-form';
7
+ import { useForm } from 'react-hook-form';
7
8
 
8
9
  interface OrderLineCustomFieldsFormProps {
9
10
  onUpdate: (customFieldValues: Record<string, any>) => void;
10
- form: UseFormReturn<any>;
11
+ value: Record<string, any>;
11
12
  }
12
13
 
13
- export function OrderLineCustomFieldsForm({ onUpdate, form }: Readonly<OrderLineCustomFieldsFormProps>) {
14
+ export function OrderLineCustomFieldsForm({ onUpdate, value }: Readonly<OrderLineCustomFieldsFormProps>) {
15
+ const form = useForm<Record<string, any>>({
16
+ defaultValues: {
17
+ customFields: value,
18
+ },
19
+ });
20
+
14
21
  const onSubmit = (values: any) => {
15
- onUpdate(values.input?.customFields);
22
+ onUpdate(values.customFields);
16
23
  };
17
24
 
18
25
  return (
@@ -24,15 +31,19 @@ export function OrderLineCustomFieldsForm({ onUpdate, form }: Readonly<OrderLine
24
31
  </PopoverTrigger>
25
32
  <PopoverContent className="w-80">
26
33
  <Form {...form}>
27
- <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
28
- <h4 className="font-medium leading-none">Custom Fields</h4>
29
- <CustomFieldsForm
30
- entityType="OrderLine"
31
- control={form.control}
32
- formPathPrefix="input"
33
- />
34
+ <form
35
+ onSubmit={e => {
36
+ e.stopPropagation();
37
+ form.handleSubmit(onSubmit)(e);
38
+ }}
39
+ className="space-y-4"
40
+ >
41
+ <h4 className="font-medium leading-none">
42
+ <Trans>Custom Fields</Trans>
43
+ </h4>
44
+ <CustomFieldsForm entityType="OrderLine" control={form.control} />
34
45
  <Button type="submit" className="w-full" disabled={!form.formState.isValid}>
35
- Update
46
+ <Trans>Update</Trans>
36
47
  </Button>
37
48
  </form>
38
49
  </Form>