@base44/app-plugin-commerce 0.10.3 → 0.10.5
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 +1 -1
- package/skills/commerce/SKILL.md +1 -1
- package/skills/commerce/references/storefront-ui.md +12 -0
- package/skills/commerce/references/storefront-verification.md +11 -15
- package/src/commerce/storefront-ui/CheckoutPage.jsx +17 -5
- package/src/commerce/storefront-ui/internal.jsx +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@base44/app-plugin-commerce",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.5",
|
|
4
4
|
"description": "Base44 Commerce plugin — entities, backend functions, shared commerce engine, admin UI and the commerce skill, shipped as copyable source",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"base44",
|
package/skills/commerce/SKILL.md
CHANGED
|
@@ -119,7 +119,7 @@ Open a file when its work starts — not while planning.
|
|
|
119
119
|
| [`references/catalog-rendering.md`](./references/catalog-rendering.md) | field shapes each catalog call returns, variant edge cases | 16K |
|
|
120
120
|
| [`references/shipping-and-tax.md`](./references/shipping-and-tax.md) | zones beyond stage 03's recipe, taxes, day-2 edits | 8K |
|
|
121
121
|
| [`references/online-payments.md`](./references/online-payments.md) | enabling card payments, or wiring the provider — at install or any time later | 10K |
|
|
122
|
-
| [`references/storefront-ui.md`](./references/storefront-ui.md) | changing the shipped cart/drawer/checkout/order-received surfaces: props, slots, tokens, locales, restyling |
|
|
122
|
+
| [`references/storefront-ui.md`](./references/storefront-ui.md) | changing the shipped cart/drawer/checkout/order-received surfaces: props, slots, tokens, locales, restyling | 9K |
|
|
123
123
|
| [`references/storefront-verification.md`](./references/storefront-verification.md) | driving the storefront from a browser script | 3K |
|
|
124
124
|
| [`references/reviews.md`](./references/reviews.md) | review policies (open by default; login-gated, verified buyers), moderation | 6K |
|
|
125
125
|
| [`references/store-settings.md`](./references/store-settings.md) | changing store behavior through settings keys | 5K |
|
|
@@ -89,6 +89,13 @@ on those two hooks is normal and supported.
|
|
|
89
89
|
`termsCheckbox: true` gates place-order on an accepted checkbox worded by
|
|
90
90
|
`brand.termsLabel`.
|
|
91
91
|
- `continueHref="/"` — where the empty state sends people.
|
|
92
|
+
- `slots`: `{ afterContact(ctx), beforeSubmit(ctx), aboveSummary(ctx) }` — your
|
|
93
|
+
markup at three fixed points: `afterContact` between the address and the
|
|
94
|
+
delivery choice, `beforeSubmit` at the end of the form column, `aboveSummary`
|
|
95
|
+
above the totals. Every slot receives `{ cart, checkout, note, setNote }`.
|
|
96
|
+
`setNote` is the one way a slot puts something on the order: it writes the
|
|
97
|
+
order note, which submits as `customer_note` and shows on the receipt and in
|
|
98
|
+
the admin's order view (`place-order` accepts no other free field).
|
|
92
99
|
- `onPlaced={(order) => …}` — replaces the default `/order-received` navigation.
|
|
93
100
|
Advanced: the default flow already handles the card redirect and the offline
|
|
94
101
|
receipt; use this only when the user explicitly wants a custom post-order flow,
|
|
@@ -151,5 +158,10 @@ element itself** (`select[data-part="control"]`, never `[data-part="control"] in
|
|
|
151
158
|
`useProduct(...)`'s `upsells`/`crossSells` with `useCart().addItem` — one-click
|
|
152
159
|
Add only for products without attributes (variants answer `400 variation_required`).
|
|
153
160
|
- **B2B-ish checkout**: `sections={{ phone: "required", termsCheckbox: true, notes: true }}`.
|
|
161
|
+
- **Gift wrap + gift note** (any extra field the customer fills at checkout):
|
|
162
|
+
`slots={{ beforeSubmit: ({ note, setNote }) => <GiftOptions note={note} onChange={setNote} /> }}`
|
|
163
|
+
— a checkbox and a textarea that write `"Gift wrap: yes\nGift note: …"` into
|
|
164
|
+
the note. It lands on the order as `customer_note`. Never copy
|
|
165
|
+
`CheckoutPage.jsx` to add a field.
|
|
154
166
|
- **Custom thank-you flow**: keep `/order-received` mounted (payment links return
|
|
155
167
|
there), add `onPlaced` only for the extra hop the user asked for.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
read_when: "You are about to drive the storefront from a browser script — adding to the cart, stepping quantities,
|
|
2
|
+
read_when: "You are about to drive the storefront from a browser script — adding to the cart, stepping quantities, opening the checkout."
|
|
3
3
|
skip_when: "You are not scripting the storefront."
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -37,17 +37,13 @@ DOM is briefly right about the *intent* and wrong about the *state*.
|
|
|
37
37
|
`/checkout` — URL plus a field of the form on screen — before emptying the
|
|
38
38
|
cart or moving on. Tearing the cart down while still on the cart page, or
|
|
39
39
|
mid-navigation, produces an empty checkout that reads as a routing bug.
|
|
40
|
-
- **
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
`
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
from a fresh navigation to `orderReceivedUrl(result)`
|
|
51
|
-
(`/order-received?order_id=…&order_key=…` — the ids come back in
|
|
52
|
-
`placeOrder`'s result, and `commerce/admin-orders` `search` has the order
|
|
53
|
-
either way).
|
|
40
|
+
- **The checkout is shipped code — never fill it, never place an order.** Its
|
|
41
|
+
logic is the kit's and already tested; what is yours is how it looks. Open
|
|
42
|
+
`/checkout` with items in the cart and check that: it renders inside the
|
|
43
|
+
store's chrome, the `.sfui` tokens took (colours, radius, heading face match
|
|
44
|
+
the rest of the site), the `brand` copy reads in the store's voice, and
|
|
45
|
+
nothing overflows on a phone-width viewport. Do not type into its fields and
|
|
46
|
+
do not submit — an order is real store data the owner sees in `/store-admin`,
|
|
47
|
+
and the platform's "delete the record you created" rule does not fit it (the
|
|
48
|
+
entities are admin-only; a raw delete skips the engine that releases stock).
|
|
49
|
+
Empty the cart when you are done.
|
|
@@ -48,6 +48,11 @@ const BRAND_KEYS = {
|
|
|
48
48
|
* phone: "optional"|"required"|"hidden", shipToDifferent: true,
|
|
49
49
|
* termsCheckbox: false }` — `phone: "required"` is enforced by marking the
|
|
50
50
|
* field required in the address spec via requiredBillingFields.
|
|
51
|
+
* @param {object} [props.slots] `{ afterContact?(ctx), beforeSubmit?(ctx),
|
|
52
|
+
* aboveSummary?(ctx) }` — the store's own markup at three fixed points; every
|
|
53
|
+
* slot receives `{ cart, checkout, note, setNote }`. `setNote` is the one way
|
|
54
|
+
* a slot puts something on the order: it writes the order note, which submits
|
|
55
|
+
* as `customer_note` (place-order accepts no other free field).
|
|
51
56
|
* @param {string} [props.continueHref="/"] where the empty state sends people.
|
|
52
57
|
* @param {(order) => void} [props.onPlaced] replaces the default
|
|
53
58
|
* order-received navigation (advanced; the default flow is complete).
|
|
@@ -56,6 +61,7 @@ export function CheckoutPage({
|
|
|
56
61
|
brand,
|
|
57
62
|
layout = "two-column",
|
|
58
63
|
sections = {},
|
|
64
|
+
slots = {},
|
|
59
65
|
continueHref = "/",
|
|
60
66
|
onPlaced,
|
|
61
67
|
}) {
|
|
@@ -75,6 +81,7 @@ export function CheckoutPage({
|
|
|
75
81
|
brand={brand}
|
|
76
82
|
layout={layout}
|
|
77
83
|
sections={{ ...sections, phone, shipToDifferent: allowShipToDifferent }}
|
|
84
|
+
slots={slots}
|
|
78
85
|
continueHref={continueHref}
|
|
79
86
|
onPlaced={onPlaced}
|
|
80
87
|
/>
|
|
@@ -82,7 +89,7 @@ export function CheckoutPage({
|
|
|
82
89
|
);
|
|
83
90
|
}
|
|
84
91
|
|
|
85
|
-
function CheckoutBody({ brand, layout, sections, continueHref, onPlaced }) {
|
|
92
|
+
function CheckoutBody({ brand, layout, sections, slots, continueHref, onPlaced }) {
|
|
86
93
|
const tt = makeT(brand, BRAND_KEYS);
|
|
87
94
|
const checkout = useCheckoutContext();
|
|
88
95
|
const { info } = useStoreInfo();
|
|
@@ -90,6 +97,11 @@ function CheckoutBody({ brand, layout, sections, continueHref, onPlaced }) {
|
|
|
90
97
|
const [termsAccepted, setTermsAccepted] = useState(false);
|
|
91
98
|
const { coupon = "auto", notes = false, phone, shipToDifferent, termsCheckbox = false } = sections;
|
|
92
99
|
const { cart, stage, blockers, canPlaceOrder, placing, orderError } = checkout;
|
|
100
|
+
const updateNote = (value) => {
|
|
101
|
+
setNote(value);
|
|
102
|
+
writeOrderNote(value);
|
|
103
|
+
};
|
|
104
|
+
const slotArgs = { cart, checkout, note, setNote: updateNote };
|
|
93
105
|
|
|
94
106
|
const submit = async () => {
|
|
95
107
|
const extra = note.trim() ? { customer_note: note.trim() } : {};
|
|
@@ -175,6 +187,7 @@ function CheckoutBody({ brand, layout, sections, continueHref, onPlaced }) {
|
|
|
175
187
|
</>
|
|
176
188
|
)}
|
|
177
189
|
</section>
|
|
190
|
+
{slots.afterContact && slots.afterContact(slotArgs)}
|
|
178
191
|
|
|
179
192
|
<ShippingMethodPicker>
|
|
180
193
|
{({ methods, mustChoose, single, chosen, hint }) => (
|
|
@@ -256,17 +269,16 @@ function CheckoutBody({ brand, layout, sections, continueHref, onPlaced }) {
|
|
|
256
269
|
id="sfui-checkout-note"
|
|
257
270
|
value={note}
|
|
258
271
|
placeholder={tt("checkout.notes.placeholder")}
|
|
259
|
-
onChange={(e) =>
|
|
260
|
-
setNote(e.target.value);
|
|
261
|
-
writeOrderNote(e.target.value);
|
|
262
|
-
}}
|
|
272
|
+
onChange={(e) => updateNote(e.target.value)}
|
|
263
273
|
/>
|
|
264
274
|
</section>
|
|
265
275
|
)}
|
|
276
|
+
{slots.beforeSubmit && slots.beforeSubmit(slotArgs)}
|
|
266
277
|
</div>
|
|
267
278
|
|
|
268
279
|
<aside className="sfui-panel sfui-summary" aria-label={tt("checkout.summaryTitle")}>
|
|
269
280
|
<h2 className="sfui-heading sfui-h2">{tt("checkout.summaryTitle")}</h2>
|
|
281
|
+
{slots.aboveSummary && slots.aboveSummary(slotArgs)}
|
|
270
282
|
{couponVisible(coupon, cart, info) && <CouponField tt={tt} />}
|
|
271
283
|
<TotalsBlock cart={cart} tt={tt} />
|
|
272
284
|
{termsCheckbox && (
|
|
@@ -54,10 +54,12 @@ export function CartLineRow({ item, tt, productHref, compact = false, lineExtra
|
|
|
54
54
|
const formatMoney = useFormatMoney();
|
|
55
55
|
const attrs = attributesLabel(item.attributes);
|
|
56
56
|
const href = productHref ? productHref(item) : null;
|
|
57
|
+
// storefront-cart sends `image` as a bare URL string; order lines carry {src, alt}.
|
|
58
|
+
const imageSrc = typeof item.image === "string" ? item.image : item.image?.src;
|
|
57
59
|
return (
|
|
58
60
|
<li className="sfui-line" data-pending={line.pending || undefined} data-compact={compact || undefined}>
|
|
59
61
|
<div className="sfui-line-media" aria-hidden="true">
|
|
60
|
-
{
|
|
62
|
+
{imageSrc && <img src={imageSrc} alt="" loading="lazy" />}
|
|
61
63
|
</div>
|
|
62
64
|
<div className="sfui-line-main">
|
|
63
65
|
<p className="sfui-line-name">
|