@spree/seller-dashboard 1.0.0-beta.8 → 1.0.0-rc.2

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.
@@ -1,8 +1,8 @@
1
- import { createRequire } from "node:module";
2
1
  import path from "node:path";
3
2
  import {
4
3
  spreeDashboardPlugin as spreeDashboardCorePlugin
5
4
  } from "@spree/dashboard-core/vite";
5
+ import { resolveShellRoutesDir } from "@spree/dashboard-core/vite/discover";
6
6
  import { TanStackRouterVite } from "@tanstack/router-plugin/vite";
7
7
  import { index, layout, physical, rootRoute, route } from "@tanstack/virtual-file-routes";
8
8
  function spreeSellerDashboardPlugin(options = {}) {
@@ -21,9 +21,7 @@ function spreeSellerDashboardPlugin(options = {}) {
21
21
  ];
22
22
  }
23
23
  function sellerRouterPlugin(hostRoot, options) {
24
- const fromHost = createRequire(path.join(hostRoot, "package.json"));
25
- const shellEntry = fromHost.resolve("@spree/seller-dashboard");
26
- const shellRoutesDir = path.join(path.dirname(shellEntry), "routes");
24
+ const shellRoutesDir = resolveShellRoutesDir(hostRoot, "@spree/seller-dashboard");
27
25
  const virtualRouteConfig = rootRoute("__root.tsx", [
28
26
  route("/login", "login.tsx"),
29
27
  route("/forgot-password", "forgot-password.tsx"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spree/seller-dashboard",
3
- "version": "1.0.0-beta.8",
3
+ "version": "1.0.0-rc.2",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Spree marketplace seller panel — the seller-facing counterpart to the admin dashboard.",
@@ -49,16 +49,16 @@
49
49
  "react-hook-form": "^7.65.0",
50
50
  "react-i18next": "^17.0.8",
51
51
  "zod": "^4.4.3",
52
- "@spree/dashboard-core": "1.0.0-beta.8",
53
- "@spree/dashboard-ui": "1.0.0-beta.8",
54
- "@spree/seller-sdk": "1.0.0-beta.3"
52
+ "@spree/dashboard-core": "1.0.0-rc.2",
53
+ "@spree/dashboard-ui": "1.0.0-rc.2",
54
+ "@spree/seller-sdk": "1.0.0-rc.1"
55
55
  },
56
56
  "peerDependencies": {
57
57
  "react": "^19.0.0",
58
58
  "react-dom": "^19.0.0"
59
59
  },
60
60
  "devDependencies": {
61
- "@biomejs/biome": "2.4.13",
61
+ "@biomejs/biome": "^2.4.14",
62
62
  "@types/node": "^25.6.0",
63
63
  "@types/react": "^19.2.2",
64
64
  "@types/react-dom": "^19.2.1",
@@ -109,7 +109,20 @@ export function ExchangesCard({ order }: { order: Order }) {
109
109
  )}
110
110
  {exchange.status === 'received' && (
111
111
  <DropdownMenuItem
112
- onClick={() => fulfill.mutate({ exchangeId: exchange.id })}
112
+ onClick={async () => {
113
+ // A dearer replacement leaves the customer owing the
114
+ // difference, which fulfilling puts on the order.
115
+ if (
116
+ Number(exchange.price_difference) > 0 &&
117
+ !(await confirm({
118
+ message: t('orders.post_sale.exchanges.balance_due_confirm'),
119
+ confirmLabel: t('orders.post_sale.exchanges.fulfill'),
120
+ }))
121
+ ) {
122
+ return
123
+ }
124
+ fulfill.mutate({ exchangeId: exchange.id })
125
+ }}
113
126
  >
114
127
  {t('orders.post_sale.exchanges.fulfill')}
115
128
  </DropdownMenuItem>
@@ -137,7 +137,8 @@ export function CreateClaimDialog({
137
137
  id: item.id,
138
138
  label: unitLabel(item),
139
139
  quantity: item.quantity,
140
- price: item.price,
140
+ discountedAmount: item.discounted_amount,
141
+ additionalTaxTotal: item.additional_tax_total,
141
142
  }))}
142
143
  currencySymbol={currencySymbol}
143
144
  reasonField={<ClaimReasonField value={reasonId} onChange={setReasonId} />}
@@ -14,6 +14,8 @@ import {
14
14
  DropdownMenuTrigger,
15
15
  ReturnReceiveDialog,
16
16
  ReturnRefundDialog,
17
+ returnOwesNothing,
18
+ returnRefundSummary,
17
19
  StatusBadge,
18
20
  useConfirm,
19
21
  } from '@spree/dashboard-ui'
@@ -114,7 +116,9 @@ export function ReturnsCard({ order }: { order: Order }) {
114
116
  )}
115
117
  {returnRecord.status === 'received' && (
116
118
  <DropdownMenuItem onClick={() => setRefunding(returnRecord)}>
117
- {t('orders.post_sale.returns.refund')}
119
+ {returnOwesNothing(returnRecord.refundable_total)
120
+ ? t('admin.pages.orders.detail.returns.actions.complete')
121
+ : t('orders.post_sale.returns.refund')}
118
122
  </DropdownMenuItem>
119
123
  )}
120
124
  <DropdownMenuSeparator />
@@ -134,11 +138,7 @@ export function ReturnsCard({ order }: { order: Order }) {
134
138
  {returnRecord.return_line_items?.map((line) => (
135
139
  <ReturnLineRow key={line.id} line={line} />
136
140
  ))}
137
- <p className="text-muted-foreground text-sm">
138
- {t('orders.post_sale.returns.refund_total', {
139
- amount: returnRecord.display_refund_total,
140
- })}
141
- </p>
141
+ <RefundSummary returnRecord={returnRecord} />
142
142
  </CardContent>
143
143
  </Card>
144
144
  ))
@@ -165,6 +165,22 @@ export function ReturnsCard({ order }: { order: Order }) {
165
165
  )
166
166
  }
167
167
 
