@spree/seller-dashboard 6.0.3 → 6.0.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.
- package/package.json +23 -23
- package/src/api-client.ts +2 -0
- package/src/components/account-dialog.tsx +1 -1
- package/src/components/centered-message.tsx +1 -1
- package/src/components/finance/balance-summary.tsx +1 -1
- package/src/components/orders/fulfillment-deliveries.tsx +1 -1
- package/src/components/orders/fulfillments-card.tsx +30 -20
- package/src/components/orders/order-cards.tsx +115 -102
- package/src/components/orders/order-payment-card.tsx +2 -3
- package/src/components/orders/post-sale-cards.tsx +79 -47
- package/src/components/orders/post-sale-create-dialogs.tsx +15 -0
- package/src/components/orders/returns-card.tsx +44 -36
- package/src/components/orders/shipping-label-row.tsx +1 -1
- package/src/components/orders/use-product-link.tsx +10 -0
- package/src/components/product-status-card.tsx +2 -2
- package/src/components/seller-address-card.tsx +2 -2
- package/src/components/seller-returns-location-card.tsx +2 -7
- package/src/components/seller-switcher.tsx +1 -1
- package/src/hooks/use-fulfillments.ts +33 -22
- package/src/hooks/use-profile.ts +38 -0
- package/src/locales/ar.json +35 -31
- package/src/locales/de.json +35 -31
- package/src/locales/en.json +35 -31
- package/src/locales/fr.json +35 -31
- package/src/locales/ja.json +35 -31
- package/src/locales/pl.json +35 -31
- package/src/locales/vi.json +35 -31
- package/src/locales/zh-CN.json +35 -31
- package/src/nav/default.ts +0 -10
- package/src/nav/settings.ts +53 -0
- package/src/pages/delivery-methods.tsx +47 -49
- package/src/pages/forgot-password.tsx +5 -5
- package/src/pages/general-settings.tsx +233 -0
- package/src/pages/home.tsx +1 -1
- package/src/pages/login.tsx +3 -3
- package/src/pages/onboarding.tsx +26 -23
- package/src/pages/order.tsx +2 -1
- package/src/pages/orders.tsx +8 -15
- package/src/pages/payout.tsx +7 -4
- package/src/pages/payouts.tsx +23 -2
- package/src/pages/policies.tsx +12 -8
- package/src/pages/product.tsx +18 -4
- package/src/pages/products.tsx +0 -4
- package/src/pages/profile-settings.tsx +221 -0
- package/src/pages/reset-password.tsx +3 -3
- package/src/pages/seller-picker.tsx +1 -1
- package/src/pages/taxes.tsx +40 -0
- package/src/pages/team.tsx +1 -1
- package/src/routeTree.gen.ts +63 -0
- package/src/routes/_authenticated/$sellerId/profile.tsx +5 -3
- package/src/routes/_authenticated/$sellerId/settings/general.tsx +6 -0
- package/src/routes/_authenticated/$sellerId/settings/profile.tsx +6 -0
- package/src/routes/_authenticated/$sellerId/settings/taxes.tsx +6 -0
- package/src/routes/_authenticated/$sellerId/settings.tsx +3 -7
- package/src/schemas/account.ts +1 -1
- package/src/tables/products.tsx +6 -5
- package/src/components/read-row.tsx +0 -16
- package/src/components/seller-business-card.tsx +0 -152
- package/src/pages/profile.tsx +0 -395
package/src/pages/products.tsx
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
ExportButton,
|
|
10
10
|
ImportButton,
|
|
11
11
|
ImportWizardDialog,
|
|
12
|
-
PageHeader,
|
|
13
12
|
ResourceTable,
|
|
14
13
|
Subject,
|
|
15
14
|
} from '@spree/dashboard-core'
|
|
@@ -149,10 +148,7 @@ export function ProductsPage({ search }: { search: ResourceSearch & { import?: s
|
|
|
149
148
|
|
|
150
149
|
return (
|
|
151
150
|
<div className="flex flex-col gap-4">
|
|
152
|
-
<PageHeader title={t('products.title')} sticky={false} />
|
|
153
|
-
|
|
154
151
|
<ResourceTable<Product>
|
|
155
|
-
hideHeader
|
|
156
152
|
tableKey="seller-products"
|
|
157
153
|
queryKey="seller-products"
|
|
158
154
|
queryFn={(params) => sellerClient().products.list(params)}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { zodResolver } from '@hookform/resolvers/zod'
|
|
2
|
+
import {
|
|
3
|
+
blankToNull,
|
|
4
|
+
extensionFormValues,
|
|
5
|
+
extensionSubmitValues,
|
|
6
|
+
mapSpreeErrorsToForm,
|
|
7
|
+
PageHeader,
|
|
8
|
+
ResourceImageField,
|
|
9
|
+
SettingsSection,
|
|
10
|
+
Slot,
|
|
11
|
+
sellerFormSchema,
|
|
12
|
+
sellerFormValues,
|
|
13
|
+
signedIdParam,
|
|
14
|
+
} from '@spree/dashboard-core'
|
|
15
|
+
import {
|
|
16
|
+
CardContent,
|
|
17
|
+
Field,
|
|
18
|
+
FieldDescription,
|
|
19
|
+
FieldError,
|
|
20
|
+
FieldGroup,
|
|
21
|
+
FieldLabel,
|
|
22
|
+
FormActions,
|
|
23
|
+
Input,
|
|
24
|
+
ResourceLayout,
|
|
25
|
+
RichTextEditor,
|
|
26
|
+
useFormSubmitShortcut,
|
|
27
|
+
} from '@spree/dashboard-ui'
|
|
28
|
+
import { ImageIcon, StoreIcon } from '@spree/dashboard-ui/icons'
|
|
29
|
+
import type { Profile } from '@spree/seller-sdk'
|
|
30
|
+
import { useState } from 'react'
|
|
31
|
+
import { Controller, FormProvider, useForm } from 'react-hook-form'
|
|
32
|
+
import { useTranslation } from 'react-i18next'
|
|
33
|
+
import type { z } from 'zod/v4'
|
|
34
|
+
import { CenteredMessage } from '../components/centered-message'
|
|
35
|
+
import { useProfile, useUpdateProfile } from '../hooks/use-profile'
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* What buyers see of the seller — name, description and images — as one
|
|
39
|
+
* form. Everything else the seller configures lives on the other settings
|
|
40
|
+
* pages.
|
|
41
|
+
*/
|
|
42
|
+
export function ProfileSettingsPage() {
|
|
43
|
+
const { t } = useTranslation()
|
|
44
|
+
const { data: profile, isLoading, error } = useProfile()
|
|
45
|
+
|
|
46
|
+
if (isLoading) return <CenteredMessage>{t('common.loading')}</CenteredMessage>
|
|
47
|
+
if (error || !profile) return <CenteredMessage>{t('common.error')}</CenteredMessage>
|
|
48
|
+
|
|
49
|
+
return <ProfileForm profile={profile} />
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Only what this page shows. The operator's schema also checks fields edited
|
|
53
|
+
// elsewhere (the emails), and an error on a field that is not on the page
|
|
54
|
+
// would block Save with nothing to see.
|
|
55
|
+
const profileFormSchema = sellerFormSchema.pick({
|
|
56
|
+
name: true,
|
|
57
|
+
about: true,
|
|
58
|
+
logo_signed_id: true,
|
|
59
|
+
logo_preview_url: true,
|
|
60
|
+
logo_cleared: true,
|
|
61
|
+
square_logo_signed_id: true,
|
|
62
|
+
square_logo_preview_url: true,
|
|
63
|
+
square_logo_cleared: true,
|
|
64
|
+
cover_photo_signed_id: true,
|
|
65
|
+
cover_photo_preview_url: true,
|
|
66
|
+
cover_photo_cleared: true,
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
type ProfileFormValues = z.infer<typeof profileFormSchema>
|
|
70
|
+
|
|
71
|
+
// Fields a plugin adds through this page's slots register under this key and
|
|
72
|
+
// save with the page; the API accepts what extensions opened to sellers.
|
|
73
|
+
const FORM_KEY = 'seller.profile'
|
|
74
|
+
|
|
75
|
+
function formValues(profile: Profile) {
|
|
76
|
+
return { ...sellerFormValues(profile), ...extensionFormValues(FORM_KEY, profile) }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function ProfileForm({ profile }: { profile: Profile }) {
|
|
80
|
+
const { t } = useTranslation()
|
|
81
|
+
const update = useUpdateProfile()
|
|
82
|
+
|
|
83
|
+
const form = useForm<ProfileFormValues>({
|
|
84
|
+
resolver: zodResolver(profileFormSchema),
|
|
85
|
+
defaultValues: formValues(profile),
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
// An image's signed id only exists once its upload finishes, so saving
|
|
89
|
+
// before then would silently drop the new image.
|
|
90
|
+
const [uploading, setUploading] = useState<ReadonlySet<string>>(new Set())
|
|
91
|
+
const trackUpload = (kind: string) => (active: boolean) =>
|
|
92
|
+
setUploading((current) => {
|
|
93
|
+
const next = new Set(current)
|
|
94
|
+
if (active) next.add(kind)
|
|
95
|
+
else next.delete(kind)
|
|
96
|
+
return next
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
async function onSubmit(values: ProfileFormValues) {
|
|
100
|
+
if (uploading.size > 0) return
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const updated = await update.mutateAsync({
|
|
104
|
+
...extensionSubmitValues(FORM_KEY, form),
|
|
105
|
+
name: values.name,
|
|
106
|
+
about: blankToNull(values.about),
|
|
107
|
+
...signedIdParam('logo', values.logo_signed_id, values.logo_cleared),
|
|
108
|
+
...signedIdParam('square_logo', values.square_logo_signed_id, values.square_logo_cleared),
|
|
109
|
+
...signedIdParam('cover_photo', values.cover_photo_signed_id, values.cover_photo_cleared),
|
|
110
|
+
})
|
|
111
|
+
// Saved images come back as server URLs, so the upload fields start
|
|
112
|
+
// empty again rather than holding the files just sent.
|
|
113
|
+
form.reset(formValues(updated))
|
|
114
|
+
} catch (err) {
|
|
115
|
+
// Anything that is not a field error has already been toasted.
|
|
116
|
+
mapSpreeErrorsToForm(err, form.setError)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
useFormSubmitShortcut(form, onSubmit)
|
|
121
|
+
|
|
122
|
+
const { errors } = form.formState
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<FormProvider {...form}>
|
|
126
|
+
<form onSubmit={form.handleSubmit(onSubmit)}>
|
|
127
|
+
<ResourceLayout
|
|
128
|
+
header={
|
|
129
|
+
<PageHeader
|
|
130
|
+
title={t('nav.profile')}
|
|
131
|
+
description={t('profile.subtitle')}
|
|
132
|
+
sticky={false}
|
|
133
|
+
actions={<FormActions form={form} disabled={uploading.size > 0} />}
|
|
134
|
+
/>
|
|
135
|
+
}
|
|
136
|
+
main={
|
|
137
|
+
<>
|
|
138
|
+
{errors.root?.message && (
|
|
139
|
+
<p className="text-base text-destructive" role="alert">
|
|
140
|
+
{errors.root.message}
|
|
141
|
+
</p>
|
|
142
|
+
)}
|
|
143
|
+
<SettingsSection title={t('profile.sections.details')} icon={<StoreIcon />}>
|
|
144
|
+
<CardContent>
|
|
145
|
+
<FieldGroup>
|
|
146
|
+
<Field>
|
|
147
|
+
<FieldLabel htmlFor="seller-name">{t('admin.fields.name.label')}</FieldLabel>
|
|
148
|
+
<Input
|
|
149
|
+
id="seller-name"
|
|
150
|
+
aria-invalid={!!errors.name || undefined}
|
|
151
|
+
{...form.register('name')}
|
|
152
|
+
/>
|
|
153
|
+
<FieldError errors={[errors.name]} />
|
|
154
|
+
</Field>
|
|
155
|
+
<Field>
|
|
156
|
+
<FieldLabel htmlFor="seller-about">
|
|
157
|
+
{t('admin.fields.seller.about.label')}
|
|
158
|
+
</FieldLabel>
|
|
159
|
+
<Controller
|
|
160
|
+
control={form.control}
|
|
161
|
+
name="about"
|
|
162
|
+
render={({ field }) => (
|
|
163
|
+
<RichTextEditor
|
|
164
|
+
id="seller-about"
|
|
165
|
+
ariaLabel={t('admin.fields.seller.about.label')}
|
|
166
|
+
value={field.value ?? ''}
|
|
167
|
+
onChange={field.onChange}
|
|
168
|
+
/>
|
|
169
|
+
)}
|
|
170
|
+
/>
|
|
171
|
+
<FieldDescription>{t('admin.fields.seller.about.help')}</FieldDescription>
|
|
172
|
+
</Field>
|
|
173
|
+
</FieldGroup>
|
|
174
|
+
</CardContent>
|
|
175
|
+
</SettingsSection>
|
|
176
|
+
|
|
177
|
+
<SettingsSection title={t('profile.sections.images')} icon={<ImageIcon />}>
|
|
178
|
+
<CardContent>
|
|
179
|
+
<FieldGroup>
|
|
180
|
+
<ResourceImageField
|
|
181
|
+
form={form}
|
|
182
|
+
kind="logo"
|
|
183
|
+
onUploadingChange={trackUpload('logo')}
|
|
184
|
+
serverUrl={profile.logo_url}
|
|
185
|
+
square
|
|
186
|
+
translationNamespace="admin.sellers"
|
|
187
|
+
labelKey="logo_label"
|
|
188
|
+
helpKey="logo_help"
|
|
189
|
+
/>
|
|
190
|
+
<ResourceImageField
|
|
191
|
+
form={form}
|
|
192
|
+
kind="square_logo"
|
|
193
|
+
onUploadingChange={trackUpload('square_logo')}
|
|
194
|
+
serverUrl={profile.square_logo_url}
|
|
195
|
+
square
|
|
196
|
+
translationNamespace="admin.sellers"
|
|
197
|
+
labelKey="square_logo_label"
|
|
198
|
+
helpKey="square_logo_help"
|
|
199
|
+
/>
|
|
200
|
+
<ResourceImageField
|
|
201
|
+
form={form}
|
|
202
|
+
kind="cover_photo"
|
|
203
|
+
onUploadingChange={trackUpload('cover_photo')}
|
|
204
|
+
serverUrl={profile.cover_photo_url}
|
|
205
|
+
translationNamespace="admin.sellers"
|
|
206
|
+
labelKey="cover_photo_label"
|
|
207
|
+
helpKey="cover_photo_help"
|
|
208
|
+
/>
|
|
209
|
+
</FieldGroup>
|
|
210
|
+
</CardContent>
|
|
211
|
+
</SettingsSection>
|
|
212
|
+
|
|
213
|
+
<Slot name="seller.profile.form_main" context={{ profile }} />
|
|
214
|
+
<Slot name="seller.profile.form_sidebar" context={{ profile }} />
|
|
215
|
+
</>
|
|
216
|
+
}
|
|
217
|
+
/>
|
|
218
|
+
</form>
|
|
219
|
+
</FormProvider>
|
|
220
|
+
)
|
|
221
|
+
}
|
|
@@ -49,12 +49,12 @@ export function ResetPasswordPage({ token }: { token?: string }) {
|
|
|
49
49
|
<div className="flex min-h-screen items-center justify-center p-6">
|
|
50
50
|
<div className="w-full max-w-sm">
|
|
51
51
|
<h1 className="font-medium text-xl">{t('reset_password.title')}</h1>
|
|
52
|
-
<p className="mt-1 mb-6 text-muted-foreground text-
|
|
52
|
+
<p className="mt-1 mb-6 text-muted-foreground text-base">{t('reset_password.subtitle')}</p>
|
|
53
53
|
|
|
54
54
|
<form onSubmit={form.handleSubmit(onSubmit)}>
|
|
55
55
|
<FieldGroup>
|
|
56
56
|
{error && (
|
|
57
|
-
<p className="text-destructive text-
|
|
57
|
+
<p className="text-destructive text-base" role="alert">
|
|
58
58
|
{error}
|
|
59
59
|
</p>
|
|
60
60
|
)}
|
|
@@ -94,7 +94,7 @@ export function ResetPasswordPage({ token }: { token?: string }) {
|
|
|
94
94
|
{isSubmitting ? t('reset_password.submitting') : t('reset_password.submit')}
|
|
95
95
|
</Button>
|
|
96
96
|
|
|
97
|
-
<Link to="/forgot-password" className="text-
|
|
97
|
+
<Link to="/forgot-password" className="text-base underline underline-offset-4">
|
|
98
98
|
{t('reset_password.request_new_link')}
|
|
99
99
|
</Link>
|
|
100
100
|
</FieldGroup>
|
|
@@ -30,7 +30,7 @@ export function SellerPicker() {
|
|
|
30
30
|
<CardTitle>{t('seller_picker.title')}</CardTitle>
|
|
31
31
|
</CardHeader>
|
|
32
32
|
<CardContent className="flex flex-col gap-2">
|
|
33
|
-
<p className="mb-2 text-muted-foreground text-
|
|
33
|
+
<p className="mb-2 text-muted-foreground text-base">{t('seller_picker.subtitle')}</p>
|
|
34
34
|
{sellers.map((seller) => (
|
|
35
35
|
<Button key={seller.id} variant="outline" className="justify-between" asChild>
|
|
36
36
|
<Link to="/$sellerId" params={{ sellerId: seller.id }}>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { PageHeader, ReadRow, SettingsSection } from '@spree/dashboard-core'
|
|
2
|
+
import { CardContent, ResourceLayout } from '@spree/dashboard-ui'
|
|
3
|
+
import { ReceiptTextIcon } from '@spree/dashboard-ui/icons'
|
|
4
|
+
import { useTranslation } from 'react-i18next'
|
|
5
|
+
import { SellerTaxIdentifiersCard } from '../components/seller-tax-identifiers-card'
|
|
6
|
+
import { useProfile } from '../hooks/use-profile'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The seller's tax registrations, then who remits tax on their sales. The
|
|
10
|
+
* second is the marketplace's decision, so it is shown read-only: the seller
|
|
11
|
+
* API refuses to write it.
|
|
12
|
+
*/
|
|
13
|
+
export function TaxesPage() {
|
|
14
|
+
const { t } = useTranslation()
|
|
15
|
+
const { data: profile } = useProfile()
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<ResourceLayout
|
|
19
|
+
header={
|
|
20
|
+
<PageHeader title={t('taxes.title')} description={t('taxes.description')} sticky={false} />
|
|
21
|
+
}
|
|
22
|
+
main={
|
|
23
|
+
<>
|
|
24
|
+
<SellerTaxIdentifiersCard />
|
|
25
|
+
<SettingsSection
|
|
26
|
+
title={t('profile.tax_remittance')}
|
|
27
|
+
icon={<ReceiptTextIcon />}
|
|
28
|
+
description={t('profile.operator_managed')}
|
|
29
|
+
>
|
|
30
|
+
<CardContent>
|
|
31
|
+
<ReadRow label={t('profile.tax_remittance')}>
|
|
32
|
+
{profile && t(`taxes.remittance.${profile.tax_remittance}`)}
|
|
33
|
+
</ReadRow>
|
|
34
|
+
</CardContent>
|
|
35
|
+
</SettingsSection>
|
|
36
|
+
</>
|
|
37
|
+
}
|
|
38
|
+
/>
|
|
39
|
+
)
|
|
40
|
+
}
|
package/src/pages/team.tsx
CHANGED
|
@@ -365,7 +365,7 @@ function InvitationRow({ invitation }: { invitation: Invitation }) {
|
|
|
365
365
|
<span className="font-medium">{invitation.email}</span>
|
|
366
366
|
</div>
|
|
367
367
|
</TableCell>
|
|
368
|
-
<TableCell className="text-muted-foreground text-
|
|
368
|
+
<TableCell className="text-muted-foreground text-base">
|
|
369
369
|
{invitation.expires_at ? (
|
|
370
370
|
<span className="inline-flex items-center gap-1">
|
|
371
371
|
<ClockIcon className="size-3.5" />
|
package/src/routeTree.gen.ts
CHANGED
|
@@ -30,9 +30,12 @@ import { Route as ProductsProductIdRouteImport } from './routes/_authenticated/$
|
|
|
30
30
|
import { Route as ProductsNewRouteImport } from './routes/_authenticated/$sellerId/products/new'
|
|
31
31
|
import { Route as SettingsIndexRouteImport } from './routes/_authenticated/$sellerId/settings/index'
|
|
32
32
|
import { Route as SettingsDeliveryMethodsRouteImport } from './routes/_authenticated/$sellerId/settings/delivery-methods'
|
|
33
|
+
import { Route as SettingsGeneralRouteImport } from './routes/_authenticated/$sellerId/settings/general'
|
|
33
34
|
import { Route as SettingsPackageTypesRouteImport } from './routes/_authenticated/$sellerId/settings/package-types'
|
|
34
35
|
import { Route as SettingsPoliciesRouteImport } from './routes/_authenticated/$sellerId/settings/policies'
|
|
36
|
+
import { Route as SettingsProfileRouteImport } from './routes/_authenticated/$sellerId/settings/profile'
|
|
35
37
|
import { Route as SettingsStockLocationsRouteImport } from './routes/_authenticated/$sellerId/settings/stock-locations'
|
|
38
|
+
import { Route as SettingsTaxesRouteImport } from './routes/_authenticated/$sellerId/settings/taxes'
|
|
36
39
|
import { Route as SettingsTeamRouteImport } from './routes/_authenticated/$sellerId/settings/team'
|
|
37
40
|
|
|
38
41
|
const authenticatedRoute = authenticatedRouteImport.update({
|
|
@@ -140,6 +143,11 @@ const SettingsDeliveryMethodsRoute = SettingsDeliveryMethodsRouteImport.update({
|
|
|
140
143
|
path: '/delivery-methods',
|
|
141
144
|
getParentRoute: () => SettingsRoute,
|
|
142
145
|
} as any)
|
|
146
|
+
const SettingsGeneralRoute = SettingsGeneralRouteImport.update({
|
|
147
|
+
id: '/general',
|
|
148
|
+
path: '/general',
|
|
149
|
+
getParentRoute: () => SettingsRoute,
|
|
150
|
+
} as any)
|
|
143
151
|
const SettingsPackageTypesRoute = SettingsPackageTypesRouteImport.update({
|
|
144
152
|
id: '/package-types',
|
|
145
153
|
path: '/package-types',
|
|
@@ -150,11 +158,21 @@ const SettingsPoliciesRoute = SettingsPoliciesRouteImport.update({
|
|
|
150
158
|
path: '/policies',
|
|
151
159
|
getParentRoute: () => SettingsRoute,
|
|
152
160
|
} as any)
|
|
161
|
+
const SettingsProfileRoute = SettingsProfileRouteImport.update({
|
|
162
|
+
id: '/profile',
|
|
163
|
+
path: '/profile',
|
|
164
|
+
getParentRoute: () => SettingsRoute,
|
|
165
|
+
} as any)
|
|
153
166
|
const SettingsStockLocationsRoute = SettingsStockLocationsRouteImport.update({
|
|
154
167
|
id: '/stock-locations',
|
|
155
168
|
path: '/stock-locations',
|
|
156
169
|
getParentRoute: () => SettingsRoute,
|
|
157
170
|
} as any)
|
|
171
|
+
const SettingsTaxesRoute = SettingsTaxesRouteImport.update({
|
|
172
|
+
id: '/taxes',
|
|
173
|
+
path: '/taxes',
|
|
174
|
+
getParentRoute: () => SettingsRoute,
|
|
175
|
+
} as any)
|
|
158
176
|
const SettingsTeamRoute = SettingsTeamRouteImport.update({
|
|
159
177
|
id: '/team',
|
|
160
178
|
path: '/team',
|
|
@@ -177,9 +195,12 @@ export interface FileRoutesByFullPath {
|
|
|
177
195
|
'/$sellerId/products/$productId': typeof ProductsProductIdRoute
|
|
178
196
|
'/$sellerId/products/new': typeof ProductsNewRoute
|
|
179
197
|
'/$sellerId/settings/delivery-methods': typeof SettingsDeliveryMethodsRoute
|
|
198
|
+
'/$sellerId/settings/general': typeof SettingsGeneralRoute
|
|
180
199
|
'/$sellerId/settings/package-types': typeof SettingsPackageTypesRoute
|
|
181
200
|
'/$sellerId/settings/policies': typeof SettingsPoliciesRoute
|
|
201
|
+
'/$sellerId/settings/profile': typeof SettingsProfileRoute
|
|
182
202
|
'/$sellerId/settings/stock-locations': typeof SettingsStockLocationsRoute
|
|
203
|
+
'/$sellerId/settings/taxes': typeof SettingsTaxesRoute
|
|
183
204
|
'/$sellerId/settings/team': typeof SettingsTeamRoute
|
|
184
205
|
'/$sellerId/earnings/': typeof EarningsIndexRoute
|
|
185
206
|
'/$sellerId/orders/': typeof OrdersIndexRoute
|
|
@@ -201,9 +222,12 @@ export interface FileRoutesByTo {
|
|
|
201
222
|
'/$sellerId/products/$productId': typeof ProductsProductIdRoute
|
|
202
223
|
'/$sellerId/products/new': typeof ProductsNewRoute
|
|
203
224
|
'/$sellerId/settings/delivery-methods': typeof SettingsDeliveryMethodsRoute
|
|
225
|
+
'/$sellerId/settings/general': typeof SettingsGeneralRoute
|
|
204
226
|
'/$sellerId/settings/package-types': typeof SettingsPackageTypesRoute
|
|
205
227
|
'/$sellerId/settings/policies': typeof SettingsPoliciesRoute
|
|
228
|
+
'/$sellerId/settings/profile': typeof SettingsProfileRoute
|
|
206
229
|
'/$sellerId/settings/stock-locations': typeof SettingsStockLocationsRoute
|
|
230
|
+
'/$sellerId/settings/taxes': typeof SettingsTaxesRoute
|
|
207
231
|
'/$sellerId/settings/team': typeof SettingsTeamRoute
|
|
208
232
|
'/$sellerId/earnings': typeof EarningsIndexRoute
|
|
209
233
|
'/$sellerId/orders': typeof OrdersIndexRoute
|
|
@@ -229,9 +253,12 @@ export interface FileRoutesById {
|
|
|
229
253
|
'/_authenticated/$sellerId/products/$productId': typeof ProductsProductIdRoute
|
|
230
254
|
'/_authenticated/$sellerId/products/new': typeof ProductsNewRoute
|
|
231
255
|
'/_authenticated/$sellerId/settings/delivery-methods': typeof SettingsDeliveryMethodsRoute
|
|
256
|
+
'/_authenticated/$sellerId/settings/general': typeof SettingsGeneralRoute
|
|
232
257
|
'/_authenticated/$sellerId/settings/package-types': typeof SettingsPackageTypesRoute
|
|
233
258
|
'/_authenticated/$sellerId/settings/policies': typeof SettingsPoliciesRoute
|
|
259
|
+
'/_authenticated/$sellerId/settings/profile': typeof SettingsProfileRoute
|
|
234
260
|
'/_authenticated/$sellerId/settings/stock-locations': typeof SettingsStockLocationsRoute
|
|
261
|
+
'/_authenticated/$sellerId/settings/taxes': typeof SettingsTaxesRoute
|
|
235
262
|
'/_authenticated/$sellerId/settings/team': typeof SettingsTeamRoute
|
|
236
263
|
'/_authenticated/$sellerId/earnings/': typeof EarningsIndexRoute
|
|
237
264
|
'/_authenticated/$sellerId/orders/': typeof OrdersIndexRoute
|
|
@@ -257,9 +284,12 @@ export interface FileRouteTypes {
|
|
|
257
284
|
| '/$sellerId/products/$productId'
|
|
258
285
|
| '/$sellerId/products/new'
|
|
259
286
|
| '/$sellerId/settings/delivery-methods'
|
|
287
|
+
| '/$sellerId/settings/general'
|
|
260
288
|
| '/$sellerId/settings/package-types'
|
|
261
289
|
| '/$sellerId/settings/policies'
|
|
290
|
+
| '/$sellerId/settings/profile'
|
|
262
291
|
| '/$sellerId/settings/stock-locations'
|
|
292
|
+
| '/$sellerId/settings/taxes'
|
|
263
293
|
| '/$sellerId/settings/team'
|
|
264
294
|
| '/$sellerId/earnings/'
|
|
265
295
|
| '/$sellerId/orders/'
|
|
@@ -281,9 +311,12 @@ export interface FileRouteTypes {
|
|
|
281
311
|
| '/$sellerId/products/$productId'
|
|
282
312
|
| '/$sellerId/products/new'
|
|
283
313
|
| '/$sellerId/settings/delivery-methods'
|
|
314
|
+
| '/$sellerId/settings/general'
|
|
284
315
|
| '/$sellerId/settings/package-types'
|
|
285
316
|
| '/$sellerId/settings/policies'
|
|
317
|
+
| '/$sellerId/settings/profile'
|
|
286
318
|
| '/$sellerId/settings/stock-locations'
|
|
319
|
+
| '/$sellerId/settings/taxes'
|
|
287
320
|
| '/$sellerId/settings/team'
|
|
288
321
|
| '/$sellerId/earnings'
|
|
289
322
|
| '/$sellerId/orders'
|
|
@@ -308,9 +341,12 @@ export interface FileRouteTypes {
|
|
|
308
341
|
| '/_authenticated/$sellerId/products/$productId'
|
|
309
342
|
| '/_authenticated/$sellerId/products/new'
|
|
310
343
|
| '/_authenticated/$sellerId/settings/delivery-methods'
|
|
344
|
+
| '/_authenticated/$sellerId/settings/general'
|
|
311
345
|
| '/_authenticated/$sellerId/settings/package-types'
|
|
312
346
|
| '/_authenticated/$sellerId/settings/policies'
|
|
347
|
+
| '/_authenticated/$sellerId/settings/profile'
|
|
313
348
|
| '/_authenticated/$sellerId/settings/stock-locations'
|
|
349
|
+
| '/_authenticated/$sellerId/settings/taxes'
|
|
314
350
|
| '/_authenticated/$sellerId/settings/team'
|
|
315
351
|
| '/_authenticated/$sellerId/earnings/'
|
|
316
352
|
| '/_authenticated/$sellerId/orders/'
|
|
@@ -476,6 +512,13 @@ declare module '@tanstack/react-router' {
|
|
|
476
512
|
preLoaderRoute: typeof SettingsDeliveryMethodsRouteImport
|
|
477
513
|
parentRoute: typeof SettingsRoute
|
|
478
514
|
}
|
|
515
|
+
'/_authenticated/$sellerId/settings/general': {
|
|
516
|
+
id: '/_authenticated/$sellerId/settings/general'
|
|
517
|
+
path: '/general'
|
|
518
|
+
fullPath: '/$sellerId/settings/general'
|
|
519
|
+
preLoaderRoute: typeof SettingsGeneralRouteImport
|
|
520
|
+
parentRoute: typeof SettingsRoute
|
|
521
|
+
}
|
|
479
522
|
'/_authenticated/$sellerId/settings/package-types': {
|
|
480
523
|
id: '/_authenticated/$sellerId/settings/package-types'
|
|
481
524
|
path: '/package-types'
|
|
@@ -490,6 +533,13 @@ declare module '@tanstack/react-router' {
|
|
|
490
533
|
preLoaderRoute: typeof SettingsPoliciesRouteImport
|
|
491
534
|
parentRoute: typeof SettingsRoute
|
|
492
535
|
}
|
|
536
|
+
'/_authenticated/$sellerId/settings/profile': {
|
|
537
|
+
id: '/_authenticated/$sellerId/settings/profile'
|
|
538
|
+
path: '/profile'
|
|
539
|
+
fullPath: '/$sellerId/settings/profile'
|
|
540
|
+
preLoaderRoute: typeof SettingsProfileRouteImport
|
|
541
|
+
parentRoute: typeof SettingsRoute
|
|
542
|
+
}
|
|
493
543
|
'/_authenticated/$sellerId/settings/stock-locations': {
|
|
494
544
|
id: '/_authenticated/$sellerId/settings/stock-locations'
|
|
495
545
|
path: '/stock-locations'
|
|
@@ -497,6 +547,13 @@ declare module '@tanstack/react-router' {
|
|
|
497
547
|
preLoaderRoute: typeof SettingsStockLocationsRouteImport
|
|
498
548
|
parentRoute: typeof SettingsRoute
|
|
499
549
|
}
|
|
550
|
+
'/_authenticated/$sellerId/settings/taxes': {
|
|
551
|
+
id: '/_authenticated/$sellerId/settings/taxes'
|
|
552
|
+
path: '/taxes'
|
|
553
|
+
fullPath: '/$sellerId/settings/taxes'
|
|
554
|
+
preLoaderRoute: typeof SettingsTaxesRouteImport
|
|
555
|
+
parentRoute: typeof SettingsRoute
|
|
556
|
+
}
|
|
500
557
|
'/_authenticated/$sellerId/settings/team': {
|
|
501
558
|
id: '/_authenticated/$sellerId/settings/team'
|
|
502
559
|
path: '/team'
|
|
@@ -509,18 +566,24 @@ declare module '@tanstack/react-router' {
|
|
|
509
566
|
|
|
510
567
|
interface SettingsRouteChildren {
|
|
511
568
|
SettingsDeliveryMethodsRoute: typeof SettingsDeliveryMethodsRoute
|
|
569
|
+
SettingsGeneralRoute: typeof SettingsGeneralRoute
|
|
512
570
|
SettingsPackageTypesRoute: typeof SettingsPackageTypesRoute
|
|
513
571
|
SettingsPoliciesRoute: typeof SettingsPoliciesRoute
|
|
572
|
+
SettingsProfileRoute: typeof SettingsProfileRoute
|
|
514
573
|
SettingsStockLocationsRoute: typeof SettingsStockLocationsRoute
|
|
574
|
+
SettingsTaxesRoute: typeof SettingsTaxesRoute
|
|
515
575
|
SettingsTeamRoute: typeof SettingsTeamRoute
|
|
516
576
|
SettingsIndexRoute: typeof SettingsIndexRoute
|
|
517
577
|
}
|
|
518
578
|
|
|
519
579
|
const SettingsRouteChildren: SettingsRouteChildren = {
|
|
520
580
|
SettingsDeliveryMethodsRoute: SettingsDeliveryMethodsRoute,
|
|
581
|
+
SettingsGeneralRoute: SettingsGeneralRoute,
|
|
521
582
|
SettingsPackageTypesRoute: SettingsPackageTypesRoute,
|
|
522
583
|
SettingsPoliciesRoute: SettingsPoliciesRoute,
|
|
584
|
+
SettingsProfileRoute: SettingsProfileRoute,
|
|
523
585
|
SettingsStockLocationsRoute: SettingsStockLocationsRoute,
|
|
586
|
+
SettingsTaxesRoute: SettingsTaxesRoute,
|
|
524
587
|
SettingsTeamRoute: SettingsTeamRoute,
|
|
525
588
|
SettingsIndexRoute: SettingsIndexRoute,
|
|
526
589
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
-
import { ProfilePage } from '../../../pages/profile'
|
|
1
|
+
import { createFileRoute, redirect } from '@tanstack/react-router'
|
|
3
2
|
|
|
3
|
+
// The profile moved under settings; old links and bookmarks follow it there.
|
|
4
4
|
export const Route = createFileRoute('/_authenticated/$sellerId/profile')({
|
|
5
|
-
|
|
5
|
+
beforeLoad: ({ params }) => {
|
|
6
|
+
throw redirect({ to: '/$sellerId/settings/profile', params })
|
|
7
|
+
},
|
|
6
8
|
})
|
|
@@ -1,18 +1,14 @@
|
|
|
1
|
+
import { SettingsContainer } from '@spree/dashboard-core'
|
|
1
2
|
import { createFileRoute, Outlet } from '@tanstack/react-router'
|
|
2
3
|
|
|
3
4
|
export const Route = createFileRoute('/_authenticated/$sellerId/settings')({
|
|
4
5
|
component: SettingsLayout,
|
|
5
6
|
})
|
|
6
7
|
|
|
7
|
-
/**
|
|
8
|
-
* Content padding for the settings area. The rail itself lives in the chrome,
|
|
9
|
-
* one level up, so it can span full height beside the top bar — this layout
|
|
10
|
-
* owns only the right-hand column, matching the dashboard's settings layout.
|
|
11
|
-
*/
|
|
12
8
|
function SettingsLayout() {
|
|
13
9
|
return (
|
|
14
|
-
<
|
|
10
|
+
<SettingsContainer>
|
|
15
11
|
<Outlet />
|
|
16
|
-
</
|
|
12
|
+
</SettingsContainer>
|
|
17
13
|
)
|
|
18
14
|
}
|
package/src/schemas/account.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { AccountUpdateParams, MeResponse } from '@spree/seller-sdk'
|
|
|
3
3
|
import { z } from 'zod/v4'
|
|
4
4
|
|
|
5
5
|
// The signed-in person's own account (PATCH /me) — not the seller business
|
|
6
|
-
// they act for, which is `pages/profile.tsx`. Every field is optional: the
|
|
6
|
+
// they act for, which is `pages/profile-settings.tsx`. Every field is optional: the
|
|
7
7
|
// panel asks for a name but does not require one.
|
|
8
8
|
//
|
|
9
9
|
// The panel's languages are NOT listed here. They are derived at render time
|
package/src/tables/products.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PRODUCT_STATUSES } from '@spree/core'
|
|
2
2
|
import { defineTable, formatPrice, ProductStockSummary } from '@spree/dashboard-core'
|
|
3
|
-
import {
|
|
3
|
+
import { ProductIdentity, StatusBadge } from '@spree/dashboard-ui'
|
|
4
4
|
import { PackageIcon } from '@spree/dashboard-ui/icons'
|
|
5
5
|
import type { Product } from '@spree/seller-sdk'
|
|
6
6
|
import i18n from 'i18next'
|
|
@@ -19,10 +19,11 @@ defineTable<Product>('seller-products', {
|
|
|
19
19
|
sortable: true,
|
|
20
20
|
default: true,
|
|
21
21
|
render: (product) => (
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
<ProductIdentity
|
|
23
|
+
name={product.name}
|
|
24
|
+
thumbnailUrl={product.thumbnail_url}
|
|
25
|
+
render={<button type="button" data-product-id={product.id} />}
|
|
26
|
+
/>
|
|
26
27
|
),
|
|
27
28
|
},
|
|
28
29
|
{
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from 'react'
|
|
2
|
-
import { useTranslation } from 'react-i18next'
|
|
3
|
-
|
|
4
|
-
/** One labelled value in a read-only card, with a placeholder when unset. */
|
|
5
|
-
export function ReadRow({ label, children }: { label: string; children: ReactNode }) {
|
|
6
|
-
const { t } = useTranslation()
|
|
7
|
-
|
|
8
|
-
return (
|
|
9
|
-
<div className="flex items-start justify-between gap-3 text-sm">
|
|
10
|
-
<span className="text-muted-foreground">{label}</span>
|
|
11
|
-
<span className="text-right">
|
|
12
|
-
{children ?? <span className="text-muted-foreground">{t('profile.not_provided')}</span>}
|
|
13
|
-
</span>
|
|
14
|
-
</div>
|
|
15
|
-
)
|
|
16
|
-
}
|