@spree/seller-dashboard 1.0.0-rc.6 → 6.0.0-rc.7
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 +20 -20
- package/src/api-client.ts +12 -1
- package/src/locales/ar.json +2 -1
- package/src/locales/de.json +2 -1
- package/src/locales/en.json +2 -1
- package/src/locales/fr.json +2 -1
- package/src/locales/pl.json +2 -1
- package/src/locales/zh-CN.json +2 -1
- package/src/pages/delivery-methods.tsx +67 -20
- package/src/pages/product.tsx +20 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spree/seller-dashboard",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0-rc.7",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Spree marketplace seller panel — the seller-facing counterpart to the admin dashboard.",
|
|
@@ -36,48 +36,48 @@
|
|
|
36
36
|
"LICENSE"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@hookform/resolvers": "^
|
|
39
|
+
"@hookform/resolvers": "^6.0.0",
|
|
40
40
|
"@dnd-kit/core": "^6.3.1",
|
|
41
41
|
"@dnd-kit/sortable": "^10.0.0",
|
|
42
42
|
"@dnd-kit/utilities": "^3.2.2",
|
|
43
|
-
"@tanstack/router-plugin": "^1.168.
|
|
43
|
+
"@tanstack/router-plugin": "^1.168.47",
|
|
44
44
|
"@tanstack/virtual-file-routes": "^1.162.0",
|
|
45
|
-
"lucide-react": "^1.
|
|
45
|
+
"lucide-react": "^1.60.0",
|
|
46
46
|
"zod": "^4.6.7",
|
|
47
|
-
"@spree/
|
|
48
|
-
"@spree/
|
|
47
|
+
"@spree/core": "^6.0.0-rc.7",
|
|
48
|
+
"@spree/seller-sdk": "6.0.0-rc.7"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
|
-
"@tanstack/react-query": "^5.104.
|
|
52
|
-
"@tanstack/react-router": "^1.170.
|
|
53
|
-
"i18next": "^26.
|
|
51
|
+
"@tanstack/react-query": "^5.104.5",
|
|
52
|
+
"@tanstack/react-router": "^1.170.45",
|
|
53
|
+
"i18next": "^26.5.3",
|
|
54
54
|
"react": "^19.3.1",
|
|
55
55
|
"react-dom": "^19.3.1",
|
|
56
56
|
"react-hook-form": "^7.90.0",
|
|
57
|
-
"react-i18next": "^17.0
|
|
58
|
-
"@spree/admin-sdk": "^
|
|
59
|
-
"@spree/dashboard-core": "^
|
|
60
|
-
"@spree/dashboard-ui": "^
|
|
57
|
+
"react-i18next": "^17.1.0",
|
|
58
|
+
"@spree/admin-sdk": "^6.0.0-rc.7",
|
|
59
|
+
"@spree/dashboard-core": "^6.0.0-rc.7",
|
|
60
|
+
"@spree/dashboard-ui": "^6.0.0-rc.7"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@biomejs/biome": "^2.4.14",
|
|
64
|
-
"@tanstack/react-query": "^5.104.
|
|
65
|
-
"@tanstack/react-router": "^1.170.
|
|
64
|
+
"@tanstack/react-query": "^5.104.5",
|
|
65
|
+
"@tanstack/react-router": "^1.170.45",
|
|
66
66
|
"@types/node": "^26.6.5",
|
|
67
67
|
"@types/react": "^19.2.2",
|
|
68
68
|
"@types/react-dom": "^19.2.1",
|
|
69
69
|
"@vitejs/plugin-react": "^6.0.1",
|
|
70
|
-
"i18next": "^26.
|
|
70
|
+
"i18next": "^26.5.3",
|
|
71
71
|
"react": "^19.3.1",
|
|
72
72
|
"react-dom": "^19.3.1",
|
|
73
73
|
"react-hook-form": "^7.90.0",
|
|
74
|
-
"react-i18next": "^17.0
|
|
74
|
+
"react-i18next": "^17.1.0",
|
|
75
75
|
"tsup": "^9.0.0",
|
|
76
76
|
"typescript": "~7.0.2",
|
|
77
77
|
"vite": "^8.3.4",
|
|
78
|
-
"@spree/
|
|
79
|
-
"@spree/dashboard-
|
|
80
|
-
"@spree/
|
|
78
|
+
"@spree/admin-sdk": "6.0.0-rc.7",
|
|
79
|
+
"@spree/dashboard-core": "6.0.0-rc.7",
|
|
80
|
+
"@spree/dashboard-ui": "6.0.0-rc.7"
|
|
81
81
|
},
|
|
82
82
|
"scripts": {
|
|
83
83
|
"build": "pnpm generate:routes && tsup",
|
package/src/api-client.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { runtimeListParams, setApiClient } from '@spree/dashboard-core'
|
|
1
|
+
import { i18n, 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
|
|
|
@@ -146,6 +146,17 @@ export function createSellerApiClient({
|
|
|
146
146
|
update: (id, params) => sellerClient().packageTypes.update(id, params),
|
|
147
147
|
delete: (id) => sellerClient().packageTypes.delete(id),
|
|
148
148
|
},
|
|
149
|
+
digitalAssets: {
|
|
150
|
+
list: (productId, params) => sellerClient().products.digitalAssets.list(productId, params),
|
|
151
|
+
// Sellers upload files only, so a file is the one thing a new asset must carry.
|
|
152
|
+
create: (productId, { signed_id, ...params }) => {
|
|
153
|
+
if (!signed_id) throw new Error(i18n.t('admin.errors.unexpected'))
|
|
154
|
+
return sellerClient().products.digitalAssets.create(productId, { ...params, signed_id })
|
|
155
|
+
},
|
|
156
|
+
update: (productId, id, params) =>
|
|
157
|
+
sellerClient().products.digitalAssets.update(productId, id, params),
|
|
158
|
+
delete: (productId, id) => sellerClient().products.digitalAssets.delete(productId, id),
|
|
159
|
+
},
|
|
149
160
|
})
|
|
150
161
|
|
|
151
162
|
return client
|
package/src/locales/ar.json
CHANGED
|
@@ -750,7 +750,8 @@
|
|
|
750
750
|
"variant_remove_confirm_description": "يُزال من المنتج عند الحفظ.",
|
|
751
751
|
"variant_remove_confirm_title": "إزالة هذا الخيار؟",
|
|
752
752
|
"variants": "الخيارات",
|
|
753
|
-
"variants_empty": "يُباع هذا المنتج كقطعة واحدة بالسعر أعلاه. أضف خيارًا لبيعه بخيارات مثل المقاس أو اللون."
|
|
753
|
+
"variants_empty": "يُباع هذا المنتج كقطعة واحدة بالسعر أعلاه. أضف خيارًا لبيعه بخيارات مثل المقاس أو اللون.",
|
|
754
|
+
"digital_files_review_notice": "إضافة ملف أو استبداله يعيد هذا المنتج إلى المراجعة، ويتوقف بيعه حتى يعتمده السوق مجددًا."
|
|
754
755
|
},
|
|
755
756
|
"profile": {
|
|
756
757
|
"about": "نبذة",
|
package/src/locales/de.json
CHANGED
|
@@ -738,7 +738,8 @@
|
|
|
738
738
|
"variant_remove_confirm_description": "Sie wird beim Speichern vom Produkt entfernt.",
|
|
739
739
|
"variant_remove_confirm_title": "Diese Variante entfernen?",
|
|
740
740
|
"variants": "Varianten",
|
|
741
|
-
"variants_empty": "Dieses Produkt wird als ein Artikel zum obigen Preis verkauft. Fügen Sie eine Variante hinzu, um es in Optionen wie Größe oder Farbe anzubieten."
|
|
741
|
+
"variants_empty": "Dieses Produkt wird als ein Artikel zum obigen Preis verkauft. Fügen Sie eine Variante hinzu, um es in Optionen wie Größe oder Farbe anzubieten.",
|
|
742
|
+
"digital_files_review_notice": "Das Hinzufügen oder Ersetzen einer Datei schickt dieses Produkt zurück in die Prüfung; es ist nicht verkäuflich, bis der Marktplatz es erneut freigibt."
|
|
742
743
|
},
|
|
743
744
|
"profile": {
|
|
744
745
|
"about": "Über uns",
|
package/src/locales/en.json
CHANGED
|
@@ -738,7 +738,8 @@
|
|
|
738
738
|
"variant_remove_confirm_description": "It comes off the product when you save.",
|
|
739
739
|
"variant_remove_confirm_title": "Remove this variant?",
|
|
740
740
|
"variants": "Variants",
|
|
741
|
-
"variants_empty": "This product is sold as one item, at the price above. Add a variant to sell it in options such as size or colour."
|
|
741
|
+
"variants_empty": "This product is sold as one item, at the price above. Add a variant to sell it in options such as size or colour.",
|
|
742
|
+
"digital_files_review_notice": "Adding or replacing a file sends this product back to review, and it is off sale until the marketplace approves it again."
|
|
742
743
|
},
|
|
743
744
|
"profile": {
|
|
744
745
|
"about": "About",
|
package/src/locales/fr.json
CHANGED
|
@@ -738,7 +738,8 @@
|
|
|
738
738
|
"variant_remove_confirm_description": "Elle sera retirée du produit à l’enregistrement.",
|
|
739
739
|
"variant_remove_confirm_title": "Retirer cette variante ?",
|
|
740
740
|
"variants": "Variantes",
|
|
741
|
-
"variants_empty": "Ce produit est vendu comme un article unique, au prix ci-dessus. Ajoutez une variante pour le vendre en plusieurs options, par exemple la taille ou la couleur."
|
|
741
|
+
"variants_empty": "Ce produit est vendu comme un article unique, au prix ci-dessus. Ajoutez une variante pour le vendre en plusieurs options, par exemple la taille ou la couleur.",
|
|
742
|
+
"digital_files_review_notice": "Ajouter ou remplacer un fichier renvoie ce produit en révision ; il n’est plus en vente jusqu’à ce que la place de marché l’approuve de nouveau."
|
|
742
743
|
},
|
|
743
744
|
"profile": {
|
|
744
745
|
"about": "À propos",
|
package/src/locales/pl.json
CHANGED
|
@@ -744,7 +744,8 @@
|
|
|
744
744
|
"variant_remove_confirm_description": "Zniknie z produktu po zapisaniu.",
|
|
745
745
|
"variant_remove_confirm_title": "Usunąć ten wariant?",
|
|
746
746
|
"variants": "Warianty",
|
|
747
|
-
"variants_empty": "Ten produkt sprzedajesz jako jedną pozycję, w cenie powyżej. Dodaj wariant, aby sprzedawać go w opcjach takich jak rozmiar czy kolor."
|
|
747
|
+
"variants_empty": "Ten produkt sprzedajesz jako jedną pozycję, w cenie powyżej. Dodaj wariant, aby sprzedawać go w opcjach takich jak rozmiar czy kolor.",
|
|
748
|
+
"digital_files_review_notice": "Dodanie lub zastąpienie pliku wysyła ten produkt ponownie do weryfikacji; do ponownego zatwierdzenia przez marketplace nie jest w sprzedaży."
|
|
748
749
|
},
|
|
749
750
|
"profile": {
|
|
750
751
|
"about": "O sprzedawcy",
|
package/src/locales/zh-CN.json
CHANGED
|
@@ -735,7 +735,8 @@
|
|
|
735
735
|
"variant_remove_confirm_description": "保存后它会从商品中移除。",
|
|
736
736
|
"variant_remove_confirm_title": "移除此变体?",
|
|
737
737
|
"variants": "变体",
|
|
738
|
-
"variants_empty": "此商品按上述价格作为单一款式销售。添加变体即可按尺码、颜色等选项销售。"
|
|
738
|
+
"variants_empty": "此商品按上述价格作为单一款式销售。添加变体即可按尺码、颜色等选项销售。",
|
|
739
|
+
"digital_files_review_notice": "添加或替换文件会将此商品重新送审,在平台再次批准前将暂停销售。"
|
|
739
740
|
},
|
|
740
741
|
"profile": {
|
|
741
742
|
"about": "简介",
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
+
AMOUNT_BASED_CALCULATORS,
|
|
3
|
+
CURRENCY_AMOUNT_KEYS,
|
|
4
|
+
CurrencyAmountsField,
|
|
2
5
|
defaultPreferences,
|
|
3
6
|
filterPreferenceSchema,
|
|
7
|
+
hasCurrencyAmount,
|
|
4
8
|
PageHeader,
|
|
5
9
|
PreferencesForm,
|
|
6
10
|
type ResourceSearch,
|
|
@@ -281,13 +285,15 @@ function DeliveryMethodSheet({
|
|
|
281
285
|
|
|
282
286
|
form.setValue('calculator', {
|
|
283
287
|
type: flatRate.type,
|
|
284
|
-
preferences:
|
|
288
|
+
preferences: calculatorDefaults(flatRate.type, flatRate.schema),
|
|
285
289
|
})
|
|
286
290
|
}, [calculators, methodId, form])
|
|
287
291
|
|
|
288
|
-
const calculatorSchema =
|
|
292
|
+
const calculatorSchema = calculatorFormSchema(
|
|
293
|
+
calculatorType,
|
|
289
294
|
calculators?.data.find((calculator) => calculator.type === calculatorType)?.schema,
|
|
290
295
|
)
|
|
296
|
+
const amountBased = AMOUNT_BASED_CALCULATORS.includes(calculatorType)
|
|
291
297
|
|
|
292
298
|
const save = useMutation({
|
|
293
299
|
mutationFn: (values: DeliveryMethodFormValues) => {
|
|
@@ -354,7 +360,17 @@ function DeliveryMethodSheet({
|
|
|
354
360
|
<SheetDescription>{t('delivery_methods.sheet.description')}</SheetDescription>
|
|
355
361
|
</SheetHeader>
|
|
356
362
|
<form
|
|
357
|
-
onSubmit={form.handleSubmit((values) =>
|
|
363
|
+
onSubmit={form.handleSubmit((values) => {
|
|
364
|
+
// An amount-based calculator with no amount in any currency would never be offered.
|
|
365
|
+
const { type, preferences } = values.calculator
|
|
366
|
+
if (AMOUNT_BASED_CALCULATORS.includes(type) && !hasCurrencyAmount(preferences)) {
|
|
367
|
+
form.setError('calculator.preferences', {
|
|
368
|
+
message: t('admin.currency_amounts.required'),
|
|
369
|
+
})
|
|
370
|
+
return
|
|
371
|
+
}
|
|
372
|
+
return save.mutateAsync(values).catch(() => undefined)
|
|
373
|
+
})}
|
|
358
374
|
className="flex min-h-0 flex-1 flex-col"
|
|
359
375
|
>
|
|
360
376
|
<div className="flex flex-1 flex-col gap-6 overflow-y-auto p-4">
|
|
@@ -488,11 +504,10 @@ function DeliveryMethodSheet({
|
|
|
488
504
|
value={field.value}
|
|
489
505
|
onValueChange={(next) => {
|
|
490
506
|
field.onChange(next)
|
|
491
|
-
const schema =
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
)
|
|
495
|
-
form.setValue('calculator.preferences', defaultPreferences(schema))
|
|
507
|
+
const schema = calculators?.data.find(
|
|
508
|
+
(calculator) => calculator.type === next,
|
|
509
|
+
)?.schema
|
|
510
|
+
form.setValue('calculator.preferences', calculatorDefaults(next, schema))
|
|
496
511
|
}}
|
|
497
512
|
>
|
|
498
513
|
<SelectTrigger id="delivery-method-calculator">
|
|
@@ -522,13 +537,24 @@ function DeliveryMethodSheet({
|
|
|
522
537
|
<Controller
|
|
523
538
|
control={form.control}
|
|
524
539
|
name="calculator.preferences"
|
|
525
|
-
render={({ field }) => (
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
540
|
+
render={({ field, fieldState }) => (
|
|
541
|
+
<>
|
|
542
|
+
{amountBased && sellerProfile && (
|
|
543
|
+
<CurrencyAmountsField
|
|
544
|
+
values={field.value}
|
|
545
|
+
onChange={field.onChange}
|
|
546
|
+
currencies={currencyOptions}
|
|
547
|
+
defaultCurrency={sellerProfile.default_currency}
|
|
548
|
+
error={fieldState.error}
|
|
549
|
+
/>
|
|
550
|
+
)}
|
|
551
|
+
<PreferencesForm
|
|
552
|
+
schema={calculatorSchema}
|
|
553
|
+
values={field.value}
|
|
554
|
+
onChange={field.onChange}
|
|
555
|
+
currencyOptions={currencyOptions}
|
|
556
|
+
/>
|
|
557
|
+
</>
|
|
532
558
|
)}
|
|
533
559
|
/>
|
|
534
560
|
</CardContent>
|
|
@@ -634,12 +660,33 @@ function DeliveryMethodSheet({
|
|
|
634
660
|
/**
|
|
635
661
|
* The part of a preference schema the shared form can render.
|
|
636
662
|
*
|
|
637
|
-
* Drops keyed maps
|
|
638
|
-
*
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
*
|
|
663
|
+
* Drops keyed maps, which the generic form would show as the hash itself in a
|
|
664
|
+
* text box.
|
|
665
|
+
*/
|
|
666
|
+
/**
|
|
667
|
+
* A calculator's settings for the generic form: an amount-based calculator's
|
|
668
|
+
* amounts are edited per currency by the amount editor instead.
|
|
642
669
|
*/
|
|
670
|
+
function calculatorFormSchema(
|
|
671
|
+
type: string,
|
|
672
|
+
schema: PreferenceSchema | undefined,
|
|
673
|
+
): PreferenceSchema | undefined {
|
|
674
|
+
const renderable = renderableSchema(schema)
|
|
675
|
+
if (!renderable || !AMOUNT_BASED_CALCULATORS.includes(type)) return renderable
|
|
676
|
+
|
|
677
|
+
return filterPreferenceSchema(renderable, (key) => !CURRENCY_AMOUNT_KEYS.includes(key))
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* A calculator's starting settings. The single amount's default is 0, which
|
|
682
|
+
* would read as a price someone set, so an amount-based calculator starts with
|
|
683
|
+
* no amount in any currency.
|
|
684
|
+
*/
|
|
685
|
+
function calculatorDefaults(type: string, schema: PreferenceSchema | undefined) {
|
|
686
|
+
const defaults = defaultPreferences(calculatorFormSchema(type, schema))
|
|
687
|
+
return AMOUNT_BASED_CALCULATORS.includes(type) ? { ...defaults, amount: null } : defaults
|
|
688
|
+
}
|
|
689
|
+
|
|
643
690
|
function renderableSchema(schema: PreferenceSchema | undefined): PreferenceSchema | undefined {
|
|
644
691
|
if (!schema) return undefined
|
|
645
692
|
|
package/src/pages/product.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { zodResolver } from '@hookform/resolvers/zod'
|
|
2
2
|
import {
|
|
3
3
|
CategorizationCard,
|
|
4
|
+
DigitalAssetsCard,
|
|
4
5
|
extensionFormValues,
|
|
5
6
|
extensionRowValues,
|
|
6
7
|
extensionSubmitValues,
|
|
@@ -16,6 +17,7 @@ import {
|
|
|
16
17
|
productFormSchema,
|
|
17
18
|
productToFormValues,
|
|
18
19
|
Slot,
|
|
20
|
+
useAssignableVariants,
|
|
19
21
|
VariantsCard,
|
|
20
22
|
variantToWirePayload,
|
|
21
23
|
withExtensionRowValues,
|
|
@@ -31,6 +33,7 @@ import { sellerClient } from '../api-client'
|
|
|
31
33
|
import { CenteredMessage } from '../components/centered-message'
|
|
32
34
|
import { ProductStatusCard } from '../components/product-status-card'
|
|
33
35
|
import { RetryableError } from '../components/retryable-error'
|
|
36
|
+
import { useAccount } from '../hooks/use-account'
|
|
34
37
|
|
|
35
38
|
/** Everything the form edits, in one request. */
|
|
36
39
|
const PRODUCT_EXPAND = [
|
|
@@ -96,6 +99,11 @@ export function ProductPage({ mode }: { mode: 'new' | 'edit' }) {
|
|
|
96
99
|
defaultValues: { ...newProductFormDefaults(), ...extensionFormValues('seller.product', null) },
|
|
97
100
|
})
|
|
98
101
|
|
|
102
|
+
const assignableVariants = useAssignableVariants(form.control, product?.variants)
|
|
103
|
+
// Whether this marketplace lets sellers upload files, and of which types,
|
|
104
|
+
// comes with the account the panel already holds.
|
|
105
|
+
const marketplace = useAccount().data?.store
|
|
106
|
+
|
|
99
107
|
// Reset once the record arrives, so the inputs show what was last saved
|
|
100
108
|
// rather than the blank defaults the form mounted with.
|
|
101
109
|
//
|
|
@@ -242,7 +250,7 @@ export function ProductPage({ mode }: { mode: 'new' | 'edit' }) {
|
|
|
242
250
|
<VariantsCard form={form} formKey="seller.product" />
|
|
243
251
|
<MediaCard
|
|
244
252
|
productId={productId}
|
|
245
|
-
variants={
|
|
253
|
+
variants={assignableVariants}
|
|
246
254
|
form={form}
|
|
247
255
|
formKey="seller.product"
|
|
248
256
|
/>
|
|
@@ -253,6 +261,17 @@ export function ProductPage({ mode }: { mode: 'new' | 'edit' }) {
|
|
|
253
261
|
defaultCurrency={profile?.default_currency}
|
|
254
262
|
/>
|
|
255
263
|
<InventoryCard form={form} />
|
|
264
|
+
<DigitalAssetsCard
|
|
265
|
+
productId={productId}
|
|
266
|
+
variants={assignableVariants}
|
|
267
|
+
readOnly={!marketplace?.seller_digital_assets_enabled}
|
|
268
|
+
accept={marketplace?.seller_digital_asset_content_types}
|
|
269
|
+
notice={
|
|
270
|
+
product?.status === 'active' && marketplace?.review_seller_digital_asset_changes
|
|
271
|
+
? t('products.digital_files_review_notice')
|
|
272
|
+
: undefined
|
|
273
|
+
}
|
|
274
|
+
/>
|
|
256
275
|
<Slot name="seller.product.form_main" context={{ product, mode }} />
|
|
257
276
|
</>
|
|
258
277
|
}
|