@spree/seller-dashboard 1.0.0-rc.2 → 1.0.0-rc.4

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 (33) hide show
  1. package/package.json +4 -4
  2. package/src/api-client.ts +13 -8
  3. package/src/components/finance/balance-summary.tsx +31 -23
  4. package/src/components/orders/fulfillment-edit-dialog.tsx +10 -3
  5. package/src/components/orders/fulfillment-label-upload-dialog.tsx +3 -2
  6. package/src/components/orders/order-cards.tsx +31 -23
  7. package/src/components/orders/order-earnings-card.tsx +4 -2
  8. package/src/components/orders/order-payment-card.tsx +16 -8
  9. package/src/components/orders/post-sale-cards.tsx +7 -10
  10. package/src/components/orders/post-sale-create-dialogs.tsx +2 -3
  11. package/src/components/orders/returns-card.tsx +15 -8
  12. package/src/components/orders/shipping-label-row.tsx +15 -3
  13. package/src/components/seller-returns-location-card.tsx +4 -4
  14. package/src/components/seller-tax-identifiers-card.tsx +1 -1
  15. package/src/hooks/use-fulfillments.ts +3 -2
  16. package/src/hooks/use-store-timezone.ts +1 -1
  17. package/src/locales/ar.json +1 -0
  18. package/src/locales/de.json +1 -0
  19. package/src/locales/en.json +1 -0
  20. package/src/locales/fr.json +1 -0
  21. package/src/locales/pl.json +1 -0
  22. package/src/locales/zh-CN.json +1 -0
  23. package/src/pages/delivery-methods.tsx +34 -31
  24. package/src/pages/onboarding.tsx +2 -1
  25. package/src/pages/payout.tsx +14 -4
  26. package/src/pages/product.tsx +3 -3
  27. package/src/pages/team.tsx +2 -2
  28. package/src/tables/delivery-methods.tsx +1 -1
  29. package/src/tables/orders.tsx +2 -2
  30. package/src/tables/payouts.tsx +3 -3
  31. package/src/tables/policies.tsx +1 -1
  32. package/src/tables/products.tsx +3 -3
  33. package/src/tables/transfers.tsx +3 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spree/seller-dashboard",
3
- "version": "1.0.0-rc.2",
3
+ "version": "1.0.0-rc.4",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Spree marketplace seller panel — the seller-facing counterpart to the admin dashboard.",
@@ -49,9 +49,9 @@
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-rc.2",
53
- "@spree/dashboard-ui": "1.0.0-rc.2",
54
- "@spree/seller-sdk": "1.0.0-rc.1"
52
+ "@spree/dashboard-ui": "1.0.0-rc.3",
53
+ "@spree/seller-sdk": "1.0.0-rc.3",
54
+ "@spree/dashboard-core": "1.0.0-rc.3"
55
55
  },
