@spree/dashboard-core 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/dist/vite/index.js +81 -5
- package/package.json +19 -19
- package/src/api-client.ts +7 -1
- package/src/client.ts +2 -0
- package/src/components/address-form-dialog.tsx +75 -28
- package/src/components/app-shell.tsx +5 -4
- package/src/components/app-sidebar.tsx +2 -1
- package/src/components/bulk-action-bar.tsx +6 -5
- package/src/components/country-state-fields.tsx +3 -0
- package/src/components/currency-select.tsx +6 -4
- package/src/components/developer-inspector-drawer.tsx +322 -0
- package/src/components/file-upload-field.tsx +64 -37
- package/src/components/image-upload-field.tsx +4 -0
- package/src/components/import-wizard-dialog.tsx +14 -12
- package/src/components/invitation-acceptance.tsx +15 -11
- package/src/components/media-editor-dialog.tsx +131 -0
- package/src/components/media-picker-dialog.tsx +866 -0
- package/src/components/media-picker-sheet.tsx +13 -300
- package/src/components/mobile-top-bar.tsx +1 -1
- package/src/components/nav-main.tsx +13 -2
- package/src/components/page-header.tsx +57 -13
- package/src/components/page-tabs.tsx +1 -1
- package/src/components/product-stock-summary.tsx +1 -1
- package/src/components/read-row.tsx +38 -0
- package/src/components/resource-combobox.tsx +1 -1
- package/src/components/resource-image-field.tsx +150 -0
- package/src/components/resource-picker-sheet.tsx +2 -2
- package/src/components/resource-table.tsx +161 -40
- package/src/components/section-heading.tsx +2 -2
- package/src/components/settings-container.tsx +19 -0
- package/src/components/settings-fields.tsx +165 -0
- package/src/components/settings-section.tsx +46 -0
- package/src/components/settings-sidebar.tsx +35 -18
- package/src/components/sheet-top-bar.tsx +7 -8
- package/src/components/sidebar-search.tsx +11 -12
- package/src/components/sidebar-user.tsx +39 -44
- package/src/components/skip-link.tsx +1 -1
- package/src/components/store-switcher.tsx +1 -1
- package/src/components/table-toolbar.tsx +189 -323
- package/src/components/timezone-combobox.tsx +119 -0
- package/src/components/top-bar-breadcrumbs.tsx +1 -1
- package/src/hooks/use-stock-locations.ts +6 -2
- package/src/hooks/use-table-density.ts +50 -0
- package/src/index.ts +19 -2
- package/src/lib/curl.test.ts +18 -0
- package/src/lib/curl.ts +28 -0
- package/src/lib/developer-inspector.test.ts +169 -0
- package/src/lib/developer-inspector.ts +134 -0
- package/src/lib/docs.ts +3 -2
- package/src/lib/fulfillment-items.test.ts +51 -0
- package/src/lib/fulfillment-items.ts +53 -0
- package/src/lib/request-snippets.ts +41 -0
- package/src/lib/table-registry.ts +13 -6
- package/src/lib/timestamp-columns.tsx +1 -1
- package/src/locales/ar.json +120 -14
- package/src/locales/de.json +120 -14
- package/src/locales/en.json +120 -14
- package/src/locales/es.json +120 -14
- package/src/locales/fr.json +120 -14
- package/src/locales/ja.json +120 -14
- package/src/locales/pl.json +120 -14
- package/src/locales/vi.json +120 -14
- package/src/locales/zh-CN.json +120 -14
- package/src/pages/settings-index.tsx +79 -48
- package/src/pages/stock-locations.tsx +14 -6
- package/src/products/digital-asset-edit-sheet.tsx +1 -1
- package/src/products/digital-asset-provider-sheet.tsx +1 -1
- package/src/products/digital-assets-card.tsx +7 -7
- package/src/products/inventory-section.tsx +2 -2
- package/src/products/media-edit-sheet.tsx +136 -133
- package/src/products/product-form-cards.tsx +201 -70
- package/src/products/product-form-mapping.ts +1 -0
- package/src/products/product-schema.ts +8 -0
- package/src/products/product-types.ts +2 -0
- package/src/products/use-product-form-data.ts +30 -3
- package/src/products/variant-edit-sheet.tsx +3 -3
- package/src/products/variants-options-builder.tsx +13 -8
- package/src/products/variants-section.tsx +196 -4
- package/src/providers/auth-provider.tsx +2 -0
- package/src/providers/developer-inspector-provider.tsx +145 -0
- package/src/providers/json-link-resolver-provider.tsx +27 -0
- package/src/sellers/seller-schema.ts +107 -0
- package/src/tables/package-types.tsx +3 -3
- package/src/tables/stock-locations.tsx +15 -14
- package/src/virtual.d.ts +4 -0
- package/src/vite/index.ts +103 -5
|
@@ -197,7 +197,7 @@ function SignInForm({ invitationId, token, invitation, onAccepted }: FormProps)
|
|
|
197
197
|
<>
|
|
198
198
|
<InvitationIntro invitation={invitation} action={t('admin.invitation.sign_in_to_accept')} />
|
|
199
199
|
<form onSubmit={form.handleSubmit(onSubmit)} className="grid gap-6">
|
|
200
|
-
{errors.root && <p className="text-
|
|
200
|
+
{errors.root && <p className="text-base text-destructive">{errors.root.message}</p>}
|
|
201
201
|
<div className="grid gap-2">
|
|
202
202
|
<Label htmlFor="invitee-email">{t('admin.fields.email.label')}</Label>
|
|
203
203
|
<Input id="invitee-email" value={invitation.email} disabled />
|
|
@@ -211,7 +211,9 @@ function SignInForm({ invitationId, token, invitation, onAccepted }: FormProps)
|
|
|
211
211
|
aria-invalid={!!errors.password || undefined}
|
|
212
212
|
{...form.register('password')}
|
|
213
213
|
/>
|
|
214
|
-
{errors.password &&
|
|
214
|
+
{errors.password && (
|
|
215
|
+
<p className="text-base text-destructive">{errors.password.message}</p>
|
|
216
|
+
)}
|
|
215
217
|
</div>
|
|
216
218
|
<Button type="submit" className="w-full" disabled={isLoading}>
|
|
217
219
|
{isLoading ? t('admin.invitation.accepting') : t('admin.invitation.sign_in_and_accept')}
|
|
@@ -248,7 +250,7 @@ function SignUpForm({ invitationId, token, invitation, onAccepted }: FormProps)
|
|
|
248
250
|
<>
|
|
249
251
|
<InvitationIntro invitation={invitation} action={t('admin.invitation.create_to_accept')} />
|
|
250
252
|
<form onSubmit={form.handleSubmit(onSubmit)} className="grid gap-6">
|
|
251
|
-
{errors.root && <p className="text-
|
|
253
|
+
{errors.root && <p className="text-base text-destructive">{errors.root.message}</p>}
|
|
252
254
|
<div className="grid gap-2">
|
|
253
255
|
<Label htmlFor="invitee-email">{t('admin.fields.email.label')}</Label>
|
|
254
256
|
<Input id="invitee-email" value={invitation.email} disabled />
|
|
@@ -263,7 +265,7 @@ function SignUpForm({ invitationId, token, invitation, onAccepted }: FormProps)
|
|
|
263
265
|
{...form.register('first_name')}
|
|
264
266
|
/>
|
|
265
267
|
{errors.first_name && (
|
|
266
|
-
<p className="text-
|
|
268
|
+
<p className="text-base text-destructive">{errors.first_name.message}</p>
|
|
267
269
|
)}
|
|
268
270
|
</div>
|
|
269
271
|
<div className="grid gap-2">
|
|
@@ -274,7 +276,7 @@ function SignUpForm({ invitationId, token, invitation, onAccepted }: FormProps)
|
|
|
274
276
|
{...form.register('last_name')}
|
|
275
277
|
/>
|
|
276
278
|
{errors.last_name && (
|
|
277
|
-
<p className="text-
|
|
279
|
+
<p className="text-base text-destructive">{errors.last_name.message}</p>
|
|
278
280
|
)}
|
|
279
281
|
</div>
|
|
280
282
|
</div>
|
|
@@ -286,7 +288,9 @@ function SignUpForm({ invitationId, token, invitation, onAccepted }: FormProps)
|
|
|
286
288
|
aria-invalid={!!errors.password || undefined}
|
|
287
289
|
{...form.register('password')}
|
|
288
290
|
/>
|
|
289
|
-
{errors.password &&
|
|
291
|
+
{errors.password && (
|
|
292
|
+
<p className="text-base text-destructive">{errors.password.message}</p>
|
|
293
|
+
)}
|
|
290
294
|
</div>
|
|
291
295
|
<div className="grid gap-2">
|
|
292
296
|
<Label htmlFor="password_confirmation">
|
|
@@ -299,7 +303,7 @@ function SignUpForm({ invitationId, token, invitation, onAccepted }: FormProps)
|
|
|
299
303
|
{...form.register('password_confirmation')}
|
|
300
304
|
/>
|
|
301
305
|
{errors.password_confirmation && (
|
|
302
|
-
<p className="text-
|
|
306
|
+
<p className="text-base text-destructive">{errors.password_confirmation.message}</p>
|
|
303
307
|
)}
|
|
304
308
|
</div>
|
|
305
309
|
<Button type="submit" className="w-full" disabled={isLoading}>
|
|
@@ -336,7 +340,7 @@ function AcceptAsSignedIn({ invitationId, token, invitation, onAccepted }: FormP
|
|
|
336
340
|
invitation={invitation}
|
|
337
341
|
action={t('admin.invitation.signed_in_as_invitee', { email: user?.email })}
|
|
338
342
|
/>
|
|
339
|
-
{error && <p className="text-
|
|
343
|
+
{error && <p className="text-base text-destructive">{error}</p>}
|
|
340
344
|
<Button className="w-full" disabled={isLoading} onClick={onAccept}>
|
|
341
345
|
{isLoading ? t('admin.invitation.accepting') : t('admin.invitation.accept')}
|
|
342
346
|
</Button>
|
|
@@ -354,7 +358,7 @@ function SignedInAsAnotherAccount({ invitation }: { invitation: InvitationSummar
|
|
|
354
358
|
<h1 className="text-2xl font-bold">
|
|
355
359
|
{t('admin.invitation.join_store', { store: invitation.target.name })}
|
|
356
360
|
</h1>
|
|
357
|
-
<p className="text-
|
|
361
|
+
<p className="text-base text-muted-foreground">
|
|
358
362
|
{t('admin.invitation.signed_in_as_another_account', {
|
|
359
363
|
email: user?.email,
|
|
360
364
|
invitee: invitation.email,
|
|
@@ -389,7 +393,7 @@ function InvitationIntro({
|
|
|
389
393
|
<h1 className="text-2xl font-bold">
|
|
390
394
|
{t('admin.invitation.join_store', { store: invitation.target.name })}
|
|
391
395
|
</h1>
|
|
392
|
-
<p className="text-
|
|
396
|
+
<p className="text-base text-muted-foreground">{`${invitedPart}${rolePart}${action}`}</p>
|
|
393
397
|
</div>
|
|
394
398
|
)
|
|
395
399
|
}
|
|
@@ -405,7 +409,7 @@ function ErrorCard({ title, message }: { title: string; message: string }) {
|
|
|
405
409
|
return (
|
|
406
410
|
<div className="flex flex-col gap-2">
|
|
407
411
|
<h1 className="text-2xl font-bold">{title}</h1>
|
|
408
|
-
<p className="text-
|
|
412
|
+
<p className="text-base text-muted-foreground">{message}</p>
|
|
409
413
|
</div>
|
|
410
414
|
)
|
|
411
415
|
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Button,
|
|
3
|
+
cn,
|
|
4
|
+
Dialog,
|
|
5
|
+
DialogContent,
|
|
6
|
+
DialogDescription,
|
|
7
|
+
DialogTitle,
|
|
8
|
+
} from '@spree/dashboard-ui'
|
|
9
|
+
import { ArrowLeftIcon } from '@spree/dashboard-ui/icons'
|
|
10
|
+
import type { FormEvent, ReactNode } from 'react'
|
|
11
|
+
import { useTranslation } from 'react-i18next'
|
|
12
|
+
|
|
13
|
+
export interface MediaEditorDialogProps {
|
|
14
|
+
open: boolean
|
|
15
|
+
/** Closing goes through here — Escape, the back button — so a host can ask first. */
|
|
16
|
+
onOpenChange: (open: boolean) => void
|
|
17
|
+
title: ReactNode
|
|
18
|
+
/** Read by screen readers as the editor's description. */
|
|
19
|
+
description?: ReactNode
|
|
20
|
+
/** The top bar's end: Cancel / Save, Discard / Done. */
|
|
21
|
+
actions?: ReactNode
|
|
22
|
+
/** The file itself, centred on the stage — usually a `MediaPreview` with `fit="stage"`. */
|
|
23
|
+
preview: ReactNode
|
|
24
|
+
/** A pill floating under the picture, such as the focal point hint. */
|
|
25
|
+
hint?: ReactNode
|
|
26
|
+
/** Makes the editor a form, so Enter in a field and a submit button in `actions` save. */
|
|
27
|
+
onSubmit?: (event: FormEvent<HTMLFormElement>) => void
|
|
28
|
+
/** The side panel's sections — `MediaEditorSection`s. */
|
|
29
|
+
children: ReactNode
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A full-screen editor for one media file: a top bar with the way out, the
|
|
34
|
+
* file's name and its save actions; the file on a dark stage, as large as the
|
|
35
|
+
* window allows; and a panel of sections beside it.
|
|
36
|
+
*
|
|
37
|
+
* Always dark, whatever the admin's theme — a picture is judged against a
|
|
38
|
+
* neutral ground, the way photo tools show it. Headless about saving: each
|
|
39
|
+
* host keeps its own model (the product form's Done, the library's Save).
|
|
40
|
+
*/
|
|
41
|
+
export function MediaEditorDialog({
|
|
42
|
+
open,
|
|
43
|
+
onOpenChange,
|
|
44
|
+
title,
|
|
45
|
+
description,
|
|
46
|
+
actions,
|
|
47
|
+
preview,
|
|
48
|
+
hint,
|
|
49
|
+
onSubmit,
|
|
50
|
+
children,
|
|
51
|
+
}: MediaEditorDialogProps) {
|
|
52
|
+
const { t } = useTranslation()
|
|
53
|
+
|
|
54
|
+
const layout = (
|
|
55
|
+
<>
|
|
56
|
+
<header className="flex h-14 shrink-0 items-center gap-2 border-b border-border px-3">
|
|
57
|
+
<Button
|
|
58
|
+
type="button"
|
|
59
|
+
variant="ghost"
|
|
60
|
+
size="icon-sm"
|
|
61
|
+
onClick={() => onOpenChange(false)}
|
|
62
|
+
aria-label={t('admin.actions.close')}
|
|
63
|
+
>
|
|
64
|
+
<ArrowLeftIcon className="size-4 rtl:rotate-180" />
|
|
65
|
+
</Button>
|
|
66
|
+
<DialogTitle className="min-w-0 truncate font-medium text-base">{title}</DialogTitle>
|
|
67
|
+
{description && <DialogDescription className="sr-only">{description}</DialogDescription>}
|
|
68
|
+
<div className="ms-auto flex shrink-0 items-center gap-2">{actions}</div>
|
|
69
|
+
</header>
|
|
70
|
+
|
|
71
|
+
<div className="flex min-h-0 flex-1 flex-col overflow-y-auto md:flex-row md:overflow-hidden">
|
|
72
|
+
{/* A faint dot grid, so the edge of a picture with a dark or
|
|
73
|
+
transparent background still reads against the stage. */}
|
|
74
|
+
<div className="relative flex min-h-[50vh] flex-1 items-center justify-center bg-[radial-gradient(circle,oklch(1_0_0/0.07)_1px,transparent_1px)] bg-size-[20px_20px] p-6 pb-20 md:min-h-0 md:p-10 md:pb-20">
|
|
75
|
+
{preview}
|
|
76
|
+
{hint && (
|
|
77
|
+
<div className="absolute bottom-5 left-1/2 flex max-w-[calc(100%-2rem)] -translate-x-1/2 items-center gap-2 rounded-full border border-border bg-card px-3 py-1.5 text-base shadow-lg">
|
|
78
|
+
{hint}
|
|
79
|
+
</div>
|
|
80
|
+
)}
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<aside className="flex w-full shrink-0 flex-col gap-3 border-border border-t p-3 md:w-80 md:overflow-y-auto md:border-t-0 md:border-s">
|
|
84
|
+
{children}
|
|
85
|
+
</aside>
|
|
86
|
+
</div>
|
|
87
|
+
</>
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
92
|
+
<DialogContent
|
|
93
|
+
fullScreen
|
|
94
|
+
showCloseButton={false}
|
|
95
|
+
// Edge to edge, and a dark scope for everything inside it.
|
|
96
|
+
className="dark inset-0 gap-0 rounded-none border-0 bg-background p-0 text-foreground"
|
|
97
|
+
>
|
|
98
|
+
{onSubmit ? (
|
|
99
|
+
<form onSubmit={onSubmit} className="flex min-h-0 flex-1 flex-col">
|
|
100
|
+
{layout}
|
|
101
|
+
</form>
|
|
102
|
+
) : (
|
|
103
|
+
layout
|
|
104
|
+
)}
|
|
105
|
+
</DialogContent>
|
|
106
|
+
</Dialog>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** One card of the editor's side panel. */
|
|
111
|
+
export function MediaEditorSection({
|
|
112
|
+
icon,
|
|
113
|
+
title,
|
|
114
|
+
children,
|
|
115
|
+
className,
|
|
116
|
+
}: {
|
|
117
|
+
icon?: ReactNode
|
|
118
|
+
title: ReactNode
|
|
119
|
+
children: ReactNode
|
|
120
|
+
className?: string
|
|
121
|
+
}) {
|
|
122
|
+
return (
|
|
123
|
+
<section className={cn('flex flex-col gap-4 rounded-xl bg-card p-4', className)}>
|
|
124
|
+
<h3 className="flex items-center gap-2 font-medium text-base [&_svg]:size-4 [&_svg]:text-muted-foreground">
|
|
125
|
+
{icon}
|
|
126
|
+
{title}
|
|
127
|
+
</h3>
|
|
128
|
+
{children}
|
|
129
|
+
</section>
|
|
130
|
+
)
|
|
131
|
+
}
|