@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.
@@ -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. Plugins and customizations consume `-core` and `-ui`, never the 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 `{ can, cannot }`
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
- queryKey: ['brands', params],
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 use 100–600 (Home 100, Orders 200, Products 300, Customers 400, Promotions 500, Reports 600; a conditional Getting Started entry sits at 50), leaving gaps to slot into. The default is 100. Keys must be unique, including against the built-in keys (`getting-started`, `home`, `orders`, `products`, `customers`, `promotions`, `reports`, `settings`) — duplicates throw at boot.
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 built 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.
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
- The dashboard logs a warning in dev mode when two registry entries share a key. That's the consumer's signal that two plugins are stepping on each other — and it falls back to "last-registered wins", which is rarely what either plugin wanted.
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, each with a documented public surface. **Anything not listed here is internal** — don't import it via `@spree/dashboard-core/src/internal/...`, because we'll move or rename it without a deprecation cycle.
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
- TopBar,
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--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.
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 './fields/lead-time-card'
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/fields/lead-time-card.tsx
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/fields/tech-specs-card.tsx
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; state: string } // Order
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.state !== 'complete') return null
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/widgets/loyalty-status-card.tsx
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 './widgets/loyalty-status-card'
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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spree/docs",
3
- "version": "0.1.239",
3
+ "version": "0.1.240",
4
4
  "description": "Spree Commerce developer documentation for AI agents and local reference",
5
5
  "type": "module",
6
6
  "license": "CC-BY-4.0",