56
56
  "peerDependencies": {
57
57
  "react": "^19.0.0",
package/src/api-client.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { setApiClient } from '@spree/dashboard-core'
1
+ import { runtimeListParams, setApiClient } from '@spree/dashboard-core'
2
2
  import type { SellerApiClient, SellerExportCreateParams } from '@spree/seller-sdk'
3
3
  import { createSellerClient } from '@spree/seller-sdk'
4
4
 
@@ -71,7 +71,7 @@ export function createSellerApiClient({
71
71
  // The shared address form reads countries through the registered client,
72
72
  // so it works in a panel that has no admin credential.
73
73
  listCountries: () => sellerClient().countries.list(),
74
- createDirectUpload: (params) => sellerClient().directUploads.create(params),
74
+ uploadFile: (file, options) => sellerClient().files.upload(file, options),
75
75
  // A file download is a bare fetch, so it does not go through the client
76
76
  // and picks up none of its headers. Without the seller header the Seller
77
77
  // API refuses the request before the action runs, so an export would
@@ -89,7 +89,7 @@ export function createSellerApiClient({
89
89
  // the marketplace and translations of catalog copy are its merchandising,
90
90
  // so the Seller API refuses both and the wizard offers neither.
91
91
  imports: {
92
- list: (params) => sellerClient().imports.list(params),
92
+ list: (params) => sellerClient().imports.list(runtimeListParams(params)),
93
93
  get: (id) => sellerClient().imports.get(id),
94
94
  create: (params) =>
95
95
  sellerClient().imports.create({
@@ -101,7 +101,10 @@ export function createSellerApiClient({
101
101
  completeMapping: (id, params) => sellerClient().imports.completeMapping(id, params),
102
102
  retryFailedRows: (id) => sellerClient().imports.retryFailedRows(id),
103
103
  delete: (id) => sellerClient().imports.delete(id),
104
- rows: { list: (importId, params) => sellerClient().imports.rows.list(importId, params) },
104
+ rows: {
105
+ list: (importId, params) =>
106
+ sellerClient().imports.rows.list(importId, runtimeListParams(params)),
107
+ },
105
108
  types: ['products'],
106
109
  // This panel's product list is keyed `seller-products`, so core's
107
110
  // default `products` invalidation would miss it and the list would sit
@@ -115,7 +118,7 @@ export function createSellerApiClient({
115
118
  // stock levels and is named on historical fulfillments, so the Seller API
116
119
  // does not offer it and the page hides the action accordingly.
117
120
  stockLocations: {
118
- list: (params) => sellerClient().stockLocations.list(params),
121
+ list: (params) => sellerClient().stockLocations.list(runtimeListParams(params)),
119
122
  get: (id) => sellerClient().stockLocations.get(id),
120
123
  create: (params) => sellerClient().stockLocations.create(params),
121
124
  update: (id, params) => sellerClient().stockLocations.update(id, params),
@@ -126,16 +129,18 @@ export function createSellerApiClient({
126
129
  // shared Categorization card offers just these two
127
130
  // (docs/plans/6.0-multi-vendor-marketplace.md, Decision 13).
128
131
  productTypes: {
129
- list: (params) => sellerClient().productTypes.list(params),
132
+ list: (params) => sellerClient().productTypes.list(runtimeListParams(params)),
130
133
  get: (id) => sellerClient().productTypes.get(id),
131
134
  },
132
- deliveryProfiles: { list: (params) => sellerClient().deliveryProfiles.list(params) },
135
+ deliveryProfiles: {
136
+ list: (params) => sellerClient().deliveryProfiles.list(runtimeListParams(params)),
137
+ },
133
138
  // What this seller packs into: their own boxes and cartons, plus the
134
139
  // marketplace's shared packaging, which the API lists read-only. Backs
135
140
  // both the packaging settings page and the variant editor's carton
136
141
  // picker (docs/plans/6.0-seller-package-types.md).
137
142
  packageTypes: {
138
- list: (params) => sellerClient().packageTypes.list(params),
143
+ list: (params) => sellerClient().packageTypes.list(runtimeListParams(params)),
139
144
  get: (id) => sellerClient().packageTypes.get(id),
140
145
  create: (params) => sellerClient().packageTypes.create(params),
141
146
  update: (id, params) => sellerClient().packageTypes.update(id, params),
@@ -1,3 +1,4 @@
1
+ import { formatMoney, useMoneyLocale } from '@spree/dashboard-core'
1
2
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@spree/dashboard-ui'
2
3
  import type { Balance } from '@spree/seller-sdk'
3
4
  import { useTranslation } from 'react-i18next'
@@ -16,6 +17,7 @@ import { useTranslation } from 'react-i18next'
16
17
  */
17
18
  export function BalanceSummary({ balances }: { balances: Balance[] }) {
18
19
  const { t } = useTranslation()
20
+ const locale = useMoneyLocale()
19
21
 
20
22
  if (balances.length === 0) {
21
23
  return (
@@ -30,31 +32,37 @@ export function BalanceSummary({ balances }: { balances: Balance[] }) {
30
32
 
31
33
  return (
32
34
  <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
33
- {balances.map((balance) => (
34
- <Card key={`${balance.currency}-${balance.settlement_currency}`}>
35
- <CardHeader>
36
- <CardTitle>
37
- {t('earnings.balance.owed', { currency: balance.settlement_currency })}
38
- </CardTitle>
39
- {balance.converted && (
40
- <CardDescription>
41
- {t('earnings.balance.converted', { currency: balance.currency })}
42
- </CardDescription>
43
- )}
44
- </CardHeader>
45
- <CardContent className="flex flex-col gap-3">
46
- <span className="text-2xl font-semibold tabular-nums">{balance.display_balance}</span>
47
- <dl className="flex flex-col gap-1 text-sm">
48
- <Figure label={t('earnings.balance.earned')} value={balance.display_earned} />
35
+ {balances.map((balance) => {
36
+ // Earned and pending are what the sales were worth; the rest is what
37
+ // the account holds, in the currency it settles in.
38
+ const sold = (amount: string) => formatMoney(amount, balance.currency, locale)
39
+ const held = (amount: string) => formatMoney(amount, balance.settlement_currency, locale)
40
+ return (
41
+ <Card key={`${balance.currency}-${balance.settlement_currency}`}>
42
+ <CardHeader>
43
+ <CardTitle>
44
+ {t('earnings.balance.owed', { currency: balance.settlement_currency })}
45
+ </CardTitle>
49
46
  {balance.converted && (
50
- <Figure label={t('earnings.balance.payable')} value={balance.display_payable} />
47
+ <CardDescription>
48
+ {t('earnings.balance.converted', { currency: balance.currency })}
49
+ </CardDescription>
51
50
  )}
52
- <Figure label={t('earnings.balance.paid')} value={balance.display_paid} />
53
- <Figure label={t('earnings.balance.pending')} value={balance.display_pending} />
54
- </dl>
55
- </CardContent>
56
- </Card>
57
- ))}
51
+ </CardHeader>
52
+ <CardContent className="flex flex-col gap-3">
53
+ <span className="text-2xl font-semibold tabular-nums">{held(balance.balance)}</span>
54
+ <dl className="flex flex-col gap-1 text-sm">
55
+ <Figure label={t('earnings.balance.earned')} value={sold(balance.earned)} />
56
+ {balance.converted && (
57
+ <Figure label={t('earnings.balance.payable')} value={held(balance.payable)} />
58
+ )}
59
+ <Figure label={t('earnings.balance.paid')} value={held(balance.paid)} />
60
+ <Figure label={t('earnings.balance.pending')} value={sold(balance.pending)} />
61
+ </dl>
62
+ </CardContent>
63
+ </Card>
64
+ )
65
+ })}
58
66
  </div>
59
67
  )
60
68
  }
@@ -1,5 +1,6 @@
1
+ import { formatMoney, useMoneyLocale } from '@spree/dashboard-core'
1
2
  import { FulfillmentEditDialog as SharedFulfillmentEditDialog } from '@spree/dashboard-ui'
2
- import { type Fulfillment, SpreeError } from '@spree/seller-sdk'
3
+ import { type Fulfillment, isZeroMoney, SpreeError } from '@spree/seller-sdk'
3
4
  import { useState } from 'react'
4
5
  import { useTranslation } from 'react-i18next'
5
6
  import { useFulfillmentActions } from '../../hooks/use-fulfillments'
@@ -27,7 +28,8 @@ export function FulfillmentEditDialog({
27
28
  const { t } = useTranslation()
28
29
  const { update } = useFulfillmentActions(orderId)
29
30
  const { data: locationsData } = useStockLocations(open)
30
- const { refetch: refetchOrder } = useOrder(orderId)
31
+ const { data: order, refetch: refetchOrder } = useOrder(orderId)
32
+ const locale = useMoneyLocale()
31
33
  const [errorMessage, setErrorMessage] = useState<string>()
32
34
 
33
35
  const originOptions = (locationsData?.data ?? []).map((location) => ({
@@ -38,7 +40,12 @@ export function FulfillmentEditDialog({
38
40
  const rateOptions = (fulfillment.delivery_rates ?? []).map((rate) => ({
39
41
  value: rate.id,
40
42
  label: `${rate.name} — ${
41
- Number.parseFloat(rate.cost) === 0 ? t('orders.fulfillments.free') : rate.display_cost
43
+ rate.unpriced
44
+ ? t('orders.fulfillments.quoted_after_review')
45
+ : isZeroMoney(rate.cost)
46
+ ? t('orders.fulfillments.free')
47
+ : // A rate is quoted in its order's currency.
48
+ formatMoney(rate.cost, order?.currency ?? '', locale) || rate.cost
42
49
  }`,
43
50
  }))
44
51
 
@@ -9,6 +9,7 @@ import {
9
9
  mapSpreeErrorsToForm,
10
10
  } from '@spree/dashboard-core'
11
11
  import { LabelUploadDialog } from '@spree/dashboard-ui'
12
+ import type { CurrencyCode } from '@spree/seller-sdk'
12
13
  import { useState } from 'react'
13
14
  import { useForm } from 'react-hook-form'
14
15
  import { useTranslation } from 'react-i18next'
@@ -25,7 +26,7 @@ export function FulfillmentLabelUploadDialog({
25
26
  }: {
26
27
  orderId: string
27
28
  fulfillmentId: string
28
- currency: string
29
+ currency: CurrencyCode
29
30
  open: boolean
30
31
  onOpenChange: (open: boolean) => void
31
32
  }) {
@@ -59,7 +60,7 @@ export function FulfillmentLabelUploadDialog({
59
60
  try {
60
61
  await uploadLabel.mutateAsync({
61
62
  fulfillmentId,
62
- file: file.signedId,
63
+ file_signed_id: file.signedId,
63
64
  tracking_number: values.tracking_number.trim(),
64
65
  carrier: values.carrier.trim() || undefined,
65
66
  cost: values.cost.trim() || undefined,
@@ -1,4 +1,10 @@
1
- import { AddressFormDialog, formatStoreDateTime } from '@spree/dashboard-core'
1
+ import {
2
+ AddressFormDialog,
3
+ formatMoney,
4
+ formatStoreDateTime,
5
+ isPositiveMoney,
6
+ useMoneyLocale,
7
+ } from '@spree/dashboard-core'
2
8
  import {
3
9
  AddressBlock,
4
10
  Button,
@@ -15,6 +21,7 @@ import {
15
21
  } from '@spree/dashboard-ui'
16
22
  import { EllipsisVerticalIcon, PencilIcon } from '@spree/dashboard-ui/icons'
17
23
  import type { Order, OrderAddressParams } from '@spree/seller-sdk'
24
+ import { isZeroMoney } from '@spree/seller-sdk'
18
25
  import { type ReactNode, useState } from 'react'
19
26
  import { useTranslation } from 'react-i18next'
20
27
  import { sellerClient } from '../../api-client'
@@ -69,10 +76,11 @@ function formatDate(iso: string | null | undefined, timezone: string) {
69
76
  */
70
77
  export function OrderSummaryCard({ order }: { order: Order }) {
71
78
  const { t } = useTranslation()
79
+ const locale = useMoneyLocale()
80
+ const money = (amount: string | null) => formatMoney(amount, order.currency, locale)
72
81
  const timezone = useStoreTimezone()
73
82
 
74
- const amount = (value: string | null | undefined) => Number.parseFloat(value ?? '0')
75
- const outstanding = amount(order.amount_due)
83
+ const outstanding = isPositiveMoney(order.amount_due)
76
84
  const placed = Boolean(order.completed_at)
77
85
 
78
86
  return (
@@ -118,43 +126,43 @@ export function OrderSummaryCard({ order }: { order: Order }) {
118
126
 
119
127
  <Separator />
120
128
 
121
- <SummaryRow label={t('orders.summary.subtotal')} value={order.display_item_total} />
129
+ <SummaryRow label={t('orders.summary.subtotal')} value={money(order.item_total)} />
122
130
 
123
- {amount(order.delivery_total) > 0 && (
124
- <SummaryRow label={t('orders.summary.shipping')} value={order.display_delivery_total} />
131
+ {isPositiveMoney(order.delivery_total) && (
132
+ <SummaryRow label={t('orders.summary.shipping')} value={money(order.delivery_total)} />
125
133
  )}
126
134
 
127
- {amount(order.discount_total) !== 0 && (
128
- <SummaryRow label={t('orders.summary.promotions')} value={order.display_discount_total} />
135
+ {!isZeroMoney(order.discount_total) && (
136
+ <SummaryRow label={t('orders.summary.promotions')} value={money(order.discount_total)} />
129
137
  )}
130
138
 
131
- {amount(order.adjustment_total) !== 0 && (
139
+ {!isZeroMoney(order.adjustment_total) && (
132
140
  <SummaryRow
133
141
  label={t('orders.summary.adjustments')}
134
- value={order.display_adjustment_total}
142
+ value={money(order.adjustment_total)}
135
143
  />
136
144
  )}
137
145
 
138
- {amount(order.included_tax_total) > 0 && (
146
+ {isPositiveMoney(order.included_tax_total) && (
139
147
  <SummaryRow
140
148
  label={t('orders.summary.tax_included')}
141
- value={order.display_included_tax_total}
149
+ value={money(order.included_tax_total)}
142
150
  />
143
151
  )}
144
152
 
145
153
  {/* Shown on a placed order even at zero, so "no tax was charged" reads
146
154
  as an answer rather than a missing row. */}
147
- {(amount(order.additional_tax_total) > 0 ||
148
- (placed && amount(order.included_tax_total) === 0)) && (
155
+ {(isPositiveMoney(order.additional_tax_total) ||
156
+ (placed && isZeroMoney(order.included_tax_total))) && (
149
157
  <SummaryRow
150
158
  label={t('orders.summary.tax_additional')}
151
- value={order.display_additional_tax_total}
159
+ value={money(order.additional_tax_total)}
152
160
  />
153
161
  )}
154
162
 
155
163
  <Separator />
156
164
 
157
- <SummaryRow label={t('orders.summary.total')} value={order.display_total} bold />
165
+ <SummaryRow label={t('orders.summary.total')} value={money(order.total)} bold />
158
166
 
159
167
  {/* What the marketplace charged the seller on this sale. Below the
160
168
  order total because it is not part of what the buyer paid — it is
@@ -178,19 +186,19 @@ export function OrderSummaryCard({ order }: { order: Order }) {
178
186
 
179
187
  <SummaryRow
180
188
  label={t('orders.summary.commission_fee')}
181
- value={order.display_commission_amount_total}
189
+ value={money(order.commission_amount_total)}
182
190
  />
183
191
 
184
- {amount(order.commission_tax_total) > 0 && (
192
+ {isPositiveMoney(order.commission_tax_total) && (
185
193
  <SummaryRow
186
194
  label={t('orders.summary.commission_tax')}
187
- value={order.display_commission_tax_total}
195
+ value={money(order.commission_tax_total)}
188
196
  />
189
197
  )}
190
198
 
191
199
  <SummaryRow
192
200
  label={t('orders.summary.commission_total')}
193
- value={order.display_commission_total}
201
+ value={money(order.commission_total)}
194
202
  bold
195
203
  />
196
204
  </>
@@ -200,14 +208,14 @@ export function OrderSummaryCard({ order }: { order: Order }) {
200
208
 
201
209
  <SummaryRow
202
210
  label={t('orders.summary.payment_total')}
203
- value={order.display_payment_total}
211
+ value={money(order.payment_total)}
204
212
  highlight
205
213
  />
206
214
  <SummaryRow
207
215
  label={t('orders.summary.outstanding_balance')}
208
- value={order.display_amount_due}
216
+ value={money(order.amount_due)}
209
217
  highlight
210
- danger={outstanding > 0}
218
+ danger={outstanding}
211
219
  />
212
220
  </div>
213
221
  </Card>
@@ -1,4 +1,4 @@
1
- import { formatStoreDateTime } from '@spree/dashboard-core'
1
+ import { formatStoreDateTime, Money } from '@spree/dashboard-core'
2
2
  import {
3
3
  Card,
4
4
  CardContent,
@@ -88,7 +88,9 @@ export function OrderEarningsCard({ order }: { order: Order }) {
88
88
  </span>
89
89
  )}
90
90
  </TableCell>
91
- <TableCell className="text-right tabular-nums">{transfer.display_amount}</TableCell>
91
+ <TableCell className="text-right tabular-nums">
92
+ <Money amount={transfer.amount} currency={transfer.currency} />
93
+ </TableCell>
92
94
  </TableRow>
93
95
  ))}
94
96
  </TableBody>
@@ -1,3 +1,4 @@
1
+ import { formatMoney, useMoneyLocale } from '@spree/dashboard-core'
1
2
  import {
2
3
  Card,
3
4
  CardAction,
@@ -31,7 +32,10 @@ import { ReadRow } from '../read-row'
31
32
  */
32
33
  export function OrderPaymentCard({ order }: { order: Order }) {
33
34
  const { t } = useTranslation()
35
+ const locale = useMoneyLocale()
34
36
  const splits = order.payment_splits ?? []
37
+ const money = (amount: string | null | undefined, currency = order.currency) =>
38
+ formatMoney(amount, currency, locale)
35
39
 
36
40
  return (
37
41
  <Card>
@@ -53,10 +57,8 @@ export function OrderPaymentCard({ order }: { order: Order }) {
53
57
  </CardHeader>
54
58
 
55
59
  <CardContent className="flex flex-col gap-3">
56
- <ReadRow label={t('orders.summary.payment_total')}>{order.display_payment_total}</ReadRow>
57
- <ReadRow label={t('orders.summary.outstanding_balance')}>
58
- {order.display_amount_due}
59
- </ReadRow>
60
+ <ReadRow label={t('orders.summary.payment_total')}>{money(order.payment_total)}</ReadRow>
61
+ <ReadRow label={t('orders.summary.outstanding_balance')}>{money(order.amount_due)}</ReadRow>
60
62
  </CardContent>
61
63
 
62
64
  {splits.length > 0 && (
@@ -77,11 +79,17 @@ export function OrderPaymentCard({ order }: { order: Order }) {
77
79
  <TableBody>
78
80
  {splits.map((split) => (
79
81
  <TableRow key={split.id}>
80
- <TableCell className="tabular-nums">{split.display_authorized_amount}</TableCell>
81
- <TableCell className="tabular-nums">{split.display_captured_amount}</TableCell>
82
- <TableCell className="tabular-nums">{split.display_refunded_amount}</TableCell>
82
+ <TableCell className="tabular-nums">
83
+ {money(split.authorized_amount, split.currency)}
84
+ </TableCell>
85
+ <TableCell className="tabular-nums">
86
+ {money(split.captured_amount, split.currency)}
87
+ </TableCell>
88
+ <TableCell className="tabular-nums">
89
+ {money(split.refunded_amount, split.currency)}
90
+ </TableCell>
83
91
  <TableCell className="text-right tabular-nums">
84
- {split.display_refundable_amount}
92
+ {money(split.refundable_amount, split.currency)}
85
93
  </TableCell>
86
94
  </TableRow>
87
95
  ))}
@@ -1,4 +1,4 @@
1
- import { currencyParts } from '@spree/dashboard-core'
1
+ import { currencyParts, formatMoney, isPositiveMoney, useMoneyLocale } from '@spree/dashboard-core'
2
2
  import {
3
3
  Button,
4
4
  Card,
@@ -23,6 +23,7 @@ import {
23
23
  ShieldAlertIcon,
24
24
  } from '@spree/dashboard-ui/icons'
25
25
  import type { Claim, Order } from '@spree/seller-sdk'
26
+ import { sumMoney } from '@spree/seller-sdk'
26
27
  import i18n from 'i18next'
27
28
  import { useState } from 'react'
28
29
  import { useTranslation } from 'react-i18next'
@@ -47,6 +48,7 @@ const CLAIM_ACTIONABLE = ['open', 'approved']
47
48
  */
48
49
  export function ExchangesCard({ order }: { order: Order }) {
49
50
  const { t } = useTranslation()
51
+ const locale = useMoneyLocale()
50
52
  const confirm = useConfirm()
51
53
  const { data } = useOrderExchanges(order.id)
52
54
  const { approve, receive, fulfill, cancel } = useExchangeActions(order.id)
@@ -113,7 +115,7 @@ export function ExchangesCard({ order }: { order: Order }) {
113
115
  // A dearer replacement leaves the customer owing the
114
116
  // difference, which fulfilling puts on the order.
115
117
  if (
116
- Number(exchange.price_difference) > 0 &&
118
+ isPositiveMoney(exchange.price_difference) &&
117
119
  !(await confirm({
118
120
  message: t('orders.post_sale.exchanges.balance_due_confirm'),
119
121
  confirmLabel: t('orders.post_sale.exchanges.fulfill'),
@@ -152,7 +154,7 @@ export function ExchangesCard({ order }: { order: Order }) {
152
154
  ))}
153
155
  <p className="text-muted-foreground text-sm">
154
156
  {t('orders.post_sale.exchanges.price_difference', {
155
- amount: exchange.display_price_difference,
157
+ amount: formatMoney(exchange.price_difference, order.currency, locale),
156
158
  })}
157
159
  </p>
158
160
  </CardContent>
@@ -322,14 +324,9 @@ function ResolveClaimDialog({
322
324
  // A claim opened without per-item amounts has a refund_total of zero, and
323
325
  // the workflow refuses to refund nothing — offer what the customer paid for
324
326
  // the claimed items instead, which is also the ceiling it enforces.
325
- const recorded = Number(claim.refund_total)
326
- const paid = lines.reduce((sum, line) => sum + Number(line.paid_amount ?? 0), 0)
327
+ const paid = sumMoney(lines.map((line) => line.paid_amount))
327
328
  const defaultAmount =
328
- Number.isFinite(recorded) && recorded > 0
329
- ? claim.refund_total
330
- : paid > 0
331
- ? paid.toFixed(2)
332
- : claim.refund_total
329
+ isPositiveMoney(claim.refund_total) || !isPositiveMoney(paid) ? claim.refund_total : paid
333
330
 
334
331
  return (
335
332
  <ClaimResolveDialog
@@ -1,4 +1,4 @@
1
- import { currencyParts } from '@spree/dashboard-core'
1
+ import { currencyParts, paidForUnits } from '@spree/dashboard-core'
2
2
  import {
3
3
  Field,
4
4
  FieldLabel,
@@ -137,8 +137,7 @@ export function CreateClaimDialog({
137
137
  id: item.id,
138
138
  label: unitLabel(item),
139
139
  quantity: item.quantity,
140
- discountedAmount: item.discounted_amount,
141
- additionalTaxTotal: item.additional_tax_total,
140
+ paidFor: (quantity: number) => paidForUnits(item, quantity, order.currency),
142
141
  }))}
143
142
  currencySymbol={currencySymbol}
144
143
  reasonField={<ClaimReasonField value={reasonId} onChange={setReasonId} />}
@@ -1,4 +1,11 @@
1
- import { currencyParts } from '@spree/dashboard-core'
1
+ import {
2
+ currencyParts,
3
+ formatMoney,
4
+ isPositiveMoney,
5
+ returnOwesNothing,
6
+ returnRefundSummary,
7
+ useMoneyLocale,
8
+ } from '@spree/dashboard-core'
2
9
  import {
3
10
  Badge,
4
11
  Button,
@@ -14,8 +21,6 @@ import {
14
21
  DropdownMenuTrigger,
15
22
  ReturnReceiveDialog,
16
23
  ReturnRefundDialog,
17
- returnOwesNothing,
18
- returnRefundSummary,
19
24
  StatusBadge,
20
25
  useConfirm,
21
26
  } from '@spree/dashboard-ui'
@@ -138,7 +143,7 @@ export function ReturnsCard({ order }: { order: Order }) {
138
143
  {returnRecord.return_line_items?.map((line) => (
139
144
  <ReturnLineRow key={line.id} line={line} />
140
145
  ))}
141
- <RefundSummary returnRecord={returnRecord} />
146
+ <RefundSummary returnRecord={returnRecord} currency={order.currency} />
142
147
  </CardContent>
143
148
  </Card>
144
149
  ))
@@ -165,9 +170,10 @@ export function ReturnsCard({ order }: { order: Order }) {
165
170
  )
166
171
  }
167
172
 
168
- function RefundSummary({ returnRecord }: { returnRecord: Return }) {
173
+ function RefundSummary({ returnRecord, currency }: { returnRecord: Return; currency: string }) {
169
174
  const { t } = useTranslation()
170
- const summary = returnRefundSummary(returnRecord)
175
+ const locale = useMoneyLocale()
176
+ const summary = returnRefundSummary(returnRecord, currency, locale)
171
177
 
172
178
  return (
173
179
  <p className="text-muted-foreground text-sm">
@@ -260,9 +266,10 @@ function RefundDialog({
260
266
  return (
261
267
  <ReturnRefundDialog
262
268
  refundableTotal={returnRecord.refundable_total}
269
+ owesNothing={returnOwesNothing(returnRecord.refundable_total)}
263
270
  refundTaxTotal={
264
- Number(returnRecord.refund_tax_total) > 0
265
- ? returnRecord.display_refund_tax_total
271
+ isPositiveMoney(returnRecord.refund_tax_total)
272
+ ? formatMoney(returnRecord.refund_tax_total, order.currency, i18n.language)
266
273
  : undefined
267
274
  }
268
275
  currencySymbol={currencySymbol}
@@ -1,4 +1,10 @@
1
- import { downloadFromApi, getApiClient, useAuth } from '@spree/dashboard-core'
1
+ import {
2
+ downloadFromApi,
3
+ formatMoney,
4
+ getApiClient,
5
+ useAuth,
6
+ useMoneyLocale,
7
+ } from '@spree/dashboard-core'
2
8
  import {
3
9
  Badge,
4
10
  Button,
@@ -19,6 +25,7 @@ import {
19
25
  import type { ShippingLabel } from '@spree/seller-sdk'
20
26
  import { useTranslation } from 'react-i18next'
21
27
  import { useFulfillmentActions } from '../../hooks/use-fulfillments'
28
+ import { useOrder } from '../../hooks/use-order'
22
29
 
23
30
  /**
24
31
  * The label bound to a parcel: what it cost and how to print it.
@@ -41,6 +48,11 @@ export function ShippingLabelRow({
41
48
  const { token } = useAuth()
42
49
  const confirm = useConfirm()
43
50
  const { deleteLabel } = useFulfillmentActions(orderId)
51
+ const locale = useMoneyLocale()
52
+ const { data: order } = useOrder(orderId)
53
+ // A label with no currency of its own was bought in the order's.
54
+ const labelCurrency = label.currency ?? order?.currency
55
+ const cost = labelCurrency ? formatMoney(label.cost, labelCurrency, locale) : ''
44
56
 
45
57
  const deletable = label.source === 'uploaded'
46
58
 
@@ -83,9 +95,9 @@ export function ShippingLabelRow({
83
95
  {label.source === 'uploaded' && (
84
96
  <Badge variant="secondary">{t('orders.fulfillments.label_uploaded')}</Badge>
85
97
  )}
86
- {label.display_cost && (
98
+ {cost && (
87
99
  <span className="text-muted-foreground">
88
- {t('orders.fulfillments.label_cost', { amount: label.display_cost })}
100
+ {t('orders.fulfillments.label_cost', { amount: cost })}
89
101
  </span>
90
102
  )}
91
103
  </div>
@@ -17,7 +17,7 @@ import {
17
17
  toastManager,
18
18
  } from '@spree/dashboard-ui'
19
19
  import { PencilIcon, PlusIcon } from '@spree/dashboard-ui/icons'
20
- import type { StockLocationParams } from '@spree/seller-sdk'
20
+ import type { StockLocationUpdateParams } from '@spree/seller-sdk'
21
21
  import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
22
22
  import { useParams } from '@tanstack/react-router'
23
23
  import { useState } from 'react'
@@ -46,11 +46,11 @@ export function SellerReturnsLocationCard({ headless = false }: { headless?: boo
46
46
  // more locations than one page holds would otherwise see one address while
47
47
  // the server sent their returns to another, with nothing on screen saying
48
48
  // so — and they would edit the wrong one.
49
- const activeParams = { active_true: true, limit: 1 }
49
+ const activeParams = { active_eq: true, limit: 1 }
50
50
  const { data: takesReturns, isError: returnsFailed } = useQuery({
51
51
  queryKey: ['seller', sellerId, 'stock-locations', 'returns'],
52
52
  queryFn: () =>
53
- sellerClient().stockLocations.list({ ...activeParams, returns_enabled_true: true }),
53
+ sellerClient().stockLocations.list({ ...activeParams, returns_enabled_eq: true }),
54
54
  })
55
55
  const hasReturnsLocation = (takesReturns?.data.length ?? 0) > 0
56
56
  const { data: anyActive, isError: anyFailed } = useQuery({
@@ -67,7 +67,7 @@ export function SellerReturnsLocationCard({ headless = false }: { headless?: boo
67
67
  const title = t('profile.returns_address')
68
68
 
69
69
  const save = useMutation({
70
- mutationFn: (params: StockLocationParams) => {
70
+ mutationFn: (params: StockLocationUpdateParams) => {
71
71
  if (!location) throw new Error(t('common.error'))
72
72
  return sellerClient().stockLocations.update(location.id, params)
73
73
  },
@@ -37,7 +37,7 @@ export function SellerTaxIdentifiersCard() {
37
37
  onSuccess: refresh,
38
38
  })
39
39
  const remove = useMutation({
40
- mutationFn: (id: string) => sellerClient().taxIdentifiers.remove(id),
40
+ mutationFn: (id: string) => sellerClient().taxIdentifiers.delete(id),
41
41
  onSuccess: refresh,
42
42
  })
43
43
  const validate = useMutation({
@@ -1,5 +1,6 @@
1
1
  import { useResourceKeyBuilder } from '@spree/dashboard-core'
2
2
  import { toastManager } from '@spree/dashboard-ui'
3
+ import type { CurrencyCode } from '@spree/seller-sdk'
3
4
  import { useMutation, useQueryClient } from '@tanstack/react-query'
4
5
  import { sellerClient } from '../api-client'
5
6
 
@@ -125,14 +126,14 @@ export function useFulfillmentActions(orderId: string) {
125
126
  ...params
126
127
  }: {
127
128
  fulfillmentId: string
128
- file: string
129
+ file_signed_id: string
129
130
  // Required: a label with no tracking number is refused server-side,
130
131
  // since nothing could then follow the parcel it prints.
131
132
  tracking_number: string
132
133
  carrier?: string
133
134
  service?: string
134
135
  cost?: string
135
- currency?: string
136
+ currency?: CurrencyCode
136
137
  }) => sellerClient().orders.fulfillments.labels.create(orderId, fulfillmentId, params),
137
138
  )
138
139
 
@@ -22,5 +22,5 @@ export function useStoreTimezone(): string {
22
22
  enabled: Boolean(sellerId),
23
23
  })
24
24
 
25
- return profile?.preferred_timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone ?? 'UTC'
25
+ return profile?.timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone ?? 'UTC'
26
26
  }
@@ -474,6 +474,7 @@
474
474
  "packing_slip_title": "قائمة التعبئة — الطلب {{number}}",
475
475
  "print_label": "طباعة الملصق",
476
476
  "print_packing_slip": "طباعة قائمة التعبئة",
477
+ "quoted_after_review": "يُسعَّر بعد المراجعة",
477
478
  "ship_to": "الشحن إلى",
478
479
  "ships_from_label": "يُشحن من",
479
480
  "split": "تقسيم",
@@ -470,6 +470,7 @@
470
470
  "packing_slip_title": "Lieferschein — Bestellung {{number}}",
471
471
  "print_label": "Etikett drucken",
472
472
  "print_packing_slip": "Lieferschein drucken",
473
+ "quoted_after_review": "Preis nach Prüfung",
473
474
  "ship_to": "Lieferung an",
474
475
  "ships_from_label": "Versand ab",
475
476
  "split": "Teilen",
@@ -470,6 +470,7 @@
470
470
  "packing_slip_title": "Packing slip — order {{number}}",
471
471
  "print_label": "Print label",
472
472
  "print_packing_slip": "Print packing slip",
473
+ "quoted_after_review": "Quoted after review",
473
474
  "ship_to": "Ship to",
474
475
  "ships_from_label": "Ships from",
475
476
  "split": "Split",
@@ -470,6 +470,7 @@
470
470
  "packing_slip_title": "Bon de livraison — commande {{number}}",
471
471
  "print_label": "Imprimer l’étiquette",
472
472
  "print_packing_slip": "Imprimer le bon de livraison",
473
+ "quoted_after_review": "Prix après vérification",
473
474
  "ship_to": "Livrer à",
474
475
  "ships_from_label": "Expédié depuis",
475
476
  "split": "Scinder",
@@ -472,6 +472,7 @@
472
472
  "packing_slip_title": "List przewozowy — zamówienie {{number}}",
473
473
  "print_label": "Drukuj etykietę",
474
474
  "print_packing_slip": "Drukuj list przewozowy",
475
+ "quoted_after_review": "Wycena po weryfikacji",
475
476
  "ship_to": "Wysyłka do",
476
477
  "ships_from_label": "Wysyłka z",
477
478
  "split": "Podziel",
@@ -469,6 +469,7 @@
469
469
  "packing_slip_title": "装箱单 — 订单 {{number}}",
470
470
  "print_label": "打印面单",
471
471
  "print_packing_slip": "打印装箱单",
472
+ "quoted_after_review": "审核后报价",
472
473
  "ship_to": "收货地址",
473
474
  "ships_from_label": "发货地",
474
475
  "split": "拆分",
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  defaultPreferences,
3
+ filterPreferenceSchema,
3
4
  PageHeader,
4
5
  PreferencesForm,
5
6
  type ResourceSearch,
@@ -37,7 +38,7 @@ import {
37
38
  useRowClickBridge,
38
39
  } from '@spree/dashboard-ui'
39
40
  import { PlusIcon, TrashIcon } from '@spree/dashboard-ui/icons'
40
- import type { DeliveryMethod, DeliveryPreferenceField } from '@spree/seller-sdk'
41
+ import type { DeliveryMethod, PreferenceSchema } from '@spree/seller-sdk'
41
42
  import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
42
43
  import { useParams } from '@tanstack/react-router'
43
44
  import { useEffect, useState } from 'react'
@@ -166,8 +167,7 @@ interface DeliveryMethodFormValues {
166
167
  storefront_visible: boolean
167
168
  delivery_profile_id: string
168
169
  delivery_zone_id: string
169
- calculator_type: string
170
- calculator_preferences: Record<string, unknown>
170
+ calculator: { type: string; preferences: Record<string, unknown> }
171
171
  rules: RuleDraft[]
172
172
  }
173
173
 
@@ -177,8 +177,7 @@ const DEFAULTS: DeliveryMethodFormValues = {
177
177
  storefront_visible: true,
178
178
  delivery_profile_id: '',
179
179
  delivery_zone_id: '',
180
- calculator_type: '',
181
- calculator_preferences: {},
180
+ calculator: { type: '', preferences: {} },
182
181
  rules: [],
183
182
  }
184
183
 
@@ -231,7 +230,7 @@ function DeliveryMethodSheet({
231
230
 
232
231
  const form = useForm<DeliveryMethodFormValues>({ defaultValues: DEFAULTS })
233
232
  const profileId = form.watch('delivery_profile_id')
234
- const calculatorType = form.watch('calculator_type')
233
+ const calculatorType = form.watch('calculator.type')
235
234
  const rules = form.watch('rules')
236
235
 
237
236
  // A zone only means something under its profile, so the picker follows
@@ -252,8 +251,10 @@ function DeliveryMethodSheet({
252
251
  storefront_visible: method.storefront_visible,
253
252
  delivery_profile_id: method.delivery_profile_id ?? '',
254
253
  delivery_zone_id: method.delivery_zone_id ?? '',
255
- calculator_type: method.calculator_type ?? '',
256
- calculator_preferences: method.calculator_preferences ?? {},
254
+ calculator: {
255
+ type: method.calculator?.type ?? '',
256
+ preferences: method.calculator?.preferences ?? {},
257
+ },
257
258
  rules: (method.rules ?? []).map((rule) => ({
258
259
  id: rule.id,
259
260
  type: rule.type,
@@ -274,19 +275,18 @@ function DeliveryMethodSheet({
274
275
  // the picker blank would show an empty Pricing box while the server
275
276
  // quietly created the method free.
276
277
  useEffect(() => {
277
- if (methodId || form.getValues('calculator_type')) return
278
+ if (methodId || form.getValues('calculator.type')) return
278
279
  const flatRate = calculators?.data.find((calculator) => calculator.type === 'flat_rate')
279
280
  if (!flatRate) return
280
281
 
281
- form.setValue('calculator_type', flatRate.type)
282
- form.setValue(
283
- 'calculator_preferences',
284
- defaultPreferences(renderableSchema(flatRate.preference_schema)),
285
- )
282
+ form.setValue('calculator', {
283
+ type: flatRate.type,
284
+ preferences: defaultPreferences(renderableSchema(flatRate.schema)),
285
+ })
286
286
  }, [calculators, methodId, form])
287
287
 
288
288
  const calculatorSchema = renderableSchema(
289
- calculators?.data.find((calculator) => calculator.type === calculatorType)?.preference_schema,
289
+ calculators?.data.find((calculator) => calculator.type === calculatorType)?.schema,
290
290
  )
291
291
 
292
292
  const save = useMutation({
@@ -297,8 +297,10 @@ function DeliveryMethodSheet({
297
297
  storefront_visible: values.storefront_visible,
298
298
  delivery_profile_id: values.delivery_profile_id || undefined,
299
299
  delivery_zone_id: values.delivery_zone_id || null,
300
- ...(values.calculator_type ? { calculator_type: values.calculator_type } : {}),
301
- calculator_preferences: values.calculator_preferences,
300
+ calculator: {
301
+ ...(values.calculator.type ? { type: values.calculator.type } : {}),
302
+ preferences: values.calculator.preferences,
303
+ },
302
304
  // The array replaces the whole set: a rule dropped here is deleted,
303
305
  // which is why every surviving row is re-sent with its id.
304
306
  rules: values.rules.map((rule) => ({
@@ -329,9 +331,7 @@ function DeliveryMethodSheet({
329
331
  const { errors } = form.formState
330
332
 
331
333
  function addRule(type: string) {
332
- const schema = renderableSchema(
333
- ruleTypes?.data.find((rule) => rule.type === type)?.preference_schema,
334
- )
334
+ const schema = renderableSchema(ruleTypes?.data.find((rule) => rule.type === type)?.schema)
335
335
  form.setValue('rules', [...rules, { type, preferences: defaultPreferences(schema) }])
336
336
  }
337
337
 
@@ -482,7 +482,7 @@ function DeliveryMethodSheet({
482
482
  </FieldLabel>
483
483
  <Controller
484
484
  control={form.control}
485
- name="calculator_type"
485
+ name="calculator.type"
486
486
  render={({ field }) => (
487
487
  <Select
488
488
  value={field.value}
@@ -490,9 +490,9 @@ function DeliveryMethodSheet({
490
490
  field.onChange(next)
491
491
  const schema = renderableSchema(
492
492
  calculators?.data.find((calculator) => calculator.type === next)
493
- ?.preference_schema,
493
+ ?.schema,
494
494
  )
495
- form.setValue('calculator_preferences', defaultPreferences(schema))
495
+ form.setValue('calculator.preferences', defaultPreferences(schema))
496
496
  }}
497
497
  >
498
498
  <SelectTrigger id="delivery-method-calculator">
@@ -521,7 +521,7 @@ function DeliveryMethodSheet({
521
521
 
522
522
  <Controller
523
523
  control={form.control}
524
- name="calculator_preferences"
524
+ name="calculator.preferences"
525
525
  render={({ field }) => (
526
526
  <PreferencesForm
527
527
  schema={calculatorSchema}
@@ -582,7 +582,7 @@ function DeliveryMethodSheet({
582
582
  </div>
583
583
 
584
584
  <PreferencesForm
585
- schema={renderableSchema(definition?.preference_schema)}
585
+ schema={renderableSchema(definition?.schema)}
586
586
  currencyOptions={currencyOptions}
587
587
  values={rule.preferences}
588
588
  onChange={(next) =>
@@ -632,16 +632,19 @@ function DeliveryMethodSheet({
632
632
  }
633
633
 
634
634
  /**
635
- * The fields of a preference schema the shared form can render.
635
+ * The part of a preference schema the shared form can render.
636
636
  *
637
- * Drops `hash`-typed preferences — today the flat-rate calculator's
637
+ * Drops keyed maps — today the flat-rate calculator's
638
638
  * per-currency `amounts` map, which the operator edits through a dedicated
639
639
  * multi-currency control and the generic form would show as the hash itself
640
640
  * in a text box. A seller quotes in the store's currency, so `amount` and
641
641
  * `currency` are the whole question.
642
642
  */
643
- function renderableSchema(
644
- schema: DeliveryPreferenceField[] | undefined,
645
- ): DeliveryPreferenceField[] {
646
- return (schema ?? []).filter((field) => field.type !== 'hash')
643
+ function renderableSchema(schema: PreferenceSchema | undefined): PreferenceSchema | undefined {
644
+ if (!schema) return undefined
645
+
646
+ return filterPreferenceSchema(
647
+ schema,
648
+ (_key, property) => typeof property.additionalProperties !== 'object',
649
+ )
647
650
  }
@@ -244,7 +244,7 @@ function RequirementAction({ requirement }: { requirement: RequirementStatus })
244
244
  mutationFn: () =>
245
245
  sellerClient().requirementSubmissions.create(requirement.id, {
246
246
  note: note || undefined,
247
- file: file.signedId || undefined,
247
+ file_signed_id: file.signedId || undefined,
248
248
  }),
249
249
  onSuccess: () => {
250
250
  setNote('')
@@ -349,6 +349,7 @@ function RequirementAction({ requirement }: { requirement: RequirementStatus })
349
349
  value={file}
350
350
  onChange={setFile}
351
351
  accept={requirement.accepted_content_types?.join(',')}
352
+ private
352
353
  label={t('onboarding.document_label')}
353
354
  help={t('onboarding.document_help')}
354
355
  />
@@ -1,4 +1,11 @@
1
- import { formatStoreDateTime, PageHeader, Slot } from '@spree/dashboard-core'
1
+ import {
2
+ formatMoney,
3
+ formatStoreDateTime,
4
+ Money,
5
+ PageHeader,
6
+ Slot,
7
+ useMoneyLocale,
8
+ } from '@spree/dashboard-core'
2
9
  import {
3
10
  Card,
4
11
  CardHeader,
@@ -32,6 +39,7 @@ import { useStoreTimezone } from '../hooks/use-store-timezone'
32
39
  */
33
40
  export function PayoutPage() {
34
41
  const { t } = useTranslation()
42
+ const locale = useMoneyLocale()
35
43
  const { sellerId, payoutId } = useParams({ from: '/_authenticated/$sellerId/payouts/$payoutId' })
36
44
 
37
45
  const timezone = useStoreTimezone()
@@ -54,7 +62,7 @@ export function PayoutPage() {
54
62
  <ResourceLayout
55
63
  header={
56
64
  <PageHeader
57
- title={payout.display_amount}
65
+ title={formatMoney(payout.amount, payout.currency, locale)}
58
66
  subtitle={formatStoreDateTime(payout.created_at, timezone)}
59
67
  backTo="payouts"
60
68
  badges={
@@ -121,7 +129,7 @@ export function PayoutPage() {
121
129
  {t(`earnings.kinds.${transfer.kind}`, { defaultValue: transfer.kind })}
122
130
  </TableCell>
123
131
  <TableCell className="text-right tabular-nums">
124
- {transfer.display_amount}
132
+ <Money amount={transfer.amount} currency={transfer.currency} />
125
133
  </TableCell>
126
134
  </TableRow>
127
135
  ))}
@@ -140,7 +148,9 @@ export function PayoutPage() {
140
148
  <CardTitle>{t('payouts.detail.settlement')}</CardTitle>
141
149
  </CardHeader>
142
150
  <div className="flex flex-col gap-3 px-6 pb-6">
143
- <ReadRow label={t('payouts.columns.amount')}>{payout.display_amount}</ReadRow>
151
+ <ReadRow label={t('payouts.columns.amount')}>
152
+ <Money amount={payout.amount} currency={payout.currency} />
153
+ </ReadRow>
144
154
  <ReadRow label={t('payouts.columns.reference')}>{payout.reference}</ReadRow>
145
155
  <ReadRow label={t('payouts.detail.period')}>
146
156
  {payout.period_start && payout.period_end
@@ -19,7 +19,7 @@ import {
19
19
  variantToWirePayload,
20
20
  } from '@spree/dashboard-core'
21
21
  import { FormActions, ResourceLayout, StatusBadge, toastManager } from '@spree/dashboard-ui'
22
- import type { ProductParams } from '@spree/seller-sdk'
22
+ import type { ProductCreateParams } from '@spree/seller-sdk'
23
23
  import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
24
24
  import { useNavigate, useParams } from '@tanstack/react-router'
25
25
  import { useEffect } from 'react'
@@ -119,7 +119,7 @@ export function ProductPage({ mode }: { mode: 'new' | 'edit' }) {
119
119
  values: ProductFormValues
120
120
  extensionValues: Record<string, unknown>
121
121
  }) => {
122
- const payload: ProductParams = {
122
+ const payload: ProductCreateParams = {
123
123
  ...extensionValues,
124
124
  name: values.name,
125
125
  description: values.description,
@@ -150,7 +150,7 @@ export function ProductPage({ mode }: { mode: 'new' | 'edit' }) {
150
150
  })),
151
151
  variants: (values.variants ?? []).map((variant, index) =>
152
152
  variantToWirePayload(variant, index),
153
- ) as ProductParams['variants'],
153
+ ) as ProductCreateParams['variants'],
154
154
  }
155
155
 
156
156
  return productId
@@ -175,7 +175,7 @@ function MemberRow({ member, lastOne }: { member: TeamMember; lastOne: boolean }
175
175
  const confirm = useConfirm()
176
176
 
177
177
  const remove = useMutation({
178
- mutationFn: (id: string) => sellerClient().team.remove(id),
178
+ mutationFn: (id: string) => sellerClient().team.delete(id),
179
179
  onSuccess: () => {
180
180
  toastManager.add({ type: 'success', title: t('team.messages.removed') })
181
181
  void queryClient.invalidateQueries({ queryKey: ['seller', sellerId, 'team'] })
@@ -302,7 +302,7 @@ function InvitationRow({ invitation }: { invitation: Invitation }) {
302
302
  })
303
303
 
304
304
  const revoke = useMutation({
305
- mutationFn: (id: string) => sellerClient().invitations.revoke(id),
305
+ mutationFn: (id: string) => sellerClient().invitations.delete(id),
306
306
  onSuccess: () => {
307
307
  toastManager.add({ type: 'success', title: t('team.messages.revoked') })
308
308
  void invalidate()
@@ -6,7 +6,7 @@ import i18n from 'i18next'
6
6
 
7
7
  defineTable<DeliveryMethod>('seller-delivery-methods', {
8
8
  title: i18n.t('delivery_methods.title'),
9
- searchParam: 'name_cont',
9
+ searchParam: 'search',
10
10
  searchPlaceholder: i18n.t('delivery_methods.search_placeholder'),
11
11
  defaultSort: { field: 'name', direction: 'asc' },
12
12
  emptyIcon: <TruckIcon className="size-8 text-muted-foreground" />,
@@ -1,4 +1,4 @@
1
- import { defineTable, formatStoreDateTime } from '@spree/dashboard-core'
1
+ import { defineTable, formatMoney, formatStoreDateTime } from '@spree/dashboard-core'
2
2
  import { ResourceNameCell, StatusBadge } from '@spree/dashboard-ui'
3
3
  import { PackageIcon } from '@spree/dashboard-ui/icons'
4
4
  import type { Order } from '@spree/seller-sdk'
@@ -110,7 +110,7 @@ defineTable<Order>('seller-orders', {
110
110
  key: 'total',
111
111
  label: i18n.t('orders.columns.total'),
112
112
  default: true,
113
- render: (order) => order.display_total as string,
113
+ render: (order) => formatMoney(order.total, order.currency, i18n.language),
114
114
  },
115
115
  ],
116
116
  })
@@ -1,4 +1,4 @@
1
- import { defineTable, formatStoreDateTime } from '@spree/dashboard-core'
1
+ import { defineTable, formatMoney, formatStoreDateTime } from '@spree/dashboard-core'
2
2
  import { ResourceNameCell, StatusBadge } from '@spree/dashboard-ui'
3
3
  import { HandCoinsIcon } from '@spree/dashboard-ui/icons'
4
4
  import type { Payout } from '@spree/seller-sdk'
@@ -22,7 +22,7 @@ const STATUSES = ['pending', 'processing', 'completed', 'failed', 'unresolved']
22
22
 
23
23
  defineTable<Payout>('seller-payouts', {
24
24
  title: i18n.t('payouts.title'),
25
- searchParam: 'reference_cont',
25
+ searchParam: 'search',
26
26
  searchPlaceholder: i18n.t('payouts.search_placeholder'),
27
27
  defaultSort: { field: 'created_at', direction: 'desc' },
28
28
  emptyIcon: <HandCoinsIcon className="size-8 text-muted-foreground" />,
@@ -81,7 +81,7 @@ defineTable<Payout>('seller-payouts', {
81
81
  sortable: true,
82
82
  default: true,
83
83
  className: 'text-right tabular-nums',
84
- render: (payout) => payout.display_amount,
84
+ render: (payout) => formatMoney(payout.amount, payout.currency, i18n.language),
85
85
  },
86
86
  ],
87
87
  })
@@ -6,7 +6,7 @@ import i18n from 'i18next'
6
6
 
7
7
  defineTable<Policy>('seller-policies', {
8
8
  title: i18n.t('policies.title'),
9
- searchParam: 'name_cont',
9
+ searchParam: 'search',
10
10
  searchPlaceholder: i18n.t('policies.search_placeholder'),
11
11
  defaultSort: { field: 'name', direction: 'asc' },
12
12
  emptyIcon: <ScrollTextIcon className="size-8 text-muted-foreground" />,
@@ -1,4 +1,4 @@
1
- import { defineTable } from '@spree/dashboard-core'
1
+ import { defineTable, formatPrice } from '@spree/dashboard-core'
2
2
  import { ResourceNameCell, StatusBadge, Thumbnail } from '@spree/dashboard-ui'
3
3
  import { PackageIcon } from '@spree/dashboard-ui/icons'
4
4
  import type { Product } from '@spree/seller-sdk'
@@ -9,7 +9,7 @@ const PRODUCT_STATUSES = ['active', 'draft', 'proposed', 'rejected', 'archived']
9
9
 
10
10
  defineTable<Product>('seller-products', {
11
11
  title: i18n.t('products.title'),
12
- searchParam: 'name_cont',
12
+ searchParam: 'search',
13
13
  searchPlaceholder: i18n.t('products.search_placeholder'),
14
14
  defaultSort: { field: 'created_at', direction: 'desc' },
15
15
  emptyIcon: <PackageIcon className="size-8 text-muted-foreground" />,
@@ -50,7 +50,7 @@ defineTable<Product>('seller-products', {
50
50
  key: 'price',
51
51
  label: i18n.t('products.columns.price'),
52
52
  default: true,
53
- render: (product) => product.price?.display_amount ?? '—',
53
+ render: (product) => formatPrice(product.price ?? null),
54
54
  },
55
55
  ],
56
56
  })
@@ -1,4 +1,4 @@
1
- import { defineTable, formatStoreDateTime, useTenantId } from '@spree/dashboard-core'
1
+ import { defineTable, formatMoney, formatStoreDateTime, useTenantId } from '@spree/dashboard-core'
2
2
  import { StatusBadge } from '@spree/dashboard-ui'
3
3
  import { BanknoteIcon } from '@spree/dashboard-ui/icons'
4
4
  import type { Transfer } from '@spree/seller-sdk'
@@ -51,7 +51,7 @@ defineTable<Transfer>('seller-transfers', {
51
51
  // Without this the toolbar sends `name_cont`, which the ledger does not
52
52
  // whitelist — Ransack drops an unknown condition, so the box would look
53
53
  // like it worked and return everything.
54
- searchParam: 'reference_cont',
54
+ searchParam: 'search',
55
55
  searchPlaceholder: i18n.t('earnings.search_placeholder'),
56
56
  defaultSort: { field: 'created_at', direction: 'desc' },
57
57
  emptyIcon: <BanknoteIcon className="size-8 text-muted-foreground" />,
@@ -112,7 +112,7 @@ defineTable<Transfer>('seller-transfers', {
112
112
  sortable: true,
113
113
  default: true,
114
114
  className: 'text-right tabular-nums',
115
- render: (transfer) => transfer.display_amount,
115
+ render: (transfer) => formatMoney(transfer.amount, transfer.currency, i18n.language),
116
116
  },
117
117
  ],
118
118
  })