@spree/seller-dashboard 1.0.0-rc.5 → 1.0.0-rc.6

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spree/seller-dashboard",
3
- "version": "1.0.0-rc.5",
3
+ "version": "1.0.0-rc.6",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Spree marketplace seller panel — the seller-facing counterpart to the admin dashboard.",
@@ -44,7 +44,8 @@
44
44
  "@tanstack/virtual-file-routes": "^1.162.0",
45
45
  "lucide-react": "^1.58.0",
46
46
  "zod": "^4.6.7",
47
- "@spree/seller-sdk": "1.0.0-rc.4"
47
+ "@spree/seller-sdk": "1.0.0-rc.5",
48
+ "@spree/core": "^0.2.0"
48
49
  },
49
50
  "peerDependencies": {
50
51
  "@tanstack/react-query": "^5.104.3",
@@ -54,9 +55,9 @@
54
55
  "react-dom": "^19.3.1",
55
56
  "react-hook-form": "^7.90.0",
56
57
  "react-i18next": "^17.0.20",
57
- "@spree/admin-sdk": "^1.0.0-rc.4",
58
- "@spree/dashboard-core": "^1.0.0-rc.4",
59
- "@spree/dashboard-ui": "^1.0.0-rc.4"
58
+ "@spree/admin-sdk": "^1.0.0-rc.5",
59
+ "@spree/dashboard-core": "^1.0.0-rc.5",
60
+ "@spree/dashboard-ui": "^1.0.0-rc.5"
60
61
  },
61
62
  "devDependencies": {
62
63
  "@biomejs/biome": "^2.4.14",
@@ -74,9 +75,9 @@
74
75
  "tsup": "^9.0.0",
75
76
  "typescript": "~7.0.2",
76
77
  "vite": "^8.3.4",
77
- "@spree/admin-sdk": "1.0.0-rc.4",
78
- "@spree/dashboard-core": "1.0.0-rc.4",
79
- "@spree/dashboard-ui": "1.0.0-rc.4"
78
+ "@spree/dashboard-core": "1.0.0-rc.5",
79
+ "@spree/dashboard-ui": "1.0.0-rc.5",
80
+ "@spree/admin-sdk": "1.0.0-rc.5"
80
81
  },
