@spree/docs 0.1.239 → 0.1.240
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/developer/dashboard/concepts.md +4 -2
- package/dist/developer/dashboard/customization/backend.md +5 -2
- package/dist/developer/dashboard/customization/navigation.md +1 -1
- package/dist/developer/dashboard/customization/quickstart.md +1 -1
- package/dist/developer/dashboard/overview.md +2 -2
- package/dist/developer/dashboard/plugins/distributing.md +1 -1
- package/dist/developer/dashboard/public-api.md +3 -2
- package/dist/developer/dashboard/recipes/attribute-end-to-end.md +3 -3
- package/dist/developer/dashboard/recipes/custom-form-field.md +1 -1
- package/dist/developer/dashboard/recipes/page-action-button.md +2 -2
- package/dist/developer/dashboard/recipes/sidebar-widget.md +2 -2
- package/dist/developer/dashboard/slots-catalog.md +60 -0
- package/package.json +1 -1
|
@@ -30,7 +30,9 @@ The dashboard is a **Single Page Application**: one HTML page, all routing clien
|
|
|
30
30
|
@spree/dashboard ─ app shell: routes, resource hooks, locales, vite config
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
[Overview](overview.md) covers the split in more detail. The short version: **`-ui`** has no providers or hooks (data comes via props); **`-core`** is the extension API and runtime services; **`-dashboard`** is the deployable app shell.
|
|
33
|
+
[Overview](overview.md) covers the split in more detail. The short version: **`-ui`** has no providers or hooks (data comes via props); **`-core`** is the extension API and runtime services; **`-dashboard`** is the deployable app shell.
|
|
34
|
+
|
|
35
|
+
The split matters when you publish a plugin: it consumes `-core` and `-ui` and never the app shell, which it plugs into rather than ships. In your own dashboard app the distinction is noise, so `@spree/dashboard` re-exports both — one import, and you never have to work out which package a given export lives in.
|
|
34
36
|
|
|
35
37
|
## How customization plugs in
|
|
36
38
|
|
|
@@ -53,7 +55,7 @@ The facade `defineDashboardPlugin({ nav, routes, slots, … })` groups all five
|
|
|
53
55
|
The signed-in admin's identity and abilities are exposed via three providers wrapping the app:
|
|
54
56
|
|
|
55
57
|
- `<AuthProvider>` — the current admin user; `useAuth()` returns `{ user, signIn, signOut, … }`
|
|
56
|
-
- `<PermissionProvider>` — CanCanCan abilities; `usePermissions()` returns `{
|
|
58
|
+
- `<PermissionProvider>` — CanCanCan abilities; `usePermissions()` returns `{ permissions, rules, isLoading }`, and the checks live on `permissions.can(...)`
|
|
57
59
|
- `<StoreProvider>` — current store + timezone + currency; `useStore()` returns `{ storeId, store, … }`
|
|
58
60
|
|
|
59
61
|
When you write a custom page or hook, pull from these. **Never reach into local state for identity** — it changes on store switch or session refresh, and the providers wire all that for you.
|
|
@@ -52,13 +52,16 @@ Resource methods are just `request<T>()` calls with the right path + types pre-b
|
|
|
52
52
|
The dashboard's data layer is React-Query — every `useFoo` hook is `useQuery({ queryKey, queryFn })` over an SDK call. Follow the same pattern for your own data:
|
|
53
53
|
|
|
54
54
|
```ts
|
|
55
|
-
import { adminClient } from '@spree/dashboard'
|
|
55
|
+
import { adminClient, useResourceKey } from '@spree/dashboard'
|
|
56
56
|
import { useQuery } from '@tanstack/react-query'
|
|
57
57
|
import type { AdminBrand } from './types'
|
|
58
58
|
|
|
59
59
|
export function useBrands(params?: { search?: string }) {
|
|
60
60
|
return useQuery({
|
|
61
|
-
|
|
61
|
+
// `useResourceKey` scopes the key to the current store. A bare
|
|
62
|
+
// `['brands', params]` key would serve one store's rows to another
|
|
63
|
+
// after a store switch.
|
|
64
|
+
queryKey: useResourceKey('brands', params),
|
|
62
65
|
queryFn: () =>
|
|
63
66
|
adminClient.request<{ data: AdminBrand[] }>('GET', '/brands', { params }),
|
|
64
67
|
staleTime: 30_000,
|
|
@@ -26,7 +26,7 @@ nav.add({
|
|
|
26
26
|
})
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
`position` controls ordering — built-ins
|
|
29
|
+
`position` controls ordering — built-ins run 50–600 (Getting Started 50 when shown, Home 100, Orders 200, Products 300, Inventory 350, Customers 400, Sellers 450, Loyalty 475, Promotions 500, Reports 600), leaving gaps to slot into. The default is 100. Keys must be unique, including against the built-in keys (`getting-started`, `home`, `orders`, `products`, `inventory`, `customers`, `sellers`, `loyalty`, `promotions`, `reports`, `settings`) — duplicates throw at boot.
|
|
30
30
|
|
|
31
31
|
### Add via `defineDashboardPlugin`
|
|
32
32
|
|
|
@@ -80,7 +80,7 @@ defineDashboardPlugin({
|
|
|
80
80
|
|
|
81
81
|
Save. Click "Analytics" — your page renders inside the dashboard chrome, at `/<store id>/analytics`.
|
|
82
82
|
|
|
83
|
-
> **WARNING:** Every `key` must be unique, including against the built-ins (`getting-started`, `home`, `orders`, `products`, `customers`, `promotions`, `reports`, `settings`). Registering a duplicate key throws at boot with a message naming the conflict.
|
|
83
|
+
> **WARNING:** Every `key` must be unique, including against the built-ins (`getting-started`, `home`, `orders`, `products`, `inventory`, `customers`, `sellers`, `loyalty`, `promotions`, `reports`, `settings`). Registering a duplicate key throws at boot with a message naming the conflict.
|
|
84
84
|
|
|
85
85
|
## What just happened
|
|
86
86
|
|
|
@@ -6,7 +6,7 @@ description: Learn about the Spree React Dashboard architecture and how to custo
|
|
|
6
6
|
|
|
7
7
|
> **INFO:** The React Dashboard is currently a **Developer Preview** — APIs may still change between releases. It is becoming the default Admin interface.
|
|
8
8
|
|
|
9
|
-
The React Dashboard is a React single-page application (SPA) that talks to the [Spree Admin API](../../api-reference/admin-api.md). It can be distributed with the Spree API or as a standalone npm package, and it can be extended with plugins. The dashboard is built with modern React libraries and patterns, including:
|
|
9
|
+
The React Dashboard is a React single-page application (SPA) that talks to the [Spree Admin API](../../api-reference/admin-api/introduction.md). It can be distributed with the Spree API or as a standalone npm package, and it can be extended with plugins. The dashboard is built with modern React libraries and patterns, including:
|
|
10
10
|
|
|
11
11
|
| Layer | Choice |
|
|
12
12
|
|---|---|
|
|
@@ -35,7 +35,7 @@ Most customization uses the registries in `@spree/dashboard-core` to add nav ent
|
|
|
35
35
|
|
|
36
36
|
**In your own dashboard app, import from `@spree/dashboard`.** It re-exports both packages, so you never have to work out which one an export lives in. A distributed plugin is the exception: it imports `@spree/dashboard-core` and `@spree/dashboard-ui` directly, because it plugs into the shell rather than shipping it.
|
|
37
37
|
|
|
38
|
-
You can also
|
|
38
|
+
You can also build your own dashboard using `@spree/dashboard-ui` primitives and `@spree/dashboard-core` registries, if you want to replace the default dashboard entirely. This is rare — most teams just add a few pages or cards to the existing dashboard.
|
|
39
39
|
|
|
40
40
|
## Get the dashboard
|
|
41
41
|
|
|
@@ -120,7 +120,7 @@ The registries are global singletons, so a key collision is the consumer's probl
|
|
|
120
120
|
- **Namespace locale keys** — `admin.acme_brands.title` not `admin.brands.title`.
|
|
121
121
|
- **Namespace API paths** — your Rails engine should mount under `/api/v3/admin/acme/...`, not `/api/v3/admin/brands` (which collides with core Brands if it ever ships).
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
Registering a key that already exists throws at boot, with a message naming the conflict. That is deliberate: a silently shadowed entry would leave the consumer with a plugin that half works, where a failure at startup points straight at the two plugins stepping on each other.
|
|
124
124
|
|
|
125
125
|
## Distribution channels
|
|
126
126
|
|
|
@@ -4,7 +4,7 @@ sidebarTitle: Public API
|
|
|
4
4
|
description: Every importable symbol the dashboard exposes — components, hooks, registries, providers, and the SDK client. Treat this as the surface you can safely depend on.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
The dashboard is structured as three packages
|
|
7
|
+
The dashboard is structured as three packages. This page lists the symbols we commit to keeping — **treat anything not listed as internal**, liable to move or be renamed without a deprecation cycle, even where the barrel happens to export it today. Never reach past a package entry point into `@spree/dashboard-core/src/...`.
|
|
8
8
|
|
|
9
9
|
## `@spree/dashboard-core`
|
|
10
10
|
|
|
@@ -51,7 +51,7 @@ import {
|
|
|
51
51
|
// Page chrome
|
|
52
52
|
PageHeader,
|
|
53
53
|
PageTabs,
|
|
54
|
-
|
|
54
|
+
MobileTopBar,
|
|
55
55
|
AppSidebar,
|
|
56
56
|
SettingsSidebar,
|
|
57
57
|
Slot,
|
|
@@ -92,6 +92,7 @@ import {
|
|
|
92
92
|
useCustomFields,
|
|
93
93
|
useDirectUpload, // ActiveStorage direct uploads
|
|
94
94
|
useExport, // CSV export job lifecycle
|
|
95
|
+
useResourceKey, // store-scoped query key — use it for every custom query
|
|
95
96
|
useResourceMutation, // useMutation wrapper with 422 handling
|
|
96
97
|
useHostForm, // the built-in form a slot widget renders inside (throws when absent)
|
|
97
98
|
useOptionalHostForm, // same, but null when the page has no host form
|
|
@@ -14,7 +14,7 @@ It's the full version of the [custom form field recipe](custom-form-field.md)
|
|
|
14
14
|
|
|
15
15
|
## First: should this be a custom field instead?
|
|
16
16
|
|
|
17
|
-
**Default to a [custom field definition](custom-form-field.md#path-a-custom-field-definition
|
|
17
|
+
**Default to a [custom field definition](custom-form-field.md#path-a-custom-field-definition-no-code).** It needs zero code — declare the field, and the product form renders and saves it. That covers most plugin data and everything merchants define themselves.
|
|
18
18
|
|
|
19
19
|
Graduate to a real database column only when the attribute needs **list operations or database guarantees**:
|
|
20
20
|
|
|
@@ -90,7 +90,7 @@ Add a Rails validation while you're here (`validates :lead_time_days, numericali
|
|
|
90
90
|
```tsx
|
|
91
91
|
// src/plugins.ts (or a plugin's entry module)
|
|
92
92
|
import { defineDashboardPlugin, i18n } from '@spree/dashboard'
|
|
93
|
-
import { LeadTimeCard } from './
|
|
93
|
+
import { LeadTimeCard } from './slots/lead-time-card'
|
|
94
94
|
|
|
95
95
|
i18n.addResourceBundle('en', 'translation', en, true, true)
|
|
96
96
|
|
|
@@ -123,7 +123,7 @@ defineDashboardPlugin({
|
|
|
123
123
|
The form widget binds to the host form — no `<form>` of its own, no save button:
|
|
124
124
|
|
|
125
125
|
```tsx
|
|
126
|
-
// src/
|
|
126
|
+
// src/slots/lead-time-card.tsx
|
|
127
127
|
import { Card, CardContent, CardHeader, CardTitle, Field, FieldError, FieldLabel, Input, useHostForm } from '@spree/dashboard'
|
|
128
128
|
import { useTranslation } from 'react-i18next'
|
|
129
129
|
|
|
@@ -93,7 +93,7 @@ defineDashboardPlugin({
|
|
|
93
93
|
The `product.form_sidebar` slot renders inside the product form. Bind your input to the host form with `useHostForm()` — no `<form>` of your own, no save button:
|
|
94
94
|
|
|
95
95
|
```tsx
|
|
96
|
-
// src/
|
|
96
|
+
// src/slots/tech-specs-card.tsx
|
|
97
97
|
import {
|
|
98
98
|
Card,
|
|
99
99
|
CardContent,
|
|
@@ -21,7 +21,7 @@ import { Button, adminClient, usePermissions, useResourceMutation } from '@spree
|
|
|
21
21
|
import { Send } from 'lucide-react'
|
|
22
22
|
|
|
23
23
|
interface Props {
|
|
24
|
-
resource: { id: string;
|
|
24
|
+
resource: { id: string; status: string } // Order
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export function SendInvoiceButton({ resource }: Props) {
|
|
@@ -34,7 +34,7 @@ export function SendInvoiceButton({ resource }: Props) {
|
|
|
34
34
|
|
|
35
35
|
// Only show to users who can act, and only on completed orders.
|
|
36
36
|
if (!permissions.can('update', 'Spree::Order')) return null
|
|
37
|
-
if (resource.
|
|
37
|
+
if (resource.status !== 'placed') return null
|
|
38
38
|
|
|
39
39
|
return (
|
|
40
40
|
<Button
|
|
@@ -11,7 +11,7 @@ This recipe builds a "Loyalty status" card for the customer detail page: read-on
|
|
|
11
11
|
## The component
|
|
12
12
|
|
|
13
13
|
```tsx
|
|
14
|
-
// src/
|
|
14
|
+
// src/slots/loyalty-status-card.tsx
|
|
15
15
|
import { useQuery } from '@tanstack/react-query'
|
|
16
16
|
import {
|
|
17
17
|
adminClient,
|
|
@@ -101,7 +101,7 @@ Worth noting:
|
|
|
101
101
|
```ts
|
|
102
102
|
// src/plugins.ts (in your dashboard app)
|
|
103
103
|
import { defineDashboardPlugin } from '@spree/dashboard'
|
|
104
|
-
import { LoyaltyStatusCard } from './
|
|
104
|
+
import { LoyaltyStatusCard } from './slots/loyalty-status-card'
|
|
105
105
|
|
|
106
106
|
defineDashboardPlugin({
|
|
107
107
|
slots: {
|
|
@@ -89,6 +89,66 @@ The resource detail pages each render a slot below their built-in cards. The con
|
|
|
89
89
|
| Context | `{ customer }` — the full `Customer` record |
|
|
90
90
|
| Host form | No — the page edits through sheets; widgets save via their own API calls |
|
|
91
91
|
|
|
92
|
+
### `collection.form_sidebar`
|
|
93
|
+
|
|
94
|
+
| Where | Collection detail page (`products/collections/$collectionId`), end of the sidebar column |
|
|
95
|
+
|---|---|
|
|
96
|
+
| Use | Collection-scoped cards — merchandising rules, feed settings, external sync state |
|
|
97
|
+
| Context | `{ collection }` — the `Collection` record |
|
|
98
|
+
| Host form | **Yes** — form key `collection` |
|
|
99
|
+
|
|
100
|
+
### `company.form_main`
|
|
101
|
+
|
|
102
|
+
| Where | Company detail page, end of the main column |
|
|
103
|
+
|---|---|
|
|
104
|
+
| Use | B2B account cards — credit terms, approval settings, external account references |
|
|
105
|
+
| Context | `{ company, kind, canEdit }` — `kind` is the company's own `kind`, `canEdit` whether the viewer may write |
|
|
106
|
+
| Host form | No — the widget owns its persistence |
|
|
107
|
+
|
|
108
|
+
### `company.form_sidebar`
|
|
109
|
+
|
|
110
|
+
| Where | Company detail page, end of the sidebar column |
|
|
111
|
+
|---|---|
|
|
112
|
+
| Use | Narrow company-scoped cards alongside the main form |
|
|
113
|
+
| Context | `{ company, kind, canEdit }` |
|
|
114
|
+
| Host form | No |
|
|
115
|
+
|
|
116
|
+
## Seller panel slots
|
|
117
|
+
|
|
118
|
+
Rendered by the marketplace seller panel (`@spree/seller-dashboard`) rather than the admin dashboard, and registered the same way.
|
|
119
|
+
|
|
120
|
+
### `seller.form_main`
|
|
121
|
+
|
|
122
|
+
| Where | Seller detail page, end of the main column |
|
|
123
|
+
|---|---|
|
|
124
|
+
| Use | Seller-scoped cards — payout configuration, onboarding state, compliance records |
|
|
125
|
+
| Context | `{ seller, canEdit }` |
|
|
126
|
+
| Host form | No |
|
|
127
|
+
|
|
128
|
+
### `seller.form_sidebar`
|
|
129
|
+
|
|
130
|
+
| Where | Seller detail page, end of the sidebar column |
|
|
131
|
+
|---|---|
|
|
132
|
+
| Use | Narrow seller-scoped cards |
|
|
133
|
+
| Context | `{ seller, canEdit }` |
|
|
134
|
+
| Host form | No |
|
|
135
|
+
|
|
136
|
+
### `seller.team.actions`
|
|
137
|
+
|
|
138
|
+
| Where | Seller team screen, in the header's action area |
|
|
139
|
+
|---|---|
|
|
140
|
+
| Use | Team-level actions — bulk invite, export, an external directory sync |
|
|
141
|
+
| Context | `{ sellerId }` |
|
|
142
|
+
| Host form | No |
|
|
143
|
+
|
|
144
|
+
### `seller.team.after`
|
|
145
|
+
|
|
146
|
+
| Where | Seller team screen, below the member list |
|
|
147
|
+
|---|---|
|
|
148
|
+
| Use | Anything that belongs under the team — audit log, role explainer, seat usage |
|
|
149
|
+
| Context | `{ sellerId }` |
|
|
150
|
+
| Host form | No |
|
|
151
|
+
|
|
92
152
|
## Page tabs slot
|
|
93
153
|
|
|
94
154
|
### `page.tabs` (default)
|