@vendure/dashboard 3.4.3-master-202509230228 → 3.4.3-master-202509250229

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 (49) hide show
  1. package/index.html +11 -12
  2. package/package.json +4 -4
  3. package/src/app/common/use-page-title.test.ts +263 -0
  4. package/src/app/common/use-page-title.ts +86 -0
  5. package/src/app/routes/__root.tsx +4 -4
  6. package/src/app/routes/_authenticated/_customers/components/customer-history/customer-history-container.tsx +2 -2
  7. package/src/app/routes/_authenticated/_customers/components/customer-history/customer-history-types.ts +5 -0
  8. package/src/app/routes/_authenticated/_customers/components/customer-history/customer-history-utils.tsx +124 -0
  9. package/src/app/routes/_authenticated/_customers/components/customer-history/customer-history.tsx +91 -59
  10. package/src/app/routes/_authenticated/_customers/components/customer-history/default-customer-history-components.tsx +176 -0
  11. package/src/app/routes/_authenticated/_customers/components/customer-history/index.ts +4 -2
  12. package/src/app/routes/_authenticated/_customers/customers.graphql.ts +2 -0
  13. package/src/app/routes/_authenticated/_orders/components/order-history/default-order-history-components.tsx +98 -0
  14. package/src/app/routes/_authenticated/_orders/components/order-history/order-history-container.tsx +9 -7
  15. package/src/app/routes/_authenticated/_orders/components/order-history/order-history-types.ts +5 -0
  16. package/src/app/routes/_authenticated/_orders/components/order-history/order-history-utils.tsx +173 -0
  17. package/src/app/routes/_authenticated/_orders/components/order-history/order-history.tsx +64 -408
  18. package/src/app/routes/_authenticated/_orders/orders.graphql.ts +4 -0
  19. package/src/app/routes/_authenticated/_orders/utils/order-detail-loaders.tsx +9 -4
  20. package/src/app/routes/_authenticated/_shipping-methods/components/metadata-badges.tsx +15 -0
  21. package/src/app/routes/_authenticated/_shipping-methods/components/price-display.tsx +21 -0
  22. package/src/app/routes/_authenticated/_shipping-methods/components/shipping-method-test-result-wrapper.tsx +87 -0
  23. package/src/app/routes/_authenticated/_shipping-methods/components/test-address-form.tsx +255 -0
  24. package/src/app/routes/_authenticated/_shipping-methods/components/test-order-builder.tsx +243 -0
  25. package/src/app/routes/_authenticated/_shipping-methods/components/test-shipping-methods-result.tsx +97 -0
  26. package/src/app/routes/_authenticated/_shipping-methods/components/test-shipping-methods-sheet.tsx +41 -0
  27. package/src/app/routes/_authenticated/_shipping-methods/components/test-shipping-methods.tsx +74 -0
  28. package/src/app/routes/_authenticated/_shipping-methods/components/test-single-method-result.tsx +90 -0
  29. package/src/app/routes/_authenticated/_shipping-methods/components/test-single-shipping-method-sheet.tsx +56 -0
  30. package/src/app/routes/_authenticated/_shipping-methods/components/test-single-shipping-method.tsx +82 -0
  31. package/src/app/routes/_authenticated/_shipping-methods/components/use-shipping-method-test-state.ts +67 -0
  32. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.graphql.ts +27 -0
  33. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.tsx +2 -2
  34. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +24 -4
  35. package/src/lib/components/shared/history-timeline/history-note-entry.tsx +65 -0
  36. package/src/lib/components/shared/history-timeline/history-timeline-with-grouping.tsx +141 -0
  37. package/src/lib/components/shared/history-timeline/use-history-note-editor.ts +26 -0
  38. package/src/lib/framework/extension-api/define-dashboard-extension.ts +5 -0
  39. package/src/lib/framework/extension-api/extension-api-types.ts +7 -0
  40. package/src/lib/framework/extension-api/logic/history-entries.ts +24 -0
  41. package/src/lib/framework/extension-api/logic/index.ts +1 -0
  42. package/src/lib/framework/extension-api/types/history-entries.ts +120 -0
  43. package/src/lib/framework/extension-api/types/index.ts +1 -0
  44. package/src/lib/framework/history-entry/history-entry-extensions.ts +11 -0
  45. package/src/lib/framework/history-entry/history-entry.tsx +129 -0
  46. package/src/lib/framework/registry/registry-types.ts +2 -0
  47. package/src/lib/index.ts +5 -1
  48. package/src/app/routes/_authenticated/_shipping-methods/components/test-shipping-method-dialog.tsx +0 -32
  49. package/src/lib/components/shared/history-timeline/history-entry.tsx +0 -188