168
+ function RefundSummary({ returnRecord }: { returnRecord: Return }) {
169
+ const { t } = useTranslation()
170
+ const summary = returnRefundSummary(returnRecord)
171
+
172
+ return (
173
+ <p className="text-muted-foreground text-sm">
174
+ {summary.kind === 'owed' &&
175
+ t('orders.post_sale.returns.refund_total', { amount: summary.amount })}
176
+ {summary.kind === 'refunded' &&
177
+ t('orders.post_sale.returns.refunded', { amount: summary.amount })}
178
+ {summary.kind === 'refunded_short' &&
179
+ t('orders.post_sale.returns.refunded_of', { amount: summary.amount, total: summary.total })}
180
+ </p>
181
+ )
182
+ }
183
+
168
184
  function ReturnLineRow({ line }: { line: ReturnLineItem }) {
169
185
  const { t } = useTranslation()
170
186
  const label = lineLabel(line.name, line.variant, line.variant_id)
@@ -244,6 +260,11 @@ function RefundDialog({
244
260
  return (
245
261
  <ReturnRefundDialog
246
262
  refundableTotal={returnRecord.refundable_total}
263
+ refundTaxTotal={
264
+ Number(returnRecord.refund_tax_total) > 0
265
+ ? returnRecord.display_refund_tax_total
266
+ : undefined
267
+ }
247
268
  currencySymbol={currencySymbol}
248
269
  onClose={() => onOpenChange(false)}
249
270
  pending={refund.isPending}
@@ -80,7 +80,7 @@ export function SellerSwitcher() {
80
80
  return (
81
81
  <SidebarMenu>
82
82
  <SidebarMenuItem className="flex h-rail-header-height items-center">
83
- <div className="flex w-full items-center gap-2 p-1.5">{header}</div>
83
+ <div className="flex w-full items-center gap-2 ">{header}</div>
84
84
  </SidebarMenuItem>
85
85
  </SidebarMenu>
86
86
  )
@@ -136,7 +136,8 @@
136
136
  "returns": {
137
137
  "actions": {
138
138
  "receive": "استلام العناصر",
139
- "refund": "رد المبلغ"
139
+ "refund": "رد المبلغ",
140
+ "complete": "إكمال المرتجع"
140
141
  },
141
142
  "create_title": "إرجاع جديد",
142
143
  "location": "الإرجاع إلى",
@@ -147,12 +148,15 @@
147
148
  "receive_title": "استلام العناصر المرتجعة",
148
149
  "received_quantity": "المستلَم",
149
150
  "refund_amount": "المبلغ",
151
+ "refund_includes_tax": "يشمل المبلغ الكامل ضريبة بقيمة {{tax}}. عند رد مبلغ أقل، تُرد الضريبة بالنسبة نفسها.",
150
152
  "refund_method": "رد المبلغ إلى",
151
153
  "refund_methods": {
152
154
  "original_payment": "وسيلة الدفع الأصلية",
153
155
  "store_credit": "رصيد المتجر"
154
156
  },
155
157
  "refund_title": "رد مبلغ الإرجاع",
158
+ "complete_title": "إكمال المرتجع",
159
+ "nothing_owed": "لا يوجد أي مبلغ مستحق على هذا المرتجع، لذلك لن يُسترد أي مبلغ. يؤدي إكماله إلى إغلاق المرتجع.",
156
160
  "resellable": "قابل لإعادة البيع"
157
161
  }
158
162
  }
@@ -520,6 +524,7 @@
520
524
  "cancel_message": "يُبلَّغ العميل بأنه لن يمضي قدمًا.",
521
525
  "cancel_title": "إلغاء هذا الاستبدال؟",
522
526
  "fulfill": "إرسال البديل",
527
+ "balance_due_confirm": "البديل أغلى من المنتج المُعاد. يؤدي إرساله إلى إضافة الفرق إلى الطلب كرصيد مستحق. لا يتم خصم أي مبلغ تلقائيًا.",
523
528
  "price_difference": "فرق السعر: {{amount}}",
524
529
  "receive": "استلام",
525
530
  "title": "الاستبدالات"
@@ -535,6 +540,8 @@
535
540
  "receive": "استلام",
536
541
  "refund": "رد المبلغ",
537
542
  "refund_total": "إجمالي الاسترداد: {{amount}}",
543
+ "refunded": "تم الاسترداد: {{amount}}",
544
+ "refunded_of": "تم الاسترداد: {{amount}} من {{total}}",
538
545
  "title": "المرتجعات"
539
546
  },
540
547
  "statuses": {
@@ -136,7 +136,8 @@
136
136
  "returns": {
137
137
  "actions": {
138
138
  "receive": "Artikel annehmen",
139
- "refund": "Erstatten"
139
+ "refund": "Erstatten",
140
+ "complete": "Retoure abschließen"
140
141
  },
141
142
  "create_title": "Neue Rücksendung",
142
143
  "location": "Zurück an",
@@ -147,12 +148,15 @@
147
148
  "receive_title": "Rücksendung annehmen",
148
149
  "received_quantity": "Erhalten",
149
150
  "refund_amount": "Betrag",
151
+ "refund_includes_tax": "Der volle Betrag enthält {{tax}} Steuern. Bei einem kleineren Betrag wird die Steuer anteilig erstattet.",
150
152
  "refund_method": "Erstatten auf",
151
153
  "refund_methods": {
152
154
  "original_payment": "Ursprüngliche Zahlung",
153
155
  "store_credit": "Guthaben"
154
156
  },
155
157
  "refund_title": "Rücksendung erstatten",
158
+ "complete_title": "Retoure abschließen",
159
+ "nothing_owed": "Für diese Retoure ist nichts geschuldet, daher wird kein Geld erstattet. Mit dem Abschließen wird die Retoure geschlossen.",
156
160
  "resellable": "Wiederverkäuflich"
157
161
  }
158
162
  }
@@ -516,6 +520,7 @@
516
520
  "cancel_message": "Der Kunde erfährt, dass es nicht weitergeht.",
517
521
  "cancel_title": "Diesen Umtausch abbrechen?",
518
522
  "fulfill": "Ersatz senden",
523
+ "balance_due_confirm": "Der Ersatz kostet mehr als die zurückgesendete Ware. Beim Versand wird die Differenz der Bestellung als offener Betrag hinzugefügt. Es wird nichts automatisch belastet.",
519
524
  "price_difference": "Preisdifferenz: {{amount}}",
520
525
  "receive": "Annehmen",
521
526
  "title": "Umtausch"
@@ -531,6 +536,8 @@
531
536
  "receive": "Annehmen",
532
537
  "refund": "Erstatten",
533
538
  "refund_total": "Erstattung gesamt: {{amount}}",
539
+ "refunded": "Erstattet: {{amount}}",
540
+ "refunded_of": "Erstattet: {{amount}} von {{total}}",
534
541
  "title": "Rücksendungen"
535
542
  },
536
543
  "statuses": {
@@ -155,10 +155,14 @@
155
155
  "resellable": "Resellable",
156
156
  "actions": {
157
157
  "receive": "Receive items",
158
- "refund": "Refund"
158
+ "refund": "Refund",
159
+ "complete": "Complete return"
159
160
  },
160
161
  "refund_title": "Refund return",
162
+ "complete_title": "Complete return",
163
+ "nothing_owed": "Nothing is owed on this return, so no money goes back. Completing it closes the return.",
161
164
  "refund_amount": "Amount",
165
+ "refund_includes_tax": "The full amount includes {{tax}} of tax. A smaller amount gives back tax in the same proportion.",
162
166
  "refund_method": "Refund to",
163
167
  "refund_methods": {
164
168
  "original_payment": "Original payment",
@@ -516,6 +520,7 @@
516
520
  "title": "Exchanges",
517
521
  "receive": "Receive",
518
522
  "fulfill": "Send replacement",
523
+ "balance_due_confirm": "The replacement costs more than what came back. Sending it adds the difference to the order as a balance due. Nothing is charged automatically.",
519
524
  "price_difference": "Price difference: {{amount}}",
520
525
  "cancel_title": "Cancel this exchange?",
521
526
  "cancel_message": "The customer is told it will not go ahead."
@@ -531,6 +536,8 @@
531
536
  "receive": "Receive",
532
537
  "refund": "Refund",
533
538
  "refund_total": "Refund total: {{amount}}",
539
+ "refunded": "Refunded: {{amount}}",
540
+ "refunded_of": "Refunded: {{amount}} of {{total}}",
534
541
  "title": "Returns"
535
542
  },
536
543
  "statuses": {
@@ -136,7 +136,8 @@
136
136
  "returns": {
137
137
  "actions": {
138
138
  "receive": "Réceptionner les articles",
139
- "refund": "Rembourser"
139
+ "refund": "Rembourser",
140
+ "complete": "Clôturer le retour"
140
141
  },
141
142
  "create_title": "Nouveau retour",
142
143
  "location": "Retourner à",
@@ -147,12 +148,15 @@
147
148
  "receive_title": "Réceptionner les articles retournés",
148
149
  "received_quantity": "Reçu",
149
150
  "refund_amount": "Montant",
151
+ "refund_includes_tax": "Le montant total comprend {{tax}} de taxes. Un montant inférieur rembourse les taxes dans la même proportion.",
150
152
  "refund_method": "Rembourser sur",
151
153
  "refund_methods": {
152
154
  "original_payment": "Paiement d’origine",
153
155
  "store_credit": "Avoir"
154
156
  },
155
157
  "refund_title": "Rembourser le retour",
158
+ "complete_title": "Clôturer le retour",
159
+ "nothing_owed": "Rien n'est dû sur ce retour, donc aucun remboursement n'est effectué. Le clôturer ferme le retour.",
156
160
  "resellable": "Revendable"
157
161
  }
158
162
  }
@@ -516,6 +520,7 @@
516
520
  "cancel_message": "Le client est informé que cela n’aura pas lieu.",
517
521
  "cancel_title": "Annuler cet échange ?",
518
522
  "fulfill": "Envoyer le remplacement",
523
+ "balance_due_confirm": "Le remplacement coûte plus cher que l'article retourné. Son envoi ajoute la différence à la commande comme solde dû. Rien n'est débité automatiquement.",
519
524
  "price_difference": "Différence de prix : {{amount}}",
520
525
  "receive": "Réceptionner",
521
526
  "title": "Échanges"
@@ -531,6 +536,8 @@
531
536
  "receive": "Réceptionner",
532
537
  "refund": "Rembourser",
533
538
  "refund_total": "Total du remboursement : {{amount}}",
539
+ "refunded": "Remboursé : {{amount}}",
540
+ "refunded_of": "Remboursé : {{amount}} sur {{total}}",
534
541
  "title": "Retours"
535
542
  },
536
543
  "statuses": {
@@ -136,7 +136,8 @@
136
136
  "returns": {
137
137
  "actions": {
138
138
  "receive": "Przyjmij pozycje",
139
- "refund": "Zwrot pieniędzy"
139
+ "refund": "Zwrot pieniędzy",
140
+ "complete": "Zakończ zwrot"
140
141
  },
141
142
  "create_title": "Nowy zwrot",
142
143
  "location": "Zwrot do",
@@ -147,12 +148,15 @@
147
148
  "receive_title": "Przyjmij zwrócone pozycje",
148
149
  "received_quantity": "Przyjęto",
149
150
  "refund_amount": "Kwota",
151
+ "refund_includes_tax": "Pełna kwota zawiera {{tax}} podatku. Przy mniejszej kwocie podatek zostanie zwrócony proporcjonalnie.",
150
152
  "refund_method": "Zwrot na",
151
153
  "refund_methods": {
152
154
  "original_payment": "Pierwotna płatność",
153
155
  "store_credit": "Kredyt sklepowy"
154
156
  },
155
157
  "refund_title": "Zwrot pieniędzy za zwrot",
158
+ "complete_title": "Zakończ zwrot",
159
+ "nothing_owed": "Z tytułu tego zwrotu nic nie jest należne, więc pieniądze nie zostaną zwrócone. Zakończenie zamyka zwrot.",
156
160
  "resellable": "Do ponownej sprzedaży"
157
161
  }
158
162
  }
@@ -518,6 +522,7 @@
518
522
  "cancel_message": "Klient dostanie informację, że sprawa nie będzie kontynuowana.",
519
523
  "cancel_title": "Anulować tę wymianę?",
520
524
  "fulfill": "Wyślij wymianę",
525
+ "balance_due_confirm": "Zamiennik kosztuje więcej niż zwrócony towar. Jego wysłanie doda różnicę do zamówienia jako kwotę do zapłaty. Nic nie zostanie pobrane automatycznie.",
521
526
  "price_difference": "Różnica w cenie: {{amount}}",
522
527
  "receive": "Przyjmij",
523
528
  "title": "Wymiany"
@@ -533,6 +538,8 @@
533
538
  "receive": "Przyjmij",
534
539
  "refund": "Zwrot pieniędzy",
535
540
  "refund_total": "Łączny zwrot: {{amount}}",
541
+ "refunded": "Zwrócono: {{amount}}",
542
+ "refunded_of": "Zwrócono: {{amount}} z {{total}}",
536
543
  "title": "Zwroty"
537
544
  },
538
545
  "statuses": {
@@ -136,7 +136,8 @@
136
136
  "returns": {
137
137
  "actions": {
138
138
  "receive": "接收商品",
139
- "refund": "退款"
139
+ "refund": "退款",
140
+ "complete": "完成退货"
140
141
  },
141
142
  "create_title": "新建退货",
142
143
  "location": "退回至",
@@ -147,12 +148,15 @@
147
148
  "receive_title": "接收退货商品",
148
149
  "received_quantity": "已接收",
149
150
  "refund_amount": "金额",
151
+ "refund_includes_tax": "全额包含 {{tax}} 税费。退款金额较小时,税费按相同比例退还。",
150
152
  "refund_method": "退款至",
151
153
  "refund_methods": {
152
154
  "original_payment": "原支付方式",
153
155
  "store_credit": "店铺余额"
154
156
  },
155
157
  "refund_title": "退货退款",
158
+ "complete_title": "完成退货",
159
+ "nothing_owed": "此退货没有应退金额,因此不会退款。完成后该退货将关闭。",
156
160
  "resellable": "可再次销售"
157
161
  }
158
162
  }
@@ -515,6 +519,7 @@
515
519
  "cancel_message": "客户会收到不再继续处理的通知。",
516
520
  "cancel_title": "取消此换货?",
517
521
  "fulfill": "寄出替换件",
522
+ "balance_due_confirm": "替换商品比退回的商品更贵。发出替换商品会将差额作为待付余额添加到订单中。不会自动扣款。",
518
523
  "price_difference": "差价:{{amount}}",
519
524
  "receive": "接收",
520
525
  "title": "换货"
@@ -530,6 +535,8 @@
530
535
  "receive": "接收",
531
536
  "refund": "退款",
532
537
  "refund_total": "退款合计:{{amount}}",
538
+ "refunded": "已退款:{{amount}}",
539
+ "refunded_of": "已退款:{{amount}} / {{total}}",
533
540
  "title": "退货"
534
541
  },
