@spree/docs 0.1.282 → 0.1.284
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/customization/api.md +13 -0
- package/dist/developer/dashboard/customization/slots.md +1 -1
- package/dist/developer/dashboard/recipes/custom-form-field.md +1 -1
- package/dist/developer/dashboard/slots-catalog.md +214 -54
- package/dist/developer/how-to/build-a-marketplace.md +1 -1
- package/dist/developer/upgrades/5.6-to-6.0.md +1 -1
- package/package.json +1 -1
|
@@ -209,6 +209,19 @@ already permits replaces its filter rather than widening it.
|
|
|
209
209
|
> which need no code and are filterable and sortable. This is for extensions
|
|
210
210
|
> that add real database columns.
|
|
211
211
|
|
|
212
|
+
### Writable by sellers
|
|
213
|
+
|
|
214
|
+
The Seller API accepts a narrower set of attributes than the Admin API, so the
|
|
215
|
+
list above does not apply to it — an attribute you open to operators stays
|
|
216
|
+
operator-only. To let sellers write it too, for example from a field on the
|
|
217
|
+
seller panel's product page, declare it again on the seller list:
|
|
218
|
+
|
|
219
|
+
```ruby config/initializers/spree.rb
|
|
220
|
+
Spree::Product.additional_seller_permitted_attributes += [:brand_id]
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Only declare attributes a seller may safely set on their own records.
|
|
224
|
+
|
|
212
225
|
Two endpoints deliberately ignore this hook because their parameters are
|
|
213
226
|
authorization data rather than resource data: API keys and invitations.
|
|
214
227
|
|
|
@@ -77,7 +77,7 @@ The entry-level `if` predicate also exists, but it only receives the slot's own
|
|
|
77
77
|
|
|
78
78
|
## Binding to the host form
|
|
79
79
|
|
|
80
|
-
Slots on form pages (product, category, store settings — see the [slots catalog](../slots-catalog.md#detail-page-
|
|
80
|
+
Slots on form pages (product, category, collection, catalog, price list, promotion, store settings — see the [slots catalog](../slots-catalog.md#detail-page-slots) for which) render **inside the page's `<form>`** and expose its react-hook-form instance. A widget can register inputs against it with `useHostForm()` — they hydrate from the API, flip the Save button on change, and persist in the page's own save, with no save logic in the widget:
|
|
81
81
|
|
|
82
82
|
```tsx
|
|
83
83
|
import { useHostForm } from '@spree/dashboard'
|
|
@@ -157,7 +157,7 @@ Extension fields are validated by the **server** — a Rails validation failing
|
|
|
157
157
|
|
|
158
158
|
### Which forms support this
|
|
159
159
|
|
|
160
|
-
`useHostForm()` works wherever a built-in form exposes its form context — currently the **product** (edit + new), **category** (edit + new), and **store settings** forms, with matching slots (`product.form_sidebar`, `category.form_sidebar`, `store.form_main`) and form keys (`product`, `category`, `store`). Orders and customers have no page-wide form — their sheets own their edits — so slot widgets there manage their own persistence; use `useOptionalHostForm()` to write a widget that adapts to both contexts.
|
|
160
|
+
`useHostForm()` works wherever a built-in form exposes its form context — currently the **product** (edit + new), **category** (edit + new), **collection**, **catalog**, **price list** (edit + new), **promotion** (edit + new), and **store settings** forms, with matching slots (`product.form_sidebar`, `category.form_sidebar`, `collection.form_sidebar`, `catalog.form_sidebar`, `price_list.form_sidebar`, `promotion.form_sidebar`, `store.form_main`) and form keys (`product`, `category`, `collection`, `catalog`, `price_list`, `promotion`, `store`). The [slots catalog](../slots-catalog.md#detail-page-slots) marks which pages have a host form. Orders and customers have no page-wide form — their sheets own their edits — so slot widgets there manage their own persistence; use `useOptionalHostForm()` to write a widget that adapts to both contexts.
|
|
161
161
|
|
|
162
162
|
## Which path should I pick?
|
|
163
163
|
|
|
@@ -4,10 +4,36 @@ sidebarTitle: Slots catalog
|
|
|
4
4
|
description: Every named slot the dashboard renders today. Each entry lists the host page, the slot name, and the context the slot's components receive.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
This is the canonical list of slots the dashboard currently
|
|
7
|
+
This is the canonical list of slots the admin dashboard and the seller panel currently expose. The source of truth is the `<Slot name="…">` call sites in the dashboard source (linked under Reference below); each entry here records the host page, the slot name, and the context your component receives.
|
|
8
8
|
|
|
9
9
|
If you need a new injection point in a built-in page, open a PR adding `<Slot name="..." context={...} />` and a documentation entry here — that's the contract for new slots.
|
|
10
10
|
|
|
11
|
+
## At a glance
|
|
12
|
+
|
|
13
|
+
| Area | Page | Slots |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| Every page | Page header | `page.actions`, `page.actions_dropdown` |
|
|
16
|
+
| Every page | Tabbed sub-navigation | `page.tabs` |
|
|
17
|
+
| Products | Product | `product.form_sidebar` |
|
|
18
|
+
| Products | Category | `category.form_sidebar` |
|
|
19
|
+
| Products | Collection | `collection.form_sidebar` |
|
|
20
|
+
| Products | Catalog | `catalog.form_sidebar` |
|
|
21
|
+
| Products | Price list | `price_list.form_sidebar`, `price_list.rule_form.<type>` |
|
|
22
|
+
| Orders | Order | `order.form_sidebar` |
|
|
23
|
+
| Customers | Customer | `customer.form_sidebar` |
|
|
24
|
+
| Customers | Company | `company.form_main`, `company.form_sidebar`, `company_membership.*` |
|
|
25
|
+
| Promotions | Promotion | `promotion.form_sidebar`, `promotion.rule_form.<type>`, `promotion.action_form.<type>` |
|
|
26
|
+
| Inventory | Purchase order | `purchase_order.form_sidebar` |
|
|
27
|
+
| Inventory | Stock transfer | `stock_transfer.form_sidebar` |
|
|
28
|
+
| Sellers | Seller | `seller.form_main`, `seller.form_sidebar` |
|
|
29
|
+
| Sellers | Seller payout | `seller_payout.form_main`, `seller_payout.form_sidebar` |
|
|
30
|
+
| Settings | Store | `store.form_main` |
|
|
31
|
+
| Settings | Payment method | `payment_method.*.<provider>` |
|
|
32
|
+
| Settings | Webhook endpoint | `webhook_endpoint.form_sidebar` |
|
|
33
|
+
| Onboarding | Getting started | `getting-started.task.<name>` |
|
|
34
|
+
| Sign-in | No store access | `no_store_access` |
|
|
35
|
+
| Seller panel | Product, order, payout, profile, team | `seller.product.*`, `seller.order.*`, `seller.payout.*`, `seller.profile.*`, `seller.team.*` |
|
|
36
|
+
|
|
11
37
|
## Ambient context
|
|
12
38
|
|
|
13
39
|
Ambient context (`permissions`, `store`, `user` merged into every slot's props) is planned but **not wired up yet** — today slot components receive only the slot-specific context listed below. Until it lands, read those values with the hooks instead:
|
|
@@ -43,13 +69,30 @@ Rendered by `<PageHeader>` (`@spree/dashboard-core/components/page-header.tsx`)
|
|
|
43
69
|
| Use | Add menu items (`<DropdownMenuItem>`) for secondary actions |
|
|
44
70
|
| Context | `{ resource, ...slotContext }` — same as `page.actions` |
|
|
45
71
|
|
|
46
|
-
##
|
|
72
|
+
## Page tabs slot
|
|
73
|
+
|
|
74
|
+
### `page.tabs` (default)
|
|
75
|
+
|
|
76
|
+
Rendered by `<PageTabs>` at the right edge of any tabbed sub-nav.
|
|
77
|
+
|
|
78
|
+
| Where | After the built-in tab strip |
|
|
79
|
+
|---|---|
|
|
80
|
+
| Use | Append your own tab(s) to a sub-route — typically for plugin-owned views inside an existing resource (e.g., "Returns" tab on the order detail) |
|
|
81
|
+
| Context | `{ tabs, ...slotContext }` — `tabs` is the array of built-in tabs (so you can inspect or filter by them) |
|
|
82
|
+
|
|
83
|
+
Some pages override `slotName` to scope tabs to a single resource (e.g., `slotName="order.tabs"`). The catalog will grow these as we wire them up; today only the default `page.tabs` name is in production use.
|
|
84
|
+
|
|
85
|
+
## Detail-page slots
|
|
86
|
+
|
|
87
|
+
Every resource detail page renders a slot at the end of its sidebar column, and some also at the end of the main column. The slot name starts with the resource name, and the context key matches it.
|
|
47
88
|
|
|
48
|
-
|
|
89
|
+
**Host form.** On pages marked *host form: yes*, the slot renders inside the page's own `<form>` and exposes it — widgets can bind inputs via [`useHostForm()`](recipes/custom-form-field.md) that hydrate, dirty-track, and save with the page's Save button. The form key is what you register extension fields under in `formFields`. On every other page, widgets own their persistence; use `useOptionalHostForm()` to write a widget that adapts to both.
|
|
49
90
|
|
|
50
|
-
**
|
|
91
|
+
> **NOTE:** On host-form pages your widget renders inside the page's `<form>` element, so a `<Button>` in it submits the page unless you give it `type="button"`.
|
|
51
92
|
|
|
52
|
-
###
|
|
93
|
+
### Products
|
|
94
|
+
|
|
95
|
+
#### `product.form_sidebar`
|
|
53
96
|
|
|
54
97
|
| Where | Product detail page (`products/$productId`), end of the sidebar column |
|
|
55
98
|
|---|---|
|
|
@@ -57,7 +100,7 @@ The resource detail pages each render a slot below their built-in cards. The con
|
|
|
57
100
|
| Context | `{ product }` — the full `Product` record from the Admin API |
|
|
58
101
|
| Host form | **Yes** — form key `product` |
|
|
59
102
|
|
|
60
|
-
|
|
103
|
+
#### `category.form_sidebar`
|
|
61
104
|
|
|
62
105
|
| Where | Category detail page (`products/categories/$categoryId`), end of the sidebar column |
|
|
63
106
|
|---|---|
|
|
@@ -65,15 +108,33 @@ The resource detail pages each render a slot below their built-in cards. The con
|
|
|
65
108
|
| Context | `{ category }` — the `Category` record (may be briefly `undefined` while refetching) |
|
|
66
109
|
| Host form | **Yes** — form key `category` |
|
|
67
110
|
|
|
68
|
-
|
|
111
|
+
#### `collection.form_sidebar`
|
|
69
112
|
|
|
70
|
-
| Where |
|
|
113
|
+
| Where | Collection detail page (`products/collections/$collectionId`), end of the sidebar column |
|
|
71
114
|
|---|---|
|
|
72
|
-
| Use |
|
|
73
|
-
| Context | `{
|
|
74
|
-
| Host form | **Yes** — form key `
|
|
115
|
+
| Use | Collection-scoped cards — merchandising rules, feed settings, external sync state |
|
|
116
|
+
| Context | `{ collection }` — the `Collection` record |
|
|
117
|
+
| Host form | **Yes** — form key `collection` |
|
|
75
118
|
|
|
76
|
-
|
|
119
|
+
#### `catalog.form_sidebar`
|
|
120
|
+
|
|
121
|
+
| Where | Catalog detail page (`products/catalogs/$catalogId`), end of the sidebar column |
|
|
122
|
+
|---|---|
|
|
123
|
+
| Use | B2B catalog cards — ERP contract references, approval state, export settings |
|
|
124
|
+
| Context | `{ catalog, canEdit }` — the `Catalog` record; `canEdit` is whether the viewer may update it |
|
|
125
|
+
| Host form | **Yes** — form key `catalog` |
|
|
126
|
+
|
|
127
|
+
#### `price_list.form_sidebar`
|
|
128
|
+
|
|
129
|
+
| Where | Price list create and detail pages (`products/price-lists/new`, `products/price-lists/$priceListId`), end of the sidebar column |
|
|
130
|
+
|---|---|
|
|
131
|
+
| Use | Price-list cards — sync state with an external pricing engine, audit notes |
|
|
132
|
+
| Context | `{ priceList, mode }` — `mode` is `'create'` or `'edit'`; `priceList` is `undefined` in create mode |
|
|
133
|
+
| Host form | **Yes** — form key `price_list` |
|
|
134
|
+
|
|
135
|
+
### Orders and customers
|
|
136
|
+
|
|
137
|
+
#### `order.form_sidebar`
|
|
77
138
|
|
|
78
139
|
| Where | Order detail page (`orders/$orderId`), end of the sidebar column |
|
|
79
140
|
|---|---|
|
|
@@ -81,7 +142,7 @@ The resource detail pages each render a slot below their built-in cards. The con
|
|
|
81
142
|
| Context | `{ order }` — the full `Order` record |
|
|
82
143
|
| Host form | No — widgets save via their own API calls |
|
|
83
144
|
|
|
84
|
-
|
|
145
|
+
#### `customer.form_sidebar`
|
|
85
146
|
|
|
86
147
|
| Where | Customer detail page (`customers/$customerId`), end of the sidebar column |
|
|
87
148
|
|---|---|
|
|
@@ -89,23 +150,15 @@ The resource detail pages each render a slot below their built-in cards. The con
|
|
|
89
150
|
| Context | `{ customer }` — the full `Customer` record |
|
|
90
151
|
| Host form | No — the page edits through sheets; widgets save via their own API calls |
|
|
91
152
|
|
|
92
|
-
|
|
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`
|
|
153
|
+
#### `company.form_main`
|
|
101
154
|
|
|
102
|
-
| Where | Company detail page, end of the main column |
|
|
155
|
+
| Where | Company detail page (`companies/$companyId`), end of the main column |
|
|
103
156
|
|---|---|
|
|
104
157
|
| Use | B2B account cards — credit terms, approval settings, external account references |
|
|
105
158
|
| Context | `{ company, kind, canEdit }` — `kind` is the company's own `kind`, `canEdit` whether the viewer may write |
|
|
106
159
|
| Host form | No — the widget owns its persistence |
|
|
107
160
|
|
|
108
|
-
|
|
161
|
+
#### `company.form_sidebar`
|
|
109
162
|
|
|
110
163
|
| Where | Company detail page, end of the sidebar column |
|
|
111
164
|
|---|---|
|
|
@@ -113,81 +166,187 @@ The resource detail pages each render a slot below their built-in cards. The con
|
|
|
113
166
|
| Context | `{ company, kind, canEdit }` |
|
|
114
167
|
| Host form | No |
|
|
115
168
|
|
|
116
|
-
|
|
169
|
+
#### Company membership slots
|
|
170
|
+
|
|
171
|
+
The company's member list and its "Add member" sheet carry three smaller slots, built for an extension that gives members roles.
|
|
172
|
+
|
|
173
|
+
| Slot | Where | Context |
|
|
174
|
+
|---|---|---|
|
|
175
|
+
| `company_membership.row_meta` | Under each member's email in the member list | `{ membership, companyId, canEdit }` |
|
|
176
|
+
| `company_membership.row_actions` | Each member's actions menu, above "Remove member" — render `<DropdownMenuItem>`s | `{ membership, companyId }` |
|
|
177
|
+
| `company_membership.form_fields` | The "Add member" sheet, below the email field. **Replaces** a built-in note about roles | `{ companyId, onChange }` — call `onChange(params)` and those params are merged into the request that adds the member |
|
|
178
|
+
|
|
179
|
+
### Promotions
|
|
117
180
|
|
|
118
|
-
|
|
181
|
+
#### `promotion.form_sidebar`
|
|
119
182
|
|
|
120
|
-
|
|
183
|
+
| Where | Promotion create and detail pages (`promotions/new`, `promotions/$promotionId`), end of the sidebar column |
|
|
184
|
+
|---|---|
|
|
185
|
+
| Use | Campaign attribution, budget tracking, external coupon-system sync |
|
|
186
|
+
| Context | `{ promotion, mode }` — `mode` is `'create'` or `'edit'`; `promotion` is `undefined` in create mode |
|
|
187
|
+
| Host form | **Yes** — form key `promotion` |
|
|
188
|
+
|
|
189
|
+
### Inventory
|
|
190
|
+
|
|
191
|
+
#### `purchase_order.form_sidebar`
|
|
121
192
|
|
|
122
|
-
| Where |
|
|
193
|
+
| Where | Purchase order detail page (`purchase-orders/$purchaseOrderId`), end of the sidebar column |
|
|
194
|
+
|---|---|
|
|
195
|
+
| Use | Supplier portal links, ERP sync status, landed-cost notes |
|
|
196
|
+
| Context | `{ purchaseOrder }` — the full `PurchaseOrder` record |
|
|
197
|
+
| Host form | No |
|
|
198
|
+
|
|
199
|
+
#### `stock_transfer.form_sidebar`
|
|
200
|
+
|
|
201
|
+
| Where | Stock transfer detail page (`transfers/$transferId`), end of the sidebar column |
|
|
202
|
+
|---|---|
|
|
203
|
+
| Use | Carrier tracking, warehouse-system sync status |
|
|
204
|
+
| Context | `{ transfer }` — the full `StockTransfer` record |
|
|
205
|
+
| Host form | No |
|
|
206
|
+
|
|
207
|
+
### Sellers (marketplace)
|
|
208
|
+
|
|
209
|
+
These slots are on the store owner's seller management pages in the admin dashboard. For the seller's own view, see the seller panel slots below.
|
|
210
|
+
|
|
211
|
+
#### `seller.form_main`
|
|
212
|
+
|
|
213
|
+
| Where | Seller detail page (`sellers/$sellerId`), end of the main column |
|
|
123
214
|
|---|---|
|
|
124
215
|
| Use | Seller-scoped cards — payout configuration, onboarding state, compliance records |
|
|
125
|
-
| Context | `{ seller, canEdit }` |
|
|
216
|
+
| Context | `{ seller, canEdit }` — the full `Seller` record; `canEdit` is whether the viewer may update it |
|
|
126
217
|
| Host form | No |
|
|
127
218
|
|
|
128
|
-
|
|
219
|
+
#### `seller.form_sidebar`
|
|
129
220
|
|
|
130
221
|
| Where | Seller detail page, end of the sidebar column |
|
|
131
222
|
|---|---|
|
|
132
|
-
| Use | Narrow seller-scoped cards |
|
|
223
|
+
| Use | Narrow seller-scoped cards — risk score, verification status, external account links |
|
|
133
224
|
| Context | `{ seller, canEdit }` |
|
|
134
225
|
| Host form | No |
|
|
135
226
|
|
|
136
|
-
|
|
227
|
+
#### `seller_payout.form_main`
|
|
137
228
|
|
|
138
|
-
| Where | Seller
|
|
229
|
+
| Where | Seller payout detail page (`sellers/payouts/$payoutId`), end of the main column |
|
|
139
230
|
|---|---|
|
|
140
|
-
| Use |
|
|
141
|
-
| Context | `{
|
|
231
|
+
| Use | Payout-scoped cards — remittance advice, the transfer as seen by the payment provider |
|
|
232
|
+
| Context | `{ payout }` — the full `SellerPayout` record |
|
|
142
233
|
| Host form | No |
|
|
143
234
|
|
|
144
|
-
|
|
235
|
+
#### `seller_payout.form_sidebar`
|
|
145
236
|
|
|
146
|
-
| Where | Seller
|
|
237
|
+
| Where | Seller payout detail page, end of the sidebar column |
|
|
147
238
|
|---|---|
|
|
148
|
-
| Use |
|
|
149
|
-
| Context | `{
|
|
239
|
+
| Use | Accounting-system sync status, links to the provider's dashboard |
|
|
240
|
+
| Context | `{ payout }` |
|
|
150
241
|
| Host form | No |
|
|
151
242
|
|
|
152
|
-
|
|
243
|
+
### Settings
|
|
153
244
|
|
|
154
|
-
|
|
245
|
+
#### `store.form_main`
|
|
155
246
|
|
|
156
|
-
|
|
247
|
+
| Where | Store settings page (`settings/store`), end of the main column |
|
|
248
|
+
|---|---|
|
|
249
|
+
| Use | Store-level settings a plugin owns — integration toggles, account linking |
|
|
250
|
+
| Context | `{ store }` — the full `Store` record |
|
|
251
|
+
| Host form | **Yes** — form key `store` |
|
|
157
252
|
|
|
158
|
-
|
|
253
|
+
#### `webhook_endpoint.form_sidebar`
|
|
254
|
+
|
|
255
|
+
| Where | Webhook endpoint detail page (`settings/webhooks/$webhookEndpointId`), end of the sidebar column |
|
|
159
256
|
|---|---|
|
|
160
|
-
| Use |
|
|
161
|
-
| Context | `{
|
|
257
|
+
| Use | Notes on the receiving system, links to its logs, a "send test event" action |
|
|
258
|
+
| Context | `{ endpoint }` — the full `WebhookEndpoint` record |
|
|
259
|
+
| Host form | No |
|
|
162
260
|
|
|
163
|
-
|
|
261
|
+
## Seller panel slots
|
|
262
|
+
|
|
263
|
+
Rendered by the marketplace seller panel (`@spree/seller-dashboard`) rather than the admin dashboard, and registered the same way, in the seller panel's own plugins. Their names start with `seller.` followed by the page — `seller.form_main` and `seller.form_sidebar` above belong to the admin dashboard instead.
|
|
264
|
+
|
|
265
|
+
The product page is a host form with the form key `seller.product`: fields a widget binds with `useHostForm()` save with the product. The Seller API accepts only the attributes your extension declares in `additional_seller_permitted_attributes` — see [Writable by sellers](../customization/api.md#writable-by-sellers). The other seller panel pages have no page-wide form, so widgets there save via their own Seller API calls.
|
|
266
|
+
|
|
267
|
+
| Slot | Where | Context |
|
|
268
|
+
|---|---|---|
|
|
269
|
+
| `seller.product.form_sidebar` | Product create and edit pages, end of the sidebar column | `{ product, mode }` — `mode` is `'new'` or `'edit'`; `product` is `undefined` while creating |
|
|
270
|
+
| `seller.order.form_sidebar` | Order detail page, end of the sidebar column | `{ order }` |
|
|
271
|
+
| `seller.payout.form_sidebar` | Payout detail page, end of the sidebar column | `{ payout }` |
|
|
272
|
+
| `seller.profile.form_main` | Profile page, end of the main column | `{ profile }` |
|
|
273
|
+
| `seller.profile.form_sidebar` | Profile page, end of the sidebar column | `{ profile }` |
|
|
274
|
+
| `seller.team.actions` | Team page, in the header's action area — bulk invite, export, an external directory sync | `{ sellerId }` |
|
|
275
|
+
| `seller.team.after` | Team page, below the member list — audit log, role explainer, seat usage | `{ sellerId }` |
|
|
276
|
+
|
|
277
|
+
## Replaceable slots
|
|
278
|
+
|
|
279
|
+
These slots render built-in content when nothing is registered. Registering a component **replaces** that content rather than adding to it.
|
|
280
|
+
|
|
281
|
+
### `no_store_access`
|
|
282
|
+
|
|
283
|
+
| Where | The screen shown to a signed-in admin who has a role on no store |
|
|
284
|
+
|---|---|
|
|
285
|
+
| Use | Replace the built-in "no access" message — for example, with a request-access form or a link to your own onboarding |
|
|
286
|
+
| Context | `{ user, signOut }` — `user` is the signed-in admin; `signOut()` ends the session |
|
|
287
|
+
|
|
288
|
+
Import the name as `NO_STORE_ACCESS_SLOT` from `@spree/dashboard-core` rather than typing it.
|
|
289
|
+
|
|
290
|
+
### `getting-started.task.<name>`
|
|
291
|
+
|
|
292
|
+
| Where | The body of one step on the Getting Started checklist (`getting-started`) |
|
|
293
|
+
|---|---|
|
|
294
|
+
| Use | Replace a step's description and link with your own flow — or give a setup task your extension added on the backend its own UI |
|
|
295
|
+
| Context | `{ task, store, storeId }` — `task` is the `SetupTask` record (`name`, `done`, …) |
|
|
296
|
+
|
|
297
|
+
`<name>` is the task's `name` as the API returns it.
|
|
298
|
+
|
|
299
|
+
## Editor slots (dynamic)
|
|
300
|
+
|
|
301
|
+
Promotions, price lists, and payment methods each pick their editor by type. The slot names are computed from the type's `api_type` shorthand, so registering against the right name is what hooks your editor in. The promotion and price list editor slots **replace** the built-in editor, which is generated from the type's preference schema.
|
|
302
|
+
|
|
303
|
+
### `promotion.rule_form.<type>`
|
|
304
|
+
|
|
305
|
+
| Where | The sheet that edits one promotion rule (e.g. `promotion.rule_form.product`) |
|
|
306
|
+
|---|---|
|
|
307
|
+
| Use | A custom editor for a rule whose settings don't fit the generated form |
|
|
308
|
+
| Context | `{ draft, onSave, onClose }` — edit `draft` locally and call `onSave(next)`; the promotion saves everything when the merchant clicks Save |
|
|
309
|
+
|
|
310
|
+
### `promotion.action_form.<type>`
|
|
311
|
+
|
|
312
|
+
| Where | The sheet that edits one promotion action (e.g. `promotion.action_form.free_shipping`) |
|
|
313
|
+
|---|---|
|
|
314
|
+
| Use | A custom editor for an action — calculators, line-item pickers |
|
|
315
|
+
| Context | `{ draft, onSave, onClose }` — same as rules |
|
|
316
|
+
|
|
317
|
+
### `price_list.rule_form.<type>`
|
|
318
|
+
|
|
319
|
+
| Where | The sheet that edits one price list rule (e.g. `price_list.rule_form.volume_rule`) |
|
|
320
|
+
|---|---|
|
|
321
|
+
| Use | A custom editor for a price rule |
|
|
322
|
+
| Context | `{ draft, onSave, onClose }` — same as promotion rules |
|
|
164
323
|
|
|
165
|
-
|
|
324
|
+
### Payment method editor slots
|
|
166
325
|
|
|
167
326
|
Used by `<PaymentMethodForm>` to let payment-provider plugins replace pieces of the editor sheet. The slot names are computed per provider type (`stripe`, `bogus`, …), so registering against the right name is what hooks your editor in.
|
|
168
327
|
|
|
169
|
-
|
|
328
|
+
#### `payment_method.guide.<providerType>`
|
|
170
329
|
|
|
171
330
|
| Where | Above the preferences form |
|
|
172
331
|
|---|---|
|
|
173
332
|
| Use | Banner explaining what the integration does, what credentials to use, links to provider docs |
|
|
174
333
|
| Context | `PaymentMethodEditorContext` (see below) |
|
|
175
334
|
|
|
176
|
-
|
|
335
|
+
#### `payment_method.form.<providerType>`
|
|
177
336
|
|
|
178
337
|
| Where | In place of the auto-generated preferences form |
|
|
179
338
|
|---|---|
|
|
180
339
|
| Use | Render a custom React form when the provider's preferences are too complex for the generated UI (multi-step OAuth, environment switchers, …) |
|
|
181
340
|
| Context | `PaymentMethodEditorContext` |
|
|
182
341
|
|
|
183
|
-
|
|
342
|
+
#### `payment_method.actions.<providerType>`
|
|
184
343
|
|
|
185
344
|
| Where | Sheet footer, before Save/Cancel |
|
|
186
345
|
|---|---|
|
|
187
346
|
| Use | Provider-specific action buttons — "Test connection", "Rotate keys", "Open dashboard in provider" |
|
|
188
347
|
| Context | `PaymentMethodEditorContext` |
|
|
189
348
|
|
|
190
|
-
|
|
349
|
+
#### `PaymentMethodEditorContext`
|
|
191
350
|
|
|
192
351
|
```ts
|
|
193
352
|
interface PaymentMethodEditorContext {
|
|
@@ -219,10 +378,11 @@ Use these instead of constructing the string yourself, so a rename in one place
|
|
|
219
378
|
|
|
220
379
|
If a built-in page should expose a new injection point:
|
|
221
380
|
|
|
222
|
-
1. Pick a name (`<resource>.<area>`, e.g., `order.timeline`)
|
|
381
|
+
1. Pick a name (`<resource>.<area>`, e.g., `order.timeline`; `seller.<page>.<area>` in the seller panel)
|
|
223
382
|
2. Add `<Slot name="..." context={{ resource: order, /* ... */ }} />` at the call site
|
|
224
|
-
3.
|
|
225
|
-
4.
|
|
383
|
+
3. If the slot sits inside the page's form, make it a host form: wrap the form in `<FormProvider>`, hydrate with `extensionFormValues('<resource>', record)`, and merge `extensionSubmitValues('<resource>', form)` into the save payload — the collection page is the reference
|
|
384
|
+
4. Document the slot here — host, intent, context shape, and form key
|
|
385
|
+
5. Open the PR
|
|
226
386
|
|
|
227
387
|
The slot is not "live" until the docs land — without an entry here, no plugin author knows it exists.
|
|
228
388
|
|
|
@@ -684,7 +684,7 @@ Import everything from `@spree/seller-dashboard` — it re-exports both the fram
|
|
|
684
684
|
| Change wording | [Translations](../dashboard/customization/translations.md) merged with `i18n.addResourceBundle` |
|
|
685
685
|
| Install a third-party plugin | `npm add <plugin>` and restart — the Vite plugin picks it up from your dependencies |
|
|
686
686
|
|
|
687
|
-
The seller panel
|
|
687
|
+
The seller panel exposes slots on its product, order, payout, profile, and team pages — `seller.product.form_sidebar`, `seller.order.form_sidebar`, `seller.payout.form_sidebar`, `seller.profile.form_main` and `seller.profile.form_sidebar`, plus `seller.team.actions` and `seller.team.after`. The operator's side has its own: `seller.form_main` and `seller.form_sidebar` on the admin dashboard's seller page, and `seller_payout.form_main` and `seller_payout.form_sidebar` on its payout page. See the [slots catalog](../dashboard/slots-catalog.md#seller-panel-slots) for each one's context. To put a widget anywhere else in the panel, add a route.
|
|
688
688
|
|
|
689
689
|
> **NOTE:** **White-labelling is theming and copy, not a branding config object.** There is no logo or brand-name setting to fill in. You restyle by owning the Tailwind layer in your host app, and you re-word by overriding translation keys. Both are real customization paths — there is simply no shortcut that skips them.
|
|
690
690
|
|
|
@@ -708,7 +708,7 @@ Every transactional email now renders from a Liquid template written in MJML ins
|
|
|
708
708
|
Other changes that come with it:
|
|
709
709
|
|
|
710
710
|
- **Subjects moved into the templates.** Each template's front matter holds its subject. `Spree::BaseMailer#order_email_subject` is removed.
|
|
711
|
-
- **The mailer view helpers are removed.** `Spree::MailHelper` (`name_for`), `Spree::BaseHelper` (`spree_storefront_resource_url`), `Spree::FulfillmentHelper` and `Spree::DigitalAssetHelper` are gone. Templates get the same values as serializer fields, such as `order.customer_name` and `item.url`.
|
|
711
|
+
- **The mailer view helpers are removed.** `Spree::MailHelper` (`name_for`), `Spree::BaseHelper` (`spree_storefront_resource_url`), `Spree::FulfillmentHelper` and `Spree::DigitalAssetHelper` are gone, and so are `spree_image_tag` and `spree_asset_aspect_ratio` from `Spree::ImagesHelper` (`spree_image_url` stays). Templates get the same values as serializer fields, such as `order.customer_name` and `item.url`.
|
|
712
712
|
- **Every email has a plain-text part**, generated from the HTML. Seven emails that had none now have one.
|
|
713
713
|
- **Alba moved into `spree_core`**, with its configuration, so core's staff emails render in installations without `spree_api`.
|
|
714
714
|
- **Your own mailers keep working.** A mailer that inherits `Spree::BaseMailer` and calls `mail` with its own ERB views is wrapped in the new email layout, and the `spree/shared/mailer_hero` and `mailer_button` partials remain for its views. See [Your own mailers](../customization/emails.md#your-own-mailers).
|