81
82
  "scripts": {
82
83
  "build": "pnpm generate:routes && tsup",
@@ -1,6 +1,7 @@
1
+ import { isZeroMoney } from '@spree/core'
1
2
  import { formatMoney, useMoneyLocale } from '@spree/dashboard-core'
2
3
  import { FulfillmentEditDialog as SharedFulfillmentEditDialog } from '@spree/dashboard-ui'
3
- import { type Fulfillment, isZeroMoney, SpreeError } from '@spree/seller-sdk'
4
+ import { type Fulfillment, SpreeError } from '@spree/seller-sdk'
4
5
  import { useState } from 'react'
5
6
  import { useTranslation } from 'react-i18next'
6
7
  import { useFulfillmentActions } from '../../hooks/use-fulfillments'
@@ -1,4 +1,5 @@
1
1
  import { zodResolver } from '@hookform/resolvers/zod'
2
+ import type { CurrencyCode } from '@spree/core'
2
3
  import {
3
4
  EMPTY_FILE_UPLOAD_VALUE,
4
5
  FileUploadField,
@@ -9,7 +10,6 @@ import {
9
10
  mapSpreeErrorsToForm,
10
11
  } from '@spree/dashboard-core'
11
12
  import { LabelUploadDialog } from '@spree/dashboard-ui'
12
- import type { CurrencyCode } from '@spree/seller-sdk'
13
13
  import { useState } from 'react'
14
14
  import { useForm } from 'react-hook-form'
15
15
  import { useTranslation } from 'react-i18next'
@@ -1,3 +1,4 @@
1
+ import { FULFILLMENT_STATUSES, subsetOf } from '@spree/core'
1
2
  import { fulfillmentItemRows } from '@spree/dashboard-core'
2
3
  import {
3
4
  Badge,
@@ -44,8 +45,8 @@ import { ShippingLabelRow } from './shipping-label-row'
44
45
  // A parcel that has not gone out is the only one still to ship or cancel.
45
46
  // Reading these off "not unfulfilled" once badged a canceled parcel as a
46
47
  // success.
47
- const CAN_SHIP = ['unfulfilled']
48
- const CAN_CANCEL = ['unfulfilled']
48
+ const CAN_SHIP = subsetOf(FULFILLMENT_STATUSES, ['unfulfilled'])
49
+ const CAN_CANCEL = subsetOf(FULFILLMENT_STATUSES, ['unfulfilled'])
49
50
 
50
51
  /** Every parcel owed on this order, and what the seller can do with each. */
51
52
  export function FulfillmentsCard({ order }: { order: Order }) {
@@ -1,3 +1,4 @@
1
+ import { isZeroMoney } from '@spree/core'
1
2
  import {
2
3
  AddressFormDialog,
3
4
  formatMoney,
@@ -21,7 +22,6 @@ import {
21
22
  } from '@spree/dashboard-ui'
22
23
  import { EllipsisVerticalIcon, PencilIcon } from '@spree/dashboard-ui/icons'
23
24
  import type { Order, OrderAddressParams } from '@spree/seller-sdk'
24
- import { isZeroMoney } from '@spree/seller-sdk'
25
25
  import { type ReactNode, useState } from 'react'
26
26
  import { useTranslation } from 'react-i18next'
27
27
  import { sellerClient } from '../../api-client'
@@ -1,3 +1,4 @@
1
+ import { CLAIM_STATUSES, EXCHANGE_STATUSES, subsetOf, sumMoney } from '@spree/core'
1
2
  import { currencyParts, formatMoney, isPositiveMoney, useMoneyLocale } from '@spree/dashboard-core'
2
3
  import {
3
4
  Button,
@@ -23,7 +24,6 @@ import {
23
24
  ShieldAlertIcon,
24
25
  } from '@spree/dashboard-ui/icons'
25
26
  import type { Claim, Order } from '@spree/seller-sdk'
26
- import { sumMoney } from '@spree/seller-sdk'
27
27
  import i18n from 'i18next'
28
28
  import { useState } from 'react'
29
29
  import { useTranslation } from 'react-i18next'
@@ -36,8 +36,8 @@ import {
36
36
  import { lineLabel } from './line-label'
37
37
  import { CreateClaimDialog } from './post-sale-create-dialogs'
38
38
 
39
- const EXCHANGE_ACTIONABLE = ['requested', 'approved', 'received']
40
- const CLAIM_ACTIONABLE = ['open', 'approved']
39
+ const EXCHANGE_ACTIONABLE = subsetOf(EXCHANGE_STATUSES, ['requested', 'approved', 'received'])
40
+ const CLAIM_ACTIONABLE = subsetOf(CLAIM_STATUSES, ['open', 'approved'])
41
41
 
42
42
  /**
43
43
  * Goods swapped for different ones.
@@ -1,3 +1,4 @@
1
+ import { RETURN_STATUSES, subsetOf } from '@spree/core'
1
2
  import {
2
3
  currencyParts,
3
4
  formatMoney,
@@ -35,7 +36,7 @@ import { CreateReturnDialog, fulfilledUnits } from './post-sale-create-dialogs'
35
36
 
36
37
  // Refunded and canceled returns are finished; offering a menu on them would
37
38
  // open onto nothing.
38
- const ACTIONABLE = ['requested', 'approved', 'received']
39
+ const ACTIONABLE = subsetOf(RETURN_STATUSES, ['requested', 'approved', 'received'])
39
40
 
40
41
  /**
41
42
  * Goods coming back on this order.
@@ -1,6 +1,6 @@
1
+ import type { CurrencyCode } from '@spree/core'
1
2
  import { useResourceKeyBuilder } from '@spree/dashboard-core'
2
3
  import { toastManager } from '@spree/dashboard-ui'
3
- import type { CurrencyCode } from '@spree/seller-sdk'
4
4
  import { useMutation, useQueryClient } from '@tanstack/react-query'
5
5
  import { sellerClient } from '../api-client'
6
6
 
@@ -1,3 +1,4 @@
1
+ import type { ClaimResolution } from '@spree/core'
1
2
  import { useResourceKey, useResourceKeyBuilder } from '@spree/dashboard-core'
2
3
  import { toastManager } from '@spree/dashboard-ui'
3
4
  import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
@@ -182,7 +183,7 @@ export function useClaimActions(orderId: string) {
182
183
  replacementLineItemIds,
183
184
  }: {
184
185
  claimId: string
185
- resolution: 'refund' | 'replacement' | 'refund_and_replacement'
186
+ resolution: ClaimResolution
186
187
  refundMethod?: 'original_payment' | 'store_credit'
187
188
  amount?: string
188
189
  replacementLineItemIds?: string[]
@@ -2,6 +2,7 @@ import { zodResolver } from '@hookform/resolvers/zod'
2
2
  import {
3
3
  CategorizationCard,
4
4
  extensionFormValues,
5
+ extensionRowValues,
5
6
  extensionSubmitValues,
6
7
  GeneralCard,
7
8
  InventoryCard,
@@ -17,6 +18,7 @@ import {
17
18
  Slot,
18
19
  VariantsCard,
19
20
  variantToWirePayload,
21
+ withExtensionRowValues,
20
22
  } from '@spree/dashboard-core'
21
23
  import { FormActions, ResourceLayout, StatusBadge, toastManager } from '@spree/dashboard-ui'
22
24
  import type { ProductCreateParams } from '@spree/seller-sdk'
@@ -106,7 +108,7 @@ export function ProductPage({ mode }: { mode: 'new' | 'edit' }) {
106
108
  if (form.formState.isDirty) return
107
109
 
108
110
  form.reset({
109
- ...productToFormValues(product as PanelProduct, product.media),
111
+ ...productToFormValues(product as PanelProduct, product.media, 'seller.product'),
110
112
  ...extensionFormValues('seller.product', product),
111
113
  })
112
114
  }, [product, form])
@@ -137,6 +139,7 @@ export function ProductPage({ mode }: { mode: 'new' | 'edit' }) {
137
139
  // cards offer both — an allow-list that omitted them let a seller
138
140
  // set a crop or paste a video URL and watch it vanish on save.
139
141
  media: (values.media ?? []).map((item) => ({
142
+ ...extensionRowValues('seller.product.media', item),
140
143
  id: item.id,
141
144
  signed_id: item.signed_id ?? undefined,
142
145
  poster_signed_id: item.poster_signed_id ?? undefined,
@@ -149,7 +152,7 @@ export function ProductPage({ mode }: { mode: 'new' | 'edit' }) {
149
152
  variant_ids: item.variant_ids,
150
153
  })),
151
154
  variants: (values.variants ?? []).map((variant, index) =>
152
- variantToWirePayload(variant, index),
155
+ variantToWirePayload(variant, index, 'seller.product'),
153
156
  ) as ProductCreateParams['variants'],
154
157
  }
155
158
 
@@ -181,7 +184,7 @@ export function ProductPage({ mode }: { mode: 'new' | 'edit' }) {
181
184
  // Raw form state, not `values`: the Zod parse strips keys the schema
182
185
  // doesn't know, which is every field a slot widget registered.
183
186
  await save.mutateAsync({
184
- values,
187
+ values: withExtensionRowValues('seller.product', values, form.getValues()),
185
188
  extensionValues: extensionSubmitValues('seller.product', form),
186
189
  })
187
190
  } catch (err) {
@@ -236,8 +239,13 @@ export function ProductPage({ mode }: { mode: 'new' | 'edit' }) {
236
239
  main={
237
240
  <>
238
241
  <GeneralCard form={form} />
239
- <VariantsCard form={form} />
240
- <MediaCard productId={productId} variants={product?.variants ?? []} form={form} />
242
+ <VariantsCard form={form} formKey="seller.product" />
243
+ <MediaCard
244
+ productId={productId}
245
+ variants={product?.variants ?? []}
246
+ form={form}
247
+ formKey="seller.product"
248
+ />
241
249
  <PricesCard
242
250
  form={form}
243
251
  productName={product?.name ?? ''}
@@ -245,6 +253,7 @@ export function ProductPage({ mode }: { mode: 'new' | 'edit' }) {
245
253
  defaultCurrency={profile?.default_currency}
246
254
  />
247
255
  <InventoryCard form={form} />
256
+ <Slot name="seller.product.form_main" context={{ product, mode }} />
248
257
  </>
249
258
  }
250
259
  sidebar={
@@ -1,3 +1,4 @@
1
+ import { PRODUCT_STATUSES, subsetOf } from '@spree/core'
1
2
  import type {
2
3
  BulkAction,
3
4
  BulkActionFormProps,
@@ -40,7 +41,7 @@ import '../tables/products'
40
41
  * approves it, so the only moves offered here are the two a seller makes
41
42
  * alone (docs/plans/6.0-seller-product-submission.md).
42
43
  */
43
- const BULK_STATUSES = ['draft', 'archived'] as const
44
+ const BULK_STATUSES = subsetOf(PRODUCT_STATUSES, ['draft', 'archived'])
44
45
 
45
46
  type BulkStatus = (typeof BULK_STATUSES)[number]
46
47
  type StatusFormValues = { status: BulkStatus }
@@ -17,5 +17,5 @@ export const Route = createFileRoute('/_authenticated/$sellerId/settings/package
17
17
  function PackageTypesRoute() {
18
18
  const search = Route.useSearch() as z.infer<typeof packageTypesSearchSchema>
19
19
 
20
- return <PackageTypesPage search={search} />
20
+ return <PackageTypesPage search={search} formKey="seller.package_type" />
21
21
  }
@@ -18,5 +18,5 @@ export const Route = createFileRoute('/_authenticated/$sellerId/settings/stock-l
18
18
  function StockLocationsRoute() {
19
19
  const search = Route.useSearch() as z.infer<typeof stockLocationsSearchSchema>
20
20
 
21
- return <StockLocationsPage search={search} />
21
+ return <StockLocationsPage search={search} formKey="seller.stock_location" />
22
22
  }
@@ -1,4 +1,10 @@
1
- import { defineTable, formatMoney, formatStoreDateTime } from '@spree/dashboard-core'
1
+ import { ORDER_PAYMENT_STATUSES } from '@spree/core'
2
+ import {
3
+ defineTable,
4
+ FILTERABLE_ORDER_FULFILLMENT_STATUSES,
5
+ formatMoney,
6
+ formatStoreDateTime,
7
+ } from '@spree/dashboard-core'
2
8
  import { ResourceNameCell, StatusBadge } from '@spree/dashboard-ui'
3
9
  import { PackageIcon } from '@spree/dashboard-ui/icons'
4
10
  import type { Order } from '@spree/seller-sdk'
@@ -17,26 +23,6 @@ function OrderDate({ iso }: { iso: string }) {
17
23
  return <>{formatStoreDateTime(iso, timezone)}</>
18
24
  }
19
25
 
20
- /** Mirrors the operator dashboard's order status vocabularies. */
21
- const FULFILLMENT_STATUSES = [
22
- 'unfulfilled',
23
- 'backorder',
24
- 'partial',
25
- 'fulfilled',
26
- 'delivered',
27
- 'canceled',
28
- ] as const
29
- const PAYMENT_STATUSES = [
30
- 'none',
31
- 'authorized',
32
- 'partially_paid',
33
- 'paid',
34
- 'partially_refunded',
35
- 'refunded',
36
- 'overcharged',
37
- 'voided',
38
- ] as const
39
-
40
26
  defineTable<Order>('seller-orders', {
41
27
  title: i18n.t('orders.title'),
42
28
  searchParam: 'number_cont',
@@ -69,7 +55,7 @@ defineTable<Order>('seller-orders', {
69
55
  label: i18n.t('orders.columns.fulfillment'),
70
56
  filterable: true,
71
57
  filterType: 'enum',
72
- filterOptions: FULFILLMENT_STATUSES.map((status) => ({
58
+ filterOptions: FILTERABLE_ORDER_FULFILLMENT_STATUSES.map((status) => ({
73
59
  value: status,
74
60
  label: i18n.t(`orders.fulfillment_statuses.${status}`),
75
61
  })),
@@ -90,7 +76,7 @@ defineTable<Order>('seller-orders', {
90
76
  label: i18n.t('orders.columns.payment'),
91
77
  filterable: true,
92
78
  filterType: 'enum',
93
- filterOptions: PAYMENT_STATUSES.map((status) => ({
79
+ filterOptions: ORDER_PAYMENT_STATUSES.map((status) => ({
94
80
  value: status,
95
81
  label: i18n.t(`orders.payment_statuses.${status}`),
96
82
  })),
@@ -1,3 +1,4 @@
1
+ import { SELLER_PAYOUT_STATUSES } from '@spree/core'
1
2
  import { defineTable, formatMoney, formatStoreDateTime } from '@spree/dashboard-core'
2
3
  import { ResourceNameCell, StatusBadge } from '@spree/dashboard-ui'
3
4
  import { HandCoinsIcon } from '@spree/dashboard-ui/icons'
@@ -18,8 +19,6 @@ function LedgerDate({ iso }: { iso: string }) {
18
19
  return <>{formatStoreDateTime(iso, timezone)}</>
19
20
  }
20
21
 
21
- const STATUSES = ['pending', 'processing', 'completed', 'failed', 'unresolved'] as const
22
-
23
22
  defineTable<Payout>('seller-payouts', {
24
23
  title: i18n.t('payouts.title'),
25
24
  searchParam: 'search',
@@ -50,7 +49,7 @@ defineTable<Payout>('seller-payouts', {
50
49
  sortable: true,
51
50
  filterable: true,
52
51
  filterType: 'enum',
53
- filterOptions: STATUSES.map((status) => ({
52
+ filterOptions: SELLER_PAYOUT_STATUSES.map((status) => ({
54
53
  value: status,
55
54
  label: i18n.t(`earnings.statuses.${status}`),
56
55
  })),
@@ -1,12 +1,10 @@
1
+ import { PRODUCT_STATUSES } from '@spree/core'
1
2
  import { defineTable, formatPrice } from '@spree/dashboard-core'
2
3
  import { ResourceNameCell, StatusBadge, Thumbnail } from '@spree/dashboard-ui'
3
4
  import { PackageIcon } from '@spree/dashboard-ui/icons'
4
5
  import type { Product } from '@spree/seller-sdk'
5
6
  import i18n from 'i18next'
6
7
 
7
- /** Mirrors the operator dashboard's product statuses. */
8
- const PRODUCT_STATUSES = ['active', 'draft', 'proposed', 'rejected', 'archived'] as const
9
-
10
8
  defineTable<Product>('seller-products', {
11
9
  title: i18n.t('products.title'),
12
10
  searchParam: 'search',
@@ -1,3 +1,4 @@
1
+ import { SELLER_TRANSFER_KINDS, SELLER_TRANSFER_STATUSES } from '@spree/core'
1
2
  import { defineTable, formatMoney, formatStoreDateTime, useTenantId } from '@spree/dashboard-core'
2
3
  import { StatusBadge } from '@spree/dashboard-ui'
3
4
  import { BanknoteIcon } from '@spree/dashboard-ui/icons'
@@ -43,9 +44,6 @@ function LedgerDate({ iso }: { iso: string }) {
43
44
  return <>{formatStoreDateTime(iso, timezone)}</>
44
45
  }
45
46
 
46
- const STATUSES = ['pending', 'processing', 'completed', 'failed', 'unresolved'] as const
47
- const KINDS = ['earning', 'refund_reversal'] as const
48
-
49
47
  defineTable<Transfer>('seller-transfers', {
50
48
  title: i18n.t('earnings.transfers.title'),
51
49
  // Without this the toolbar sends `name_cont`, which the ledger does not
@@ -78,7 +76,7 @@ defineTable<Transfer>('seller-transfers', {
78
76
  label: i18n.t('earnings.columns.kind'),
79
77
  filterable: true,
80
78
  filterType: 'enum',
81
- filterOptions: KINDS.map((kind) => ({
79
+ filterOptions: SELLER_TRANSFER_KINDS.map((kind) => ({
82
80
  value: kind,
83
81
  label: i18n.t(`earnings.kinds.${kind}`),
84
82
  })),
@@ -93,7 +91,7 @@ defineTable<Transfer>('seller-transfers', {
93
91
  sortable: true,
94
92
  filterable: true,
95
93
  filterType: 'enum',
96
- filterOptions: STATUSES.map((status) => ({
94
+ filterOptions: SELLER_TRANSFER_STATUSES.map((status) => ({
97
95
  value: status,
98
96
  label: i18n.t(`earnings.statuses.${status}`),
99
97
  })),