@@ -1,81 +1,113 @@
1
- import { HistoryEntry, HistoryEntryItem } from '@/vdb/components/shared/history-timeline/history-entry.js';
1
+ import { HistoryNoteEditor } from '@/vdb/components/shared/history-timeline/history-note-editor.js';
2
+ import { HistoryNoteEntry } from '@/vdb/components/shared/history-timeline/history-note-entry.js';
2
3
  import { HistoryNoteInput } from '@/vdb/components/shared/history-timeline/history-note-input.js';
3
- import { HistoryTimeline } from '@/vdb/components/shared/history-timeline/history-timeline.js';
4
- import { Badge } from '@/vdb/components/ui/badge.js';
5
- import { Trans } from '@/vdb/lib/trans.js';
6
- import { CheckIcon, SquarePen } from 'lucide-react';
4
+ import { HistoryTimelineWithGrouping } from '@/vdb/components/shared/history-timeline/history-timeline-with-grouping.js';
5
+ import { useHistoryNoteEditor } from '@/vdb/components/shared/history-timeline/use-history-note-editor.js';
6
+ import { HistoryEntryItem } from '@/vdb/framework/extension-api/types/history-entries.js';
7
+ import { HistoryEntryProps } from '@/vdb/framework/history-entry/history-entry.js';
8
+ import { CustomerHistoryCustomerDetail } from './customer-history-types.js';
9
+ import { customerHistoryUtils } from './customer-history-utils.js';
10
+ import {
11
+ CustomerAddRemoveGroupComponent,
12
+ CustomerAddressCreatedComponent,
13
+ CustomerAddressDeletedComponent,
14
+ CustomerAddressUpdatedComponent,
15
+ CustomerDetailUpdatedComponent,
16
+ CustomerEmailUpdateComponent,
17
+ CustomerPasswordResetRequestedComponent,
18
+ CustomerPasswordResetVerifiedComponent,
19
+ CustomerPasswordUpdatedComponent,
20
+ CustomerRegisteredOrVerifiedComponent,
21
+ } from './default-customer-history-components.js';
7
22
 
8
23
  interface CustomerHistoryProps {
9
- customer: {
10
- id: string;
11
- };
12
- historyEntries: Array<HistoryEntryItem>;
24
+ customer: CustomerHistoryCustomerDetail;
25
+ historyEntries: HistoryEntryItem[];
13
26
  onAddNote: (note: string, isPrivate: boolean) => void;
14
27
  onUpdateNote?: (entryId: string, note: string, isPrivate: boolean) => void;
15
28
  onDeleteNote?: (entryId: string) => void;
16
29
  }
17
30
 
