@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@base44/app-plugin-commerce",
3
- "version": "0.10.3",
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",
@@ -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 | 8K |
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, filling the checkout, placing an order."
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
- - **Filling the checkout.** Every field is a controlled React input, so writing
41
- `el.value` changes nothing React sees. Use the harness's own fill (it
42
- dispatches `input` + `change`) never lift the native setter off
43
- `HTMLInputElement.prototype` and call `descriptor.set(v)`: detached from the
44
- element it throws `Illegal invocation`, and the workaround it is reaching for
45
- is what the fill helper already does.
46
- - **`placeOrder` ends the page.** It navigates with `window.location.assign`,
47
- so a script that placed an order loses its page context and can land back at
48
- `/` while the order itself was created normally. That is the hard
49
- navigation, not a broken redirect. The confirmation is reachable at any time
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
- {item.image?.src && <img src={item.image.src} alt="" loading="lazy" />}
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">