535
542
  "statuses": {
@@ -1,4 +1,4 @@
1
- import { hasVisibleSettingsEntries, i18n, nav } from '@spree/dashboard-core'
1
+ import { hasVisibleSettingsEntries, i18n, nav, Subject } from '@spree/dashboard-core'
2
2
  import {
3
3
  BanknoteIcon,
4
4
  ClipboardCheckIcon,
@@ -50,7 +50,7 @@ nav.add({
50
50
  path: '/products',
51
51
  icon: TagIcon,
52
52
  position: 110,
53
- subject: 'Spree::Product',
53
+ subject: Subject.Product,
54
54
  })
55
55
 
56
56
  nav.add({
@@ -59,7 +59,7 @@ nav.add({
59
59
  path: '/orders',
60
60
  icon: PackageIcon,
61
61
  position: 120,
62
- subject: 'Spree::Order',
62
+ subject: Subject.Order,
63
63
  })
64
64
 
65
65
  // The seller's own books. Two entries rather than one because they answer
@@ -1,4 +1,4 @@
1
- import { i18n, settingsNav } from '@spree/dashboard-core'
1
+ import { i18n, Subject, settingsNav } from '@spree/dashboard-core'
2
2
  import {
3
3
  PackageIcon,
4
4
  ScrollTextIcon,
@@ -44,7 +44,7 @@ settingsNav.add({
44
44
  position: 100,
45
45
  // The seller-branch subject the API's `/me` serializes. A member without
46
46
  // stock permission never sees the entry.
47
- subject: 'Spree::StockLocation',
47
+ subject: Subject.StockLocation,
48
48
  })
49
49
 
50
50
  // How this seller's goods ship. Beside stock locations because the two are
@@ -60,7 +60,7 @@ settingsNav.add({
60
60
  position: 200,
61
61
  // The seller-branch subject the API's `/me` serializes. A member without
62
62
  // delivery permission never sees the entry.
63
- subject: 'Spree::DeliveryMethod',
63
+ subject: Subject.DeliveryMethod,
64
64
  })
65
65
 
66
66
  // What this seller packs their goods into. Beside the other two because the
@@ -77,7 +77,7 @@ settingsNav.add({
77
77
  position: 300,
78
78
  // The seller-branch subject the API's `/me` serializes. A member without
79
79
  // packaging permission never sees the entry.
80
- subject: 'Spree::PackageType',
80
+ subject: Subject.PackageType,
81
81
  })
82
82
 
83
83
  // Who runs this seller. Under settings rather than in the main rail, matching
@@ -1,4 +1,4 @@
1
- import { formatStoreDateTime, PageHeader } from '@spree/dashboard-core'
1
+ import { formatStoreDateTime, PageHeader, Slot } from '@spree/dashboard-core'
2
2
  import { DropdownMenuItem, ResourceLayout, StatusBadge } from '@spree/dashboard-ui'
3
3
  import { XCircleIcon } from '@spree/dashboard-ui/icons'
4
4
  import { useParams } from '@tanstack/react-router'
@@ -113,6 +113,7 @@ export function OrderPage() {
113
113
  <OrderCustomerCard order={order} />
114
114
  <SpecialInstructionsCard order={order} />
115
115
  <InternalNoteCard order={order} />
116
+ <Slot name="seller.order.form_sidebar" context={{ order }} />
116
117
  </>
117
118
  }
118
119
  />
@@ -1,4 +1,4 @@
1
- import { formatStoreDateTime, PageHeader } from '@spree/dashboard-core'
1
+ import { formatStoreDateTime, PageHeader, Slot } from '@spree/dashboard-core'
2
2
  import {
3
3
  Card,
4
4
  CardHeader,
@@ -134,20 +134,23 @@ export function PayoutPage() {
134
134
  </Card>
135
135
  }
136
136
  sidebar={
137
- <Card>
138
- <CardHeader>
139
- <CardTitle>{t('payouts.detail.settlement')}</CardTitle>
140
- </CardHeader>
141
- <div className="flex flex-col gap-3 px-6 pb-6">
142
- <ReadRow label={t('payouts.columns.amount')}>{payout.display_amount}</ReadRow>
143
- <ReadRow label={t('payouts.columns.reference')}>{payout.reference}</ReadRow>
144
- <ReadRow label={t('payouts.detail.period')}>
145
- {payout.period_start && payout.period_end
146
- ? `${formatStoreDateTime(payout.period_start, timezone)} – ${formatStoreDateTime(payout.period_end, timezone)}`
147
- : null}
148
- </ReadRow>
149
- </div>
150
- </Card>
137
+ <>
138
+ <Card>
139
+ <CardHeader>
140
+ <CardTitle>{t('payouts.detail.settlement')}</CardTitle>
141
+ </CardHeader>
142
+ <div className="flex flex-col gap-3 px-6 pb-6">
143
+ <ReadRow label={t('payouts.columns.amount')}>{payout.display_amount}</ReadRow>
144
+ <ReadRow label={t('payouts.columns.reference')}>{payout.reference}</ReadRow>
145
+ <ReadRow label={t('payouts.detail.period')}>
146
+ {payout.period_start && payout.period_end
147
+ ? `${formatStoreDateTime(payout.period_start, timezone)} – ${formatStoreDateTime(payout.period_end, timezone)}`
148
+ : null}
149
+ </ReadRow>
150
+ </div>
151
+ </Card>
152
+ <Slot name="seller.payout.form_sidebar" context={{ payout }} />
153
+ </>
151
154
  }
152
155
  />
153
156
  )
@@ -1,6 +1,8 @@
1
1
  import { zodResolver } from '@hookform/resolvers/zod'
2
2
  import {
3
3
  CategorizationCard,
4
+ extensionFormValues,
5
+ extensionSubmitValues,
4
6
  GeneralCard,
5
7
  InventoryCard,
6
8
  MediaCard,
@@ -12,6 +14,7 @@ import {
12
14
  type ProductFormValues,
13
15
  productFormSchema,
14
16
  productToFormValues,
17
+ Slot,
15
18
  VariantsCard,
16
19
  variantToWirePayload,
17
20
  } from '@spree/dashboard-core'
@@ -88,7 +91,7 @@ export function ProductPage({ mode }: { mode: 'new' | 'edit' }) {
88
91
  // The operator's defaults, which seed one placeholder variant — without
89
92
  // it the Prices and Inventory cards render no row, so a seller could not
90
93
  // price or stock a new listing at all.
91
- defaultValues: newProductFormDefaults(),
94
+ defaultValues: { ...newProductFormDefaults(), ...extensionFormValues('seller.product', null) },
92
95
  })
93
96
 
94
97
  // Reset once the record arrives, so the inputs show what was last saved
@@ -102,12 +105,22 @@ export function ProductPage({ mode }: { mode: 'new' | 'edit' }) {
102
105
  if (!product) return
103
106
  if (form.formState.isDirty) return
104
107
 
105
- form.reset(productToFormValues(product as PanelProduct, product.media))
108
+ form.reset({
109
+ ...productToFormValues(product as PanelProduct, product.media),
110
+ ...extensionFormValues('seller.product', product),
111
+ })
106
112
  }, [product, form])
107
113
 
108
114
  const save = useMutation({
109
- mutationFn: (values: ProductFormValues) => {
115
+ mutationFn: ({
116
+ values,
117
+ extensionValues,
118
+ }: {
119
+ values: ProductFormValues
120
+ extensionValues: Record<string, unknown>
121
+ }) => {
110
122
  const payload: ProductParams = {
123
+ ...extensionValues,
111
124
  name: values.name,
112
125
  description: values.description,
113
126
  slug: values.slug || undefined,
@@ -165,7 +178,12 @@ export function ProductPage({ mode }: { mode: 'new' | 'edit' }) {
165
178
 
166
179
  async function onSubmit(values: ProductFormValues) {
167
180
  try {
168
- await save.mutateAsync(values)
181
+ // Raw form state, not `values`: the Zod parse strips keys the schema
182
+ // doesn't know, which is every field a slot widget registered.
183
+ await save.mutateAsync({
184
+ values,
185
+ extensionValues: extensionSubmitValues('seller.product', form),
186
+ })
169
187
  } catch (err) {
170
188
  if (!mapSpreeErrorsToForm(err, form.setError)) {
171
189
  toastManager.add({
@@ -241,6 +259,7 @@ export function ProductPage({ mode }: { mode: 'new' | 'edit' }) {
241
259
  />
242
260
  )}
243
261
  <CategorizationCard form={form} />
262
+ <Slot name="seller.product.form_sidebar" context={{ product, mode }} />
244
263
  </>
245
264
  }
246
265
  />
@@ -1,4 +1,4 @@
1
- import { PageHeader } from '@spree/dashboard-core'
1
+ import { PageHeader, Slot } from '@spree/dashboard-core'
2
2
  import {
3
3
  Button,
4
4
  Card,
@@ -84,7 +84,12 @@ export function ProfilePage() {
84
84
  <>
85
85
  <ResourceLayout
86
86
  header={<PageHeader title={profile.name} subtitle={t('profile.subtitle')} />}
87
- main={<BrandCard profile={profile} onEdit={() => setEditing(true)} />}
87
+ main={
88
+ <>
89
+ <BrandCard profile={profile} onEdit={() => setEditing(true)} />
90
+ <Slot name="seller.profile.form_main" context={{ profile }} />
91
+ </>
92
+ }
88
93
  sidebar={
89
94
  <>
90
95
  <StandingCard profile={profile} />
@@ -95,6 +100,7 @@ export function ProfilePage() {
95
100
  <SellerAddressCard profile={profile} />
96
101
  <SellerReturnsLocationCard />
97
102
  <SettlementCard profile={profile} />
103
+ <Slot name="seller.profile.form_sidebar" context={{ profile }} />
98
104
  </>
99
105
  }
100
106
  />
@@ -9,40 +9,34 @@
9
9
  // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
10
10
 
11
11
  import { Route as rootRouteImport } from './routes/__root'
12
- import { Route as resetPasswordRouteImport } from './routes/reset-password'
13
- import { Route as loginRouteImport } from './routes/login'
14
- import { Route as forgotPasswordRouteImport } from './routes/forgot-password'
15
12
  import { Route as authenticatedRouteImport } from './routes/_authenticated'
13
+ import { Route as forgotPasswordRouteImport } from './routes/forgot-password'
14
+ import { Route as loginRouteImport } from './routes/login'
15
+ import { Route as resetPasswordRouteImport } from './routes/reset-password'
16
16
  import { Route as authenticatedIndexRouteImport } from './routes/_authenticated/index'
17
- import { Route as acceptInvitationDotinvitationIdRouteImport } from './routes/accept-invitation.$invitationId'
18
17
  import { Route as authenticatedSellerIdRouteImport } from './routes/_authenticated/$sellerId'
18
+ import { Route as acceptInvitationDotinvitationIdRouteImport } from './routes/accept-invitation.$invitationId'
19
19
  import { Route as IndexRouteImport } from './routes/_authenticated/$sellerId/index'
20
- import { Route as SettingsRouteImport } from './routes/_authenticated/$sellerId/settings'
21
- import { Route as ProfileRouteImport } from './routes/_authenticated/$sellerId/profile'
22
20
  import { Route as OnboardingRouteImport } from './routes/_authenticated/$sellerId/onboarding'
23
- import { Route as SettingsIndexRouteImport } from './routes/_authenticated/$sellerId/settings/index'
24
- import { Route as ProductsIndexRouteImport } from './routes/_authenticated/$sellerId/products/index'
25
- import { Route as PayoutsIndexRouteImport } from './routes/_authenticated/$sellerId/payouts/index'
26
- import { Route as OrdersIndexRouteImport } from './routes/_authenticated/$sellerId/orders/index'
21
+ import { Route as ProfileRouteImport } from './routes/_authenticated/$sellerId/profile'
22
+ import { Route as SettingsRouteImport } from './routes/_authenticated/$sellerId/settings'
27
23
  import { Route as EarningsIndexRouteImport } from './routes/_authenticated/$sellerId/earnings/index'
28
- import { Route as SettingsTeamRouteImport } from './routes/_authenticated/$sellerId/settings/team'
29
- import { Route as SettingsStockLocationsRouteImport } from './routes/_authenticated/$sellerId/settings/stock-locations'
30
- import { Route as SettingsPoliciesRouteImport } from './routes/_authenticated/$sellerId/settings/policies'
31
- import { Route as SettingsPackageTypesRouteImport } from './routes/_authenticated/$sellerId/settings/package-types'
32
- import { Route as SettingsDeliveryMethodsRouteImport } from './routes/_authenticated/$sellerId/settings/delivery-methods'
33
- import { Route as ProductsNewRouteImport } from './routes/_authenticated/$sellerId/products/new'
34
- import { Route as ProductsProductIdRouteImport } from './routes/_authenticated/$sellerId/products/$productId'
35
- import { Route as PayoutsPayoutIdRouteImport } from './routes/_authenticated/$sellerId/payouts/$payoutId'
24
+ import { Route as OrdersIndexRouteImport } from './routes/_authenticated/$sellerId/orders/index'
36
25
  import { Route as OrdersOrderIdRouteImport } from './routes/_authenticated/$sellerId/orders/$orderId'
26
+ import { Route as PayoutsIndexRouteImport } from './routes/_authenticated/$sellerId/payouts/index'
27
+ import { Route as PayoutsPayoutIdRouteImport } from './routes/_authenticated/$sellerId/payouts/$payoutId'
28
+ import { Route as ProductsIndexRouteImport } from './routes/_authenticated/$sellerId/products/index'
29
+ import { Route as ProductsProductIdRouteImport } from './routes/_authenticated/$sellerId/products/$productId'
30
+ import { Route as ProductsNewRouteImport } from './routes/_authenticated/$sellerId/products/new'
31
+ import { Route as SettingsIndexRouteImport } from './routes/_authenticated/$sellerId/settings/index'
32
+ import { Route as SettingsDeliveryMethodsRouteImport } from './routes/_authenticated/$sellerId/settings/delivery-methods'
33
+ import { Route as SettingsPackageTypesRouteImport } from './routes/_authenticated/$sellerId/settings/package-types'
34
+ import { Route as SettingsPoliciesRouteImport } from './routes/_authenticated/$sellerId/settings/policies'
35
+ import { Route as SettingsStockLocationsRouteImport } from './routes/_authenticated/$sellerId/settings/stock-locations'
36
+ import { Route as SettingsTeamRouteImport } from './routes/_authenticated/$sellerId/settings/team'
37
37
 
38
- const resetPasswordRoute = resetPasswordRouteImport.update({
39
- id: '/reset-password',
40
- path: '/reset-password',
41
- getParentRoute: () => rootRouteImport,
42
- } as any)
43
- const loginRoute = loginRouteImport.update({
44
- id: '/login',
45
- path: '/login',
38
+ const authenticatedRoute = authenticatedRouteImport.update({
39
+ id: '/_authenticated',
46
40
  getParentRoute: () => rootRouteImport,
47
41
  } as any)
48
42
  const forgotPasswordRoute = forgotPasswordRouteImport.update({
@@ -50,8 +44,14 @@ const forgotPasswordRoute = forgotPasswordRouteImport.update({
50
44
  path: '/forgot-password',
51
45
  getParentRoute: () => rootRouteImport,
52
46
  } as any)
53
- const authenticatedRoute = authenticatedRouteImport.update({
54
- id: '/_authenticated',
47
+ const loginRoute = loginRouteImport.update({
48
+ id: '/login',
49
+ path: '/login',
50
+ getParentRoute: () => rootRouteImport,
51
+ } as any)
52
+ const resetPasswordRoute = resetPasswordRouteImport.update({
53
+ id: '/reset-password',
54
+ path: '/reset-password',
55
55
  getParentRoute: () => rootRouteImport,
56
56
  } as any)
57
57
  const authenticatedIndexRoute = authenticatedIndexRouteImport.update({
@@ -59,25 +59,25 @@ const authenticatedIndexRoute = authenticatedIndexRouteImport.update({
59
59
  path: '/',
60
60
  getParentRoute: () => authenticatedRoute,
61
61
  } as any)
62
+ const authenticatedSellerIdRoute = authenticatedSellerIdRouteImport.update({
63
+ id: '/$sellerId',
64
+ path: '/$sellerId',
65
+ getParentRoute: () => authenticatedRoute,
66
+ } as any)
62
67
  const acceptInvitationDotinvitationIdRoute =
63
68
  acceptInvitationDotinvitationIdRouteImport.update({
64
69
  id: '/accept-invitation/$invitationId',
65
70
  path: '/accept-invitation/$invitationId',
66
71
  getParentRoute: () => rootRouteImport,
67
72
  } as any)
68
- const authenticatedSellerIdRoute = authenticatedSellerIdRouteImport.update({
69
- id: '/$sellerId',
70
- path: '/$sellerId',
71
- getParentRoute: () => authenticatedRoute,
72
- } as any)
73
73
  const IndexRoute = IndexRouteImport.update({
74
74
  id: '/',
75
75
  path: '/',
76
76
  getParentRoute: () => authenticatedSellerIdRoute,
77
77
  } as any)
78
- const SettingsRoute = SettingsRouteImport.update({
79
- id: '/settings',
80
- path: '/settings',
78
+ const OnboardingRoute = OnboardingRouteImport.update({
79
+ id: '/onboarding',
80
+ path: '/onboarding',
81
81
  getParentRoute: () => authenticatedSellerIdRoute,
82
82
  } as any)
83
83
  const ProfileRoute = ProfileRouteImport.update({
@@ -85,19 +85,24 @@ const ProfileRoute = ProfileRouteImport.update({
85
85
  path: '/profile',
86
86
  getParentRoute: () => authenticatedSellerIdRoute,
87
87
  } as any)
88
- const OnboardingRoute = OnboardingRouteImport.update({
89
- id: '/onboarding',
90
- path: '/onboarding',
88
+ const SettingsRoute = SettingsRouteImport.update({
89
+ id: '/settings',
90
+ path: '/settings',
91
91
  getParentRoute: () => authenticatedSellerIdRoute,
92
92
  } as any)
93
- const SettingsIndexRoute = SettingsIndexRouteImport.update({
94
- id: '/',
95
- path: '/',
96
- getParentRoute: () => SettingsRoute,
93
+ const EarningsIndexRoute = EarningsIndexRouteImport.update({
94
+ id: '/earnings/',
95
+ path: '/earnings/',
96
+ getParentRoute: () => authenticatedSellerIdRoute,
97
97
  } as any)
98
- const ProductsIndexRoute = ProductsIndexRouteImport.update({
99
- id: '/products/',
100
- path: '/products/',
98
+ const OrdersIndexRoute = OrdersIndexRouteImport.update({
99
+ id: '/orders/',
100
+ path: '/orders/',
101
+ getParentRoute: () => authenticatedSellerIdRoute,
102
+ } as any)
103
+ const OrdersOrderIdRoute = OrdersOrderIdRouteImport.update({
104
+ id: '/orders/$orderId',
105
+ path: '/orders/$orderId',
101
106
  getParentRoute: () => authenticatedSellerIdRoute,
102
107
  } as any)
103
108
  const PayoutsIndexRoute = PayoutsIndexRouteImport.update({
@@ -105,29 +110,34 @@ const PayoutsIndexRoute = PayoutsIndexRouteImport.update({
105
110
  path: '/payouts/',
106
111
  getParentRoute: () => authenticatedSellerIdRoute,
107
112
  } as any)
108
- const OrdersIndexRoute = OrdersIndexRouteImport.update({
109
- id: '/orders/',
110
- path: '/orders/',
113
+ const PayoutsPayoutIdRoute = PayoutsPayoutIdRouteImport.update({
114
+ id: '/payouts/$payoutId',
115
+ path: '/payouts/$payoutId',
111
116
  getParentRoute: () => authenticatedSellerIdRoute,
112
117
  } as any)
113
- const EarningsIndexRoute = EarningsIndexRouteImport.update({
114
- id: '/earnings/',
115
- path: '/earnings/',
118
+ const ProductsIndexRoute = ProductsIndexRouteImport.update({
119
+ id: '/products/',
120
+ path: '/products/',
116
121
  getParentRoute: () => authenticatedSellerIdRoute,
117
122
  } as any)
118
- const SettingsTeamRoute = SettingsTeamRouteImport.update({
119
- id: '/team',
120
- path: '/team',
121
- getParentRoute: () => SettingsRoute,
123
+ const ProductsProductIdRoute = ProductsProductIdRouteImport.update({
124
+ id: '/products/$productId',
125
+ path: '/products/$productId',
126
+ getParentRoute: () => authenticatedSellerIdRoute,
122
127
  } as any)
123
- const SettingsStockLocationsRoute = SettingsStockLocationsRouteImport.update({
124
- id: '/stock-locations',
125
- path: '/stock-locations',
128
+ const ProductsNewRoute = ProductsNewRouteImport.update({
129
+ id: '/products/new',
130
+ path: '/products/new',
131
+ getParentRoute: () => authenticatedSellerIdRoute,
132
+ } as any)
133
+ const SettingsIndexRoute = SettingsIndexRouteImport.update({
134
+ id: '/',
135
+ path: '/',
126
136
  getParentRoute: () => SettingsRoute,
127
137
  } as any)
128
- const SettingsPoliciesRoute = SettingsPoliciesRouteImport.update({
129
- id: '/policies',
130
- path: '/policies',
138
+ const SettingsDeliveryMethodsRoute = SettingsDeliveryMethodsRouteImport.update({
139
+ id: '/delivery-methods',
140
+ path: '/delivery-methods',
131
141
  getParentRoute: () => SettingsRoute,
132
142
  } as any)
133
143
  const SettingsPackageTypesRoute = SettingsPackageTypesRouteImport.update({
@@ -135,30 +145,20 @@ const SettingsPackageTypesRoute = SettingsPackageTypesRouteImport.update({
135
145
  path: '/package-types',
136
146
  getParentRoute: () => SettingsRoute,
137
147
  } as any)
138
- const SettingsDeliveryMethodsRoute = SettingsDeliveryMethodsRouteImport.update({
139
- id: '/delivery-methods',
140
- path: '/delivery-methods',
148
+ const SettingsPoliciesRoute = SettingsPoliciesRouteImport.update({
149
+ id: '/policies',
150
+ path: '/policies',
141
151
  getParentRoute: () => SettingsRoute,
142
152
  } as any)
143
- const ProductsNewRoute = ProductsNewRouteImport.update({
144
- id: '/products/new',
145
- path: '/products/new',
146
- getParentRoute: () => authenticatedSellerIdRoute,
147
- } as any)
148
- const ProductsProductIdRoute = ProductsProductIdRouteImport.update({
149
- id: '/products/$productId',
150
- path: '/products/$productId',
151
- getParentRoute: () => authenticatedSellerIdRoute,
152
- } as any)
153
- const PayoutsPayoutIdRoute = PayoutsPayoutIdRouteImport.update({
154
- id: '/payouts/$payoutId',
155
- path: '/payouts/$payoutId',
156
- getParentRoute: () => authenticatedSellerIdRoute,
153
+ const SettingsStockLocationsRoute = SettingsStockLocationsRouteImport.update({
154
+ id: '/stock-locations',
155
+ path: '/stock-locations',
156
+ getParentRoute: () => SettingsRoute,
157
157
  } as any)
158
- const OrdersOrderIdRoute = OrdersOrderIdRouteImport.update({
159
- id: '/orders/$orderId',
160
- path: '/orders/$orderId',
161
- getParentRoute: () => authenticatedSellerIdRoute,
158
+ const SettingsTeamRoute = SettingsTeamRouteImport.update({
159
+ id: '/team',
160
+ path: '/team',
161
+ getParentRoute: () => SettingsRoute,
162
162
  } as any)
163
163
 
164
164
  export interface FileRoutesByFullPath {
@@ -329,18 +329,11 @@ export interface RootRouteChildren {
329
329
 
330
330
  declare module '@tanstack/react-router' {
331
331
  interface FileRoutesByPath {
332
- '/reset-password': {
333
- id: '/reset-password'
334
- path: '/reset-password'
335
- fullPath: '/reset-password'
336
- preLoaderRoute: typeof resetPasswordRouteImport
337
- parentRoute: typeof rootRouteImport
338
- }
339
- '/login': {
340
- id: '/login'
341
- path: '/login'
342
- fullPath: '/login'
343
- preLoaderRoute: typeof loginRouteImport
332
+ '/_authenticated': {
333
+ id: '/_authenticated'
334
+ path: ''
335
+ fullPath: '/'
336
+ preLoaderRoute: typeof authenticatedRouteImport
344
337
  parentRoute: typeof rootRouteImport
345
338
  }
346
339
  '/forgot-password': {
@@ -350,11 +343,18 @@ declare module '@tanstack/react-router' {
350
343
  preLoaderRoute: typeof forgotPasswordRouteImport
351
344
  parentRoute: typeof rootRouteImport
352
345
  }
353
- '/_authenticated': {
354
- id: '/_authenticated'
355
- path: ''
356
- fullPath: '/'
357
- preLoaderRoute: typeof authenticatedRouteImport
346
+ '/login': {
347
+ id: '/login'
348
+ path: '/login'
349
+ fullPath: '/login'
350
+ preLoaderRoute: typeof loginRouteImport
351
+ parentRoute: typeof rootRouteImport
352
+ }
353
+ '/reset-password': {
354
+ id: '/reset-password'
355
+ path: '/reset-password'
356
+ fullPath: '/reset-password'
357
+ preLoaderRoute: typeof resetPasswordRouteImport
358
358
  parentRoute: typeof rootRouteImport
359
359
  }
360
360
  '/_authenticated/': {
@@ -364,13 +364,6 @@ declare module '@tanstack/react-router' {
364
364
  preLoaderRoute: typeof authenticatedIndexRouteImport
365
365
  parentRoute: typeof authenticatedRoute
366
366
  }
367
- '/accept-invitation/$invitationId': {
368
- id: '/accept-invitation/$invitationId'
369
- path: '/accept-invitation/$invitationId'
370
- fullPath: '/accept-invitation/$invitationId'
371
- preLoaderRoute: typeof acceptInvitationDotinvitationIdRouteImport
372
- parentRoute: typeof rootRouteImport
373
- }
374
367
  '/_authenticated/$sellerId': {
375
368
  id: '/_authenticated/$sellerId'
376
369
  path: '/$sellerId'
@@ -378,6 +371,13 @@ declare module '@tanstack/react-router' {
378
371
  preLoaderRoute: typeof authenticatedSellerIdRouteImport
379
372
  parentRoute: typeof authenticatedRoute
380
373
  }
374
+ '/accept-invitation/$invitationId': {
375
+ id: '/accept-invitation/$invitationId'
376
+ path: '/accept-invitation/$invitationId'
377
+ fullPath: '/accept-invitation/$invitationId'
378
+ preLoaderRoute: typeof acceptInvitationDotinvitationIdRouteImport
379
+ parentRoute: typeof rootRouteImport
380
+ }
381
381
  '/_authenticated/$sellerId/': {
382
382
  id: '/_authenticated/$sellerId/'
383
383
  path: '/'
@@ -385,11 +385,11 @@ declare module '@tanstack/react-router' {
385
385
  preLoaderRoute: typeof IndexRouteImport
386
386
  parentRoute: typeof authenticatedSellerIdRoute
387
387
  }
388
- '/_authenticated/$sellerId/settings': {
389
- id: '/_authenticated/$sellerId/settings'
390
- path: '/settings'
391
- fullPath: '/$sellerId/settings'
392
- preLoaderRoute: typeof SettingsRouteImport
388
+ '/_authenticated/$sellerId/onboarding': {
389
+ id: '/_authenticated/$sellerId/onboarding'
390
+ path: '/onboarding'
391
+ fullPath: '/$sellerId/onboarding'
392
+ preLoaderRoute: typeof OnboardingRouteImport
393
393
  parentRoute: typeof authenticatedSellerIdRoute
394
394
  }
395
395
  '/_authenticated/$sellerId/profile': {
@@ -399,25 +399,32 @@ declare module '@tanstack/react-router' {
399
399
  preLoaderRoute: typeof ProfileRouteImport
400
400
  parentRoute: typeof authenticatedSellerIdRoute
401
401
  }
402
- '/_authenticated/$sellerId/onboarding': {
403
- id: '/_authenticated/$sellerId/onboarding'
404
- path: '/onboarding'
405
- fullPath: '/$sellerId/onboarding'
406
- preLoaderRoute: typeof OnboardingRouteImport
402
+ '/_authenticated/$sellerId/settings': {
403
+ id: '/_authenticated/$sellerId/settings'
404
+ path: '/settings'
405
+ fullPath: '/$sellerId/settings'
406
+ preLoaderRoute: typeof SettingsRouteImport
407
407
  parentRoute: typeof authenticatedSellerIdRoute
408
408
  }
409
- '/_authenticated/$sellerId/settings/': {
410
- id: '/_authenticated/$sellerId/settings/'
411
- path: '/'
412
- fullPath: '/$sellerId/settings/'
413
- preLoaderRoute: typeof SettingsIndexRouteImport
414
- parentRoute: typeof SettingsRoute
409
+ '/_authenticated/$sellerId/earnings/': {
410
+ id: '/_authenticated/$sellerId/earnings/'
411
+ path: '/earnings'
412
+ fullPath: '/$sellerId/earnings/'
413
+ preLoaderRoute: typeof EarningsIndexRouteImport
414
+ parentRoute: typeof authenticatedSellerIdRoute
415
415
  }
416
- '/_authenticated/$sellerId/products/': {
417
- id: '/_authenticated/$sellerId/products/'
418
- path: '/products'
419
- fullPath: '/$sellerId/products/'
420
- preLoaderRoute: typeof ProductsIndexRouteImport
416
+ '/_authenticated/$sellerId/orders/': {
417
+ id: '/_authenticated/$sellerId/orders/'
418
+ path: '/orders'
419
+ fullPath: '/$sellerId/orders/'
420
+ preLoaderRoute: typeof OrdersIndexRouteImport
421
+ parentRoute: typeof authenticatedSellerIdRoute
422
+ }
423
+ '/_authenticated/$sellerId/orders/$orderId': {
424
+ id: '/_authenticated/$sellerId/orders/$orderId'
425
+ path: '/orders/$orderId'
426
+ fullPath: '/$sellerId/orders/$orderId'
427
+ preLoaderRoute: typeof OrdersOrderIdRouteImport
421
428
  parentRoute: typeof authenticatedSellerIdRoute
422
429
  }
423
430
  '/_authenticated/$sellerId/payouts/': {
@@ -427,39 +434,46 @@ declare module '@tanstack/react-router' {
427
434
  preLoaderRoute: typeof PayoutsIndexRouteImport
428
435
  parentRoute: typeof authenticatedSellerIdRoute
429
436
  }
430
- '/_authenticated/$sellerId/orders/': {
431
- id: '/_authenticated/$sellerId/orders/'
432
- path: '/orders'
433
- fullPath: '/$sellerId/orders/'
434
- preLoaderRoute: typeof OrdersIndexRouteImport
437
+ '/_authenticated/$sellerId/payouts/$payoutId': {
438
+ id: '/_authenticated/$sellerId/payouts/$payoutId'
439
+ path: '/payouts/$payoutId'
440
+ fullPath: '/$sellerId/payouts/$payoutId'
441
+ preLoaderRoute: typeof PayoutsPayoutIdRouteImport
435
442
  parentRoute: typeof authenticatedSellerIdRoute
436
443
  }
437
- '/_authenticated/$sellerId/earnings/': {
438
- id: '/_authenticated/$sellerId/earnings/'
439
- path: '/earnings'
440
- fullPath: '/$sellerId/earnings/'
441
- preLoaderRoute: typeof EarningsIndexRouteImport
444
+ '/_authenticated/$sellerId/products/': {
445
+ id: '/_authenticated/$sellerId/products/'
446
+ path: '/products'
447
+ fullPath: '/$sellerId/products/'
448
+ preLoaderRoute: typeof ProductsIndexRouteImport
442
449
  parentRoute: typeof authenticatedSellerIdRoute
443
450
  }
444
- '/_authenticated/$sellerId/settings/team': {
445
- id: '/_authenticated/$sellerId/settings/team'
446
- path: '/team'
447
- fullPath: '/$sellerId/settings/team'
448
- preLoaderRoute: typeof SettingsTeamRouteImport
449
- parentRoute: typeof SettingsRoute
451
+ '/_authenticated/$sellerId/products/$productId': {
452
+ id: '/_authenticated/$sellerId/products/$productId'
453
+ path: '/products/$productId'
454
+ fullPath: '/$sellerId/products/$productId'
455
+ preLoaderRoute: typeof ProductsProductIdRouteImport
456
+ parentRoute: typeof authenticatedSellerIdRoute
450
457
  }
451
- '/_authenticated/$sellerId/settings/stock-locations': {
452
- id: '/_authenticated/$sellerId/settings/stock-locations'
453
- path: '/stock-locations'
454
- fullPath: '/$sellerId/settings/stock-locations'
455
- preLoaderRoute: typeof SettingsStockLocationsRouteImport
458
+ '/_authenticated/$sellerId/products/new': {
459
+ id: '/_authenticated/$sellerId/products/new'
460
+ path: '/products/new'
461
+ fullPath: '/$sellerId/products/new'
462
+ preLoaderRoute: typeof ProductsNewRouteImport
463
+ parentRoute: typeof authenticatedSellerIdRoute
464
+ }
465
+ '/_authenticated/$sellerId/settings/': {
466
+ id: '/_authenticated/$sellerId/settings/'
467
+ path: '/'
468
+ fullPath: '/$sellerId/settings/'
469
+ preLoaderRoute: typeof SettingsIndexRouteImport
456
470
  parentRoute: typeof SettingsRoute
457
471
  }
458
- '/_authenticated/$sellerId/settings/policies': {
459
- id: '/_authenticated/$sellerId/settings/policies'
460
- path: '/policies'
461
- fullPath: '/$sellerId/settings/policies'
462
- preLoaderRoute: typeof SettingsPoliciesRouteImport
472
+ '/_authenticated/$sellerId/settings/delivery-methods': {
473
+ id: '/_authenticated/$sellerId/settings/delivery-methods'
474
+ path: '/delivery-methods'
475
+ fullPath: '/$sellerId/settings/delivery-methods'
476
+ preLoaderRoute: typeof SettingsDeliveryMethodsRouteImport
463
477
  parentRoute: typeof SettingsRoute
464
478
  }
465
479
  '/_authenticated/$sellerId/settings/package-types': {
@@ -469,40 +483,26 @@ declare module '@tanstack/react-router' {
469
483
  preLoaderRoute: typeof SettingsPackageTypesRouteImport
470
484
  parentRoute: typeof SettingsRoute
471
485
  }
472
- '/_authenticated/$sellerId/settings/delivery-methods': {
473
- id: '/_authenticated/$sellerId/settings/delivery-methods'
474
- path: '/delivery-methods'
475
- fullPath: '/$sellerId/settings/delivery-methods'
476
- preLoaderRoute: typeof SettingsDeliveryMethodsRouteImport
486
+ '/_authenticated/$sellerId/settings/policies': {
487
+ id: '/_authenticated/$sellerId/settings/policies'
488
+ path: '/policies'
489
+ fullPath: '/$sellerId/settings/policies'
490
+ preLoaderRoute: typeof SettingsPoliciesRouteImport
477
491
  parentRoute: typeof SettingsRoute
478
492
  }
479
- '/_authenticated/$sellerId/products/new': {
480
- id: '/_authenticated/$sellerId/products/new'
481
- path: '/products/new'
482
- fullPath: '/$sellerId/products/new'
483
- preLoaderRoute: typeof ProductsNewRouteImport
484
- parentRoute: typeof authenticatedSellerIdRoute
485
- }
486
- '/_authenticated/$sellerId/products/$productId': {
487
- id: '/_authenticated/$sellerId/products/$productId'
488
- path: '/products/$productId'
489
- fullPath: '/$sellerId/products/$productId'
490
- preLoaderRoute: typeof ProductsProductIdRouteImport
491
- parentRoute: typeof authenticatedSellerIdRoute
492
- }
493
- '/_authenticated/$sellerId/payouts/$payoutId': {
494
- id: '/_authenticated/$sellerId/payouts/$payoutId'
495
- path: '/payouts/$payoutId'
496
- fullPath: '/$sellerId/payouts/$payoutId'
497
- preLoaderRoute: typeof PayoutsPayoutIdRouteImport
498
- parentRoute: typeof authenticatedSellerIdRoute
493
+ '/_authenticated/$sellerId/settings/stock-locations': {
494
+ id: '/_authenticated/$sellerId/settings/stock-locations'
495
+ path: '/stock-locations'
496
+ fullPath: '/$sellerId/settings/stock-locations'
497
+ preLoaderRoute: typeof SettingsStockLocationsRouteImport
498
+ parentRoute: typeof SettingsRoute
499
499
  }
500
- '/_authenticated/$sellerId/orders/$orderId': {
501
- id: '/_authenticated/$sellerId/orders/$orderId'
502
- path: '/orders/$orderId'
503
- fullPath: '/$sellerId/orders/$orderId'
504
- preLoaderRoute: typeof OrdersOrderIdRouteImport
505
- parentRoute: typeof authenticatedSellerIdRoute
500
+ '/_authenticated/$sellerId/settings/team': {
501
+ id: '/_authenticated/$sellerId/settings/team'
502
+ path: '/team'
503
+ fullPath: '/$sellerId/settings/team'
504
+ preLoaderRoute: typeof SettingsTeamRouteImport
505
+ parentRoute: typeof SettingsRoute
506
506
  }
507
507
  }
508
508
  }
package/src/vite/index.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { createRequire } from 'node:module'
2
1
  import path from 'node:path'
3
2
  import {
4
3
  type SpreeDashboardPluginOptions as CoreOptions,
5
4
  spreeDashboardPlugin as spreeDashboardCorePlugin,
6
5
  } from '@spree/dashboard-core/vite'
6
+ import { resolveShellRoutesDir } from '@spree/dashboard-core/vite/discover'
7
7
  import { TanStackRouterVite } from '@tanstack/router-plugin/vite'
8
8
  import { index, layout, physical, rootRoute, route } from '@tanstack/virtual-file-routes'
9
9
  import type { PluginOption } from 'vite'
@@ -55,12 +55,7 @@ export function spreeSellerDashboardPlugin(
55
55
  * authenticated branch whose pages all hang off a seller.
56
56
  */
57
57
  function sellerRouterPlugin(hostRoot: string, options: SpreeSellerDashboardPluginOptions) {
58
- const fromHost = createRequire(path.join(hostRoot, 'package.json'))
59
- // Resolve the shell's routes directory from wherever the host's
60
- // `@spree/seller-dashboard` lives (workspace symlink or npm install). The
61
- // `.` export maps to a file in src/, so `routes/` is its sibling.
62
- const shellEntry = fromHost.resolve('@spree/seller-dashboard')
63
- const shellRoutesDir = path.join(path.dirname(shellEntry), 'routes')
58
+ const shellRoutesDir = resolveShellRoutesDir(hostRoot, '@spree/seller-dashboard')
64
59
 
65
60
  const virtualRouteConfig = rootRoute('__root.tsx', [
66
61
  route('/login', 'login.tsx'),