18
31
  export function CustomerHistory({
32
+ customer,
19
33
  historyEntries,
20
34
  onAddNote,
21
35
  onUpdateNote,
22
36
  onDeleteNote,
23
- }: CustomerHistoryProps) {
24
- const getTimelineIcon = (entry: CustomerHistoryProps['historyEntries'][0]) => {
25
- switch (entry.type) {
26
- case 'CUSTOMER_NOTE':
27
- return <SquarePen className="h-4 w-4" />;
28
- default:
29
- return <CheckIcon className="h-4 w-4" />;
30
- }
37
+ }: Readonly<CustomerHistoryProps>) {
38
+ const { noteState, noteEditorOpen, handleEditNote, setNoteEditorOpen } = useHistoryNoteEditor();
39
+
40
+ const handleDeleteNote = (noteId: string) => {
41
+ onDeleteNote?.(noteId);
31
42
  };
32
43
 
33
- const getTitle = (entry: CustomerHistoryProps['historyEntries'][0]) => {
34
- switch (entry.type) {
35
- case 'CUSTOMER_NOTE':
36
- return <Trans>Note added</Trans>;
37
- default:
38
- return <Trans>{entry.type.replace(/_/g, ' ').toLowerCase()}</Trans>;
44
+ const handleNoteEditorSave = (noteId: string, note: string, isPrivate: boolean) => {
45
+ onUpdateNote?.(noteId, note, isPrivate);
46
+ };
47
+
48
+ const { getTimelineIcon, getTitle, getIconColor, getActorName, isPrimaryEvent } =
49
+ customerHistoryUtils(customer);
50
+
51
+ const renderEntryContent = (entry: HistoryEntryItem) => {
52
+ const props: HistoryEntryProps = {
53
+ entry,
54
+ title: getTitle(entry),
55
+ actorName: getActorName(entry),
56
+ timelineIcon: getTimelineIcon(entry),
57
+ timelineIconClassName: getIconColor(entry),
58
+ isPrimary: isPrimaryEvent(entry),
59
+ children: null,
60
+ };
61
+ if (entry.type === 'CUSTOMER_REGISTERED' || entry.type === 'CUSTOMER_VERIFIED') {
62
+ return <CustomerRegisteredOrVerifiedComponent {...props} />;
63
+ } else if (entry.type === 'CUSTOMER_DETAIL_UPDATED') {
64
+ return <CustomerDetailUpdatedComponent {...props} />;
65
+ } else if (entry.type === 'CUSTOMER_ADDED_TO_GROUP' || entry.type === 'CUSTOMER_REMOVED_FROM_GROUP') {
66
+ return <CustomerAddRemoveGroupComponent {...props} />;
67
+ } else if (entry.type === 'CUSTOMER_ADDRESS_CREATED') {
68
+ return <CustomerAddressCreatedComponent {...props} />;
69
+ } else if (entry.type === 'CUSTOMER_ADDRESS_UPDATED') {
70
+ return <CustomerAddressUpdatedComponent {...props} />;
71
+ } else if (entry.type === 'CUSTOMER_ADDRESS_DELETED') {
72
+ return <CustomerAddressDeletedComponent {...props} />;
73
+ } else if (entry.type === 'CUSTOMER_NOTE') {
74
+ return (
75
+ <HistoryNoteEntry {...props} onEditNote={handleEditNote} onDeleteNote={handleDeleteNote} />
76
+ );
77
+ } else if (entry.type === 'CUSTOMER_PASSWORD_UPDATED') {
78
+ return <CustomerPasswordUpdatedComponent {...props} />;
79
+ } else if (entry.type === 'CUSTOMER_PASSWORD_RESET_REQUESTED') {
80
+ return <CustomerPasswordResetRequestedComponent {...props} />;
81
+ } else if (entry.type === 'CUSTOMER_PASSWORD_RESET_VERIFIED') {
82
+ return <CustomerPasswordResetVerifiedComponent {...props} />;
83
+ } else if (
84
+ entry.type === 'CUSTOMER_EMAIL_UPDATE_REQUESTED' ||
85
+ entry.type === 'CUSTOMER_EMAIL_UPDATE_VERIFIED'
86
+ ) {
87
+ return <CustomerEmailUpdateComponent {...props} />;
39
88
  }
89
+ return null;
40
90
  };
41
91
 
42
92
  return (
43
- <div className="">
44
- <div className="mb-4">
93
+ <>
94
+ <HistoryTimelineWithGrouping
95
+ historyEntries={historyEntries}
96
+ isPrimaryEvent={isPrimaryEvent}
97
+ renderEntryContent={renderEntryContent}
98
+ entity={customer}
99
+ >
45
100
  <HistoryNoteInput onAddNote={onAddNote} />
46
- </div>
47
- <HistoryTimeline onEditNote={onUpdateNote} onDeleteNote={onDeleteNote}>
48
- {historyEntries.map(entry => (
49
- <HistoryEntry
50
- key={entry.id}
51
- entry={entry}
52
- isNoteEntry={entry.type === 'CUSTOMER_NOTE'}
53
- timelineIcon={getTimelineIcon(entry)}
54
- title={getTitle(entry)}
55
- >
56
- {entry.type === 'CUSTOMER_NOTE' && (
57
- <div className="flex items-center space-x-2">
58
- <Badge variant={entry.isPublic ? 'outline' : 'secondary'} className="text-xs">
59
- {entry.isPublic ? 'Public' : 'Private'}
60
- </Badge>
61
- <span>{entry.data.note}</span>
62
- </div>
63
- )}
64
- <div className="text-sm text-muted-foreground">
65
- {entry.type === 'CUSTOMER_NOTE' && (
66
- <Trans>
67
- From {entry.data.from} to {entry.data.to}
68
- </Trans>
69
- )}
70
- {entry.type === 'ORDER_PAYMENT_TRANSITION' && (
71
- <Trans>
72
- Payment #{entry.data.paymentId} transitioned to {entry.data.to}
73
- </Trans>
74
- )}
75
- </div>
76
- </HistoryEntry>
77
- ))}
78
- </HistoryTimeline>
79
- </div>
101
+ </HistoryTimelineWithGrouping>
102
+ <HistoryNoteEditor
103
+ key={noteState.noteId}
104
+ open={noteEditorOpen}
105
+ onOpenChange={setNoteEditorOpen}
106
+ onNoteChange={handleNoteEditorSave}
107
+ note={noteState.note}
108
+ isPrivate={noteState.isPrivate}
109
+ noteId={noteState.noteId}
110
+ />
111
+ </>
80
112
  );
81
113
  }
@@ -0,0 +1,176 @@
1
+ import { HistoryEntry, HistoryEntryProps } from '@/vdb/framework/history-entry/history-entry.js';
2
+ import { Trans } from '@/vdb/lib/trans.js';
3
+
4
+ export function CustomerRegisteredOrVerifiedComponent(props: Readonly<HistoryEntryProps>) {
5
+ const { entry } = props;
6
+ const strategy = entry.data?.strategy || 'native';
7
+
8
+ return (
9
+ <HistoryEntry {...props}>
10
+ <div className="space-y-1">
11
+ {strategy === 'native' ? (
12
+ <p className="text-xs text-muted-foreground">
13
+ <Trans>Using native authentication strategy</Trans>
14
+ </p>
15
+ ) : (
16
+ <p className="text-xs text-muted-foreground">
17
+ <Trans>Using external authentication strategy: {strategy}</Trans>
18
+ </p>
19
+ )}
20
+ </div>
21
+ </HistoryEntry>
22
+ );
23
+ }
24
+
25
+ export function CustomerDetailUpdatedComponent(props: Readonly<HistoryEntryProps>) {
26
+ const { entry } = props;
27
+ return (
28
+ <HistoryEntry {...props}>
29
+ <div className="space-y-2">
30
+ {entry.data?.input && (
31
+ <details className="text-xs">
32
+ <summary className="cursor-pointer text-muted-foreground hover:text-foreground">
33
+ <Trans>View changes</Trans>
34
+ </summary>
35
+ <pre className="mt-2 p-2 bg-muted rounded text-xs overflow-auto">
36
+ {JSON.stringify(entry.data.input, null, 2)}
37
+ </pre>
38
+ </details>
39
+ )}
40
+ </div>
41
+ </HistoryEntry>
42
+ );
43
+ }
44
+
45
+ export function CustomerAddRemoveGroupComponent(props: Readonly<HistoryEntryProps>) {
46
+ const { entry } = props;
47
+ const groupName = entry.data?.groupName || 'Unknown Group';
48
+
49
+ return (
50
+ <HistoryEntry {...props}>
51
+ <p className="text-xs text-muted-foreground">{groupName}</p>
52
+ </HistoryEntry>
53
+ );
54
+ }
55
+
56
+ export function CustomerAddressCreatedComponent(props: Readonly<HistoryEntryProps>) {
57
+ const { entry } = props;
58
+ return (
59
+ <HistoryEntry {...props}>
60
+ <div className="space-y-1">
61
+ <p className="text-xs text-muted-foreground">
62
+ <Trans>Address created</Trans>
63
+ </p>
64
+ {entry.data?.address && (
65
+ <div className="text-xs p-2 bg-muted rounded">{entry.data.address}</div>
66
+ )}
67
+ </div>
68
+ </HistoryEntry>
69
+ );
70
+ }
71
+
72
+ export function CustomerAddressUpdatedComponent(props: Readonly<HistoryEntryProps>) {
73
+ const { entry } = props;
74
+ return (
75
+ <HistoryEntry {...props}>
76
+ <div className="space-y-2">
77
+ <p className="text-xs text-muted-foreground">
78
+ <Trans>Address updated</Trans>
79
+ </p>
80
+ {entry.data?.address && (
81
+ <div className="text-xs p-2 bg-muted rounded">{entry.data.address}</div>
82
+ )}
83
+ {entry.data?.input && (
84
+ <details className="text-xs">
85
+ <summary className="cursor-pointer text-muted-foreground hover:text-foreground">
86
+ <Trans>View changes</Trans>
87
+ </summary>
88
+ <pre className="mt-2 p-2 bg-muted rounded text-xs overflow-auto">
89
+ {JSON.stringify(entry.data.input, null, 2)}
90
+ </pre>
91
+ </details>
92
+ )}
93
+ </div>
94
+ </HistoryEntry>
95
+ );
96
+ }
97
+
98
+ export function CustomerAddressDeletedComponent(props: Readonly<HistoryEntryProps>) {
99
+ const { entry } = props;
100
+ return (
101
+ <HistoryEntry {...props}>
102
+ <div className="space-y-1">
103
+ <p className="text-xs text-muted-foreground">
104
+ <Trans>Address deleted</Trans>
105
+ </p>
106
+ {entry.data?.address && (
107
+ <div className="text-xs p-2 bg-muted rounded">{entry.data.address}</div>
108
+ )}
109
+ </div>
110
+ </HistoryEntry>
111
+ );
112
+ }
113
+
114
+ export function CustomerPasswordUpdatedComponent(props: Readonly<HistoryEntryProps>) {
115
+ return (
116
+ <HistoryEntry {...props}>
117
+ <p className="text-xs text-muted-foreground">
118
+ <Trans>Password updated</Trans>
119
+ </p>
120
+ </HistoryEntry>
121
+ );
122
+ }
123
+
124
+ export function CustomerPasswordResetRequestedComponent(props: Readonly<HistoryEntryProps>) {
125
+ return (
126
+ <HistoryEntry {...props}>
127
+ <p className="text-xs text-muted-foreground">
128
+ <Trans>Password reset requested</Trans>
129
+ </p>
130
+ </HistoryEntry>
131
+ );
132
+ }
133
+
134
+ export function CustomerPasswordResetVerifiedComponent(props: Readonly<HistoryEntryProps>) {
135
+ return (
136
+ <HistoryEntry {...props}>
137
+ <p className="text-xs text-muted-foreground">
138
+ <Trans>Password reset verified</Trans>
139
+ </p>
140
+ </HistoryEntry>
141
+ );
142
+ }
143
+
144
+ export function CustomerEmailUpdateComponent({ entry }: Readonly<HistoryEntryProps>) {
145
+ const { oldEmailAddress, newEmailAddress } = entry.data || {};
146
+
147
+ return (
148
+ <div className="space-y-2">
149
+ {(oldEmailAddress || newEmailAddress) && (
150
+ <details className="text-xs">
151
+ <summary className="cursor-pointer text-muted-foreground hover:text-foreground">
152
+ <Trans>View details</Trans>
153
+ </summary>
154
+ <div className="mt-2 space-y-1">
155
+ {oldEmailAddress && (
156
+ <div>
157
+ <span className="font-medium">
158
+ <Trans>Old email:</Trans>
159
+ </span>{' '}
160
+ {oldEmailAddress}
161
+ </div>
162
+ )}
163
+ {newEmailAddress && (
164
+ <div>
165
+ <span className="font-medium">
166
+ <Trans>New email:</Trans>
167
+ </span>{' '}
168
+ {newEmailAddress}
169
+ </div>
170
+ )}
171
+ </div>
172
+ </details>
173
+ )}
174
+ </div>
175
+ );
176
+ }
@@ -1,3 +1,5 @@
1
- export * from './customer-history.js';
2
1
  export * from './customer-history-container.js';
3
- export * from './use-customer-history.js';
2
+ export * from './customer-history-utils.js';
3
+ export * from './customer-history.js';
4
+ export * from './default-customer-history-components.js';
5
+ export * from './default-customer-history-registry.js';
@@ -167,6 +167,8 @@ export const customerHistoryDocument = graphql(`
167
167
  id
168
168
  createdAt
169
169
  updatedAt
170
+ firstName
171
+ lastName
170
172
  history(options: $options) {
171
173
  totalItems
172
174
  items {
@@ -0,0 +1,98 @@
1
+ import { HistoryEntry, HistoryEntryProps } from '@/vdb/framework/history-entry/history-entry.js';
2
+ import { Trans } from '@/vdb/lib/trans.js';
3
+
4
+ export function OrderStateTransitionComponent(props: Readonly<HistoryEntryProps>) {
5
+ const { entry } = props;
6
+ if (entry.data.from === 'Created') return null;
7
+
8
+ return (
9
+ <HistoryEntry {...props}>
10
+ <p className="text-xs text-muted-foreground">
11
+ <Trans>
12
+ From {entry.data.from} to {entry.data.to}
13
+ </Trans>
14
+ </p>
15
+ </HistoryEntry>
16
+ );
17
+ }
18
+
19
+ export function OrderPaymentTransitionComponent(props: Readonly<HistoryEntryProps>) {
20
+ const { entry } = props;
21
+ return (
22
+ <HistoryEntry {...props}>
23
+ <p className="text-xs text-muted-foreground">
24
+ <Trans>
25
+ Payment #{entry.data.paymentId} transitioned to {entry.data.to}
26
+ </Trans>
27
+ </p>
28
+ </HistoryEntry>
29
+ );
30
+ }
31
+
32
+ export function OrderRefundTransitionComponent(props: Readonly<HistoryEntryProps>) {
33
+ const { entry } = props;
34
+ return (
35
+ <HistoryEntry {...props}>
36
+ <p className="text-xs text-muted-foreground">
37
+ <Trans>
38
+ Refund #{entry.data.refundId} transitioned to {entry.data.to}
39
+ </Trans>
40
+ </p>
41
+ </HistoryEntry>
42
+ );
43
+ }
44
+
45
+ export function OrderFulfillmentTransitionComponent(props: Readonly<HistoryEntryProps>) {
46
+ const { entry } = props;
47
+ return (
48
+ <HistoryEntry {...props}>
49
+ <p className="text-xs text-muted-foreground">
50
+ <Trans>
51
+ Fulfillment #{entry.data.fulfillmentId} from {entry.data.from} to {entry.data.to}
52
+ </Trans>
53
+ </p>
54
+ </HistoryEntry>
55
+ );
56
+ }
57
+
58
+ export function OrderFulfillmentComponent(props: Readonly<HistoryEntryProps>) {
59
+ const { entry } = props;
60
+ return (
61
+ <HistoryEntry {...props}>
62
+ <p className="text-xs text-muted-foreground">
63
+ <Trans>Fulfillment #{entry.data.fulfillmentId} created</Trans>
64
+ </p>
65
+ </HistoryEntry>
66
+ );
67
+ }
68
+
69
+ export function OrderModifiedComponent(props: Readonly<HistoryEntryProps>) {
70
+ const { entry } = props;
71
+ return (
72
+ <HistoryEntry {...props}>
73
+ <p className="text-xs text-muted-foreground">
74
+ <Trans>Order modification #{entry.data.modificationId}</Trans>
75
+ </p>
76
+ </HistoryEntry>
77
+ );
78
+ }
79
+
80
+ export function OrderCustomerUpdatedComponent(props: Readonly<HistoryEntryProps>) {
81
+ return (
82
+ <HistoryEntry {...props}>
83
+ <p className="text-xs text-muted-foreground">
84
+ <Trans>Customer information updated</Trans>
85
+ </p>
86
+ </HistoryEntry>
87
+ );
88
+ }
89
+
90
+ export function OrderCancellationComponent(props: Readonly<HistoryEntryProps>) {
91
+ return (
92
+ <HistoryEntry {...props}>
93
+ <p className="text-xs text-muted-foreground">
94
+ <Trans>Order cancelled</Trans>
95
+ </p>
96
+ </HistoryEntry>
97
+ );
98
+ }
@@ -61,13 +61,15 @@ export function OrderHistoryContainer({ orderId }: Readonly<OrderHistoryContaine
61
61
 
62
62
  return (
63
63
  <>
64
- <OrderHistory
65
- order={order}
66
- historyEntries={historyEntries ?? []}
67
- onAddNote={addNote}
68
- onUpdateNote={updateNote}
69
- onDeleteNote={deleteNote}
70
- />
64
+ {order ? (
65
+ <OrderHistory
66
+ order={order}
67
+ historyEntries={historyEntries ?? []}
68
+ onAddNote={addNote}
69
+ onUpdateNote={updateNote}
70
+ onDeleteNote={deleteNote}
71
+ />
72
+ ) : null}
71
73
  {hasNextPage && (
72
74
  <Button type="button" variant="outline" onClick={() => fetchNextPage()}>
73
75
  <Trans>Load more</Trans>
@@ -0,0 +1,5 @@
1
+ import { ResultOf } from 'gql.tada';
2
+
3
+ import { orderHistoryDocument } from '../../orders.graphql.js';
4
+
5
+ export type OrderHistoryOrderDetail = NonNullable<ResultOf<typeof orderHistoryDocument>>['order'];
@@ -0,0 +1,173 @@
1
+ import { HistoryEntryItem } from '@/vdb/framework/extension-api/types/index.js';
2
+ import { Trans } from '@/vdb/lib/trans.js';
3
+ import {
4
+ ArrowRightToLine,
5
+ Ban,
6
+ CheckIcon,
7
+ CreditCardIcon,
8
+ Edit3,
9
+ SquarePen,
10
+ Truck,
11
+ UserX,
12
+ } from 'lucide-react';
13
+ import { OrderHistoryOrderDetail } from './order-history-types.js';
14
+
15
+ export function orderHistoryUtils(order: OrderHistoryOrderDetail) {
16
+ const getTimelineIcon = (entry: HistoryEntryItem) => {
17
+ switch (entry.type) {
18
+ case 'ORDER_PAYMENT_TRANSITION':
19
+ return <CreditCardIcon className="h-4 w-4" />;
20
+ case 'ORDER_REFUND_TRANSITION':
21
+ return <CreditCardIcon className="h-4 w-4" />;
22
+ case 'ORDER_NOTE':
23
+ return <SquarePen className="h-4 w-4" />;
24
+ case 'ORDER_STATE_TRANSITION':
25
+ if (entry.data.to === 'Delivered') {
26
+ return <CheckIcon className="h-4 w-4" />;
27
+ }
28
+ if (entry.data.to === 'Cancelled') {
29
+ return <Ban className="h-4 w-4" />;
30
+ }
31
+ return <ArrowRightToLine className="h-4 w-4" />;
32
+ case 'ORDER_FULFILLMENT_TRANSITION':
33
+ if (entry.data.to === 'Shipped' || entry.data.to === 'Delivered') {
34
+ return <Truck className="h-4 w-4" />;
35
+ }
36
+ return <ArrowRightToLine className="h-4 w-4" />;
37
+ case 'ORDER_FULFILLMENT':
38
+ return <Truck className="h-4 w-4" />;
39
+ case 'ORDER_MODIFIED':
40
+ return <Edit3 className="h-4 w-4" />;
41
+ case 'ORDER_CUSTOMER_UPDATED':
42
+ return <UserX className="h-4 w-4" />;
43
+ case 'ORDER_CANCELLATION':
44
+ return <Ban className="h-4 w-4" />;
45
+ default:
46
+ return <CheckIcon className="h-4 w-4" />;
47
+ }
48
+ };
49
+
50
+ const getTitle = (entry: HistoryEntryItem) => {
51
+ switch (entry.type) {
52
+ case 'ORDER_PAYMENT_TRANSITION':
53
+ if (entry.data.to === 'Settled') {
54
+ return <Trans>Payment settled</Trans>;
55
+ }
56
+ if (entry.data.to === 'Authorized') {
57
+ return <Trans>Payment authorized</Trans>;
58
+ }
59
+ if (entry.data.to === 'Declined' || entry.data.to === 'Cancelled') {
60
+ return <Trans>Payment failed</Trans>;
61
+ }
62
+ return <Trans>Payment transitioned</Trans>;
63
+ case 'ORDER_REFUND_TRANSITION':
64
+ if (entry.data.to === 'Settled') {
65
+ return <Trans>Refund settled</Trans>;
66
+ }
67
+ return <Trans>Refund transitioned</Trans>;
68
+ case 'ORDER_NOTE':
69
+ return <Trans>Note added</Trans>;
70
+ case 'ORDER_STATE_TRANSITION': {
71
+ if (entry.data.from === 'Created') {
72
+ return <Trans>Order placed</Trans>;
73
+ }
74
+ if (entry.data.to === 'Delivered') {
75
+ return <Trans>Order fulfilled</Trans>;
76
+ }
77
+ if (entry.data.to === 'Cancelled') {
78
+ return <Trans>Order cancelled</Trans>;
79
+ }
80
+ if (entry.data.to === 'Shipped') {
81
+ return <Trans>Order shipped</Trans>;
82
+ }
83
+ return <Trans>Order transitioned</Trans>;
84
+ }
85
+ case 'ORDER_FULFILLMENT_TRANSITION':
86
+ if (entry.data.to === 'Shipped') {
87
+ return <Trans>Order shipped</Trans>;
88
+ }
89
+ if (entry.data.to === 'Delivered') {
90
+ return <Trans>Order delivered</Trans>;
91
+ }
92
+ return <Trans>Fulfillment transitioned</Trans>;
93
+ case 'ORDER_FULFILLMENT':
94
+ return <Trans>Fulfillment created</Trans>;
95
+ case 'ORDER_MODIFIED':
96
+ return <Trans>Order modified</Trans>;
97
+ case 'ORDER_CUSTOMER_UPDATED':
98
+ return <Trans>Customer updated</Trans>;
99
+ case 'ORDER_CANCELLATION':
100
+ return <Trans>Order cancelled</Trans>;
101
+ default:
102
+ return <Trans>{entry.type.replace(/_/g, ' ').toLowerCase()}</Trans>;
103
+ }
104
+ };
105
+
106
+ const getIconColor = ({ type, data }: HistoryEntryItem) => {
107
+ const success = 'bg-success text-success-foreground';
108
+ const destructive = 'bg-danger text-danger-foreground';
109
+ const regular = 'bg-muted text-muted-foreground';
110
+
111
+ if (type === 'ORDER_PAYMENT_TRANSITION' && data.to === 'Settled') {
112
+ return success;
113
+ }
114
+ if (type === 'ORDER_STATE_TRANSITION' && data.to === 'Delivered') {
115
+ return success;
116
+ }
117
+ if (type === 'ORDER_FULFILLMENT_TRANSITION' && data.to === 'Delivered') {
118
+ return success;
119
+ }
120
+ if (type === 'ORDER_CANCELLATION') {
121
+ return destructive;
122
+ }
123
+ if (type === 'ORDER_STATE_TRANSITION' && data.to === 'Cancelled') {
124
+ return destructive;
125
+ }
126
+ if (type === 'ORDER_PAYMENT_TRANSITION' && (data.to === 'Declined' || data.to === 'Cancelled')) {
127
+ return destructive;
128
+ }
129
+ return regular;
130
+ };
131
+
132
+ const getActorName = (entry: HistoryEntryItem) => {
133
+ if (entry.administrator) {
134
+ return `${entry.administrator.firstName} ${entry.administrator.lastName}`;
135
+ } else if (order?.customer) {
136
+ return `${order.customer.firstName} ${order.customer.lastName}`;
137
+ }
138
+ return '';
139
+ };
140
+
141
+ const isPrimaryEvent = (entry: HistoryEntryItem) => {
142
+ switch (entry.type) {
143
+ case 'ORDER_STATE_TRANSITION':
144
+ return (
145
+ entry.data.to === 'Delivered' ||
146
+ entry.data.to === 'Cancelled' ||
147
+ entry.data.to === 'Settled' ||
148
+ entry.data.from === 'Created'
149
+ );
150
+ case 'ORDER_REFUND_TRANSITION':
151
+ return entry.data.to === 'Settled';
152
+ case 'ORDER_PAYMENT_TRANSITION':
153
+ return entry.data.to === 'Settled' || entry.data.to === 'Cancelled';
154
+ case 'ORDER_FULFILLMENT_TRANSITION':
155
+ return entry.data.to === 'Delivered' || entry.data.to === 'Shipped';
156
+ case 'ORDER_NOTE':
157
+ case 'ORDER_MODIFIED':
158
+ case 'ORDER_CUSTOMER_UPDATED':
159
+ case 'ORDER_CANCELLATION':
160
+ return true;
161
+ default:
162
+ return false; // All other events are secondary
163
+ }
164
+ };
165
+
166
+ return {
167
+ getTimelineIcon,
168
+ getTitle,
169
+ getIconColor,
170
+ getActorName,
171
+ isPrimaryEvent,
172
+ };
173
+ }