@base44/app-plugin-commerce 0.6.2 → 0.6.4
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.6.
|
|
3
|
+
"version": "0.6.4",
|
|
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",
|
|
@@ -116,7 +116,7 @@ import { useProductList, useCategories, useStoreInfo, useFormatMoney, productPri
|
|
|
116
116
|
|
|
117
117
|
A card can render `name`, `productImages(row)[0]`, `productPrice(row, { formatMoney }).label` (already "From €19.99" when the product sells variants — there is no product `type` flag, and `product.price` alone is a rolled-up from-price), `on_sale`, `short_description`, `stock_status`, `average_rating`/`rating_count`, `productRibbons(row)`, `productSpecs(row)`. ⚑ **Images and ribbons are objects, either may be empty** — render your placeholder, never a broken `<img>` or a raw object. Field matrix: [`../references/catalog-rendering.md`](../references/catalog-rendering.md). That list is an inventory, not a card design and not an order to render in. An even grid of identical cards, each carrying the same name/price/stars trio, is where a generated store lands by default and almost never where this catalog belongs: give the grid a rhythm (a hero piece spanning two columns, an editorial break between rows, a denser tile for a large catalog), and lead each card with the one or two fields *these* products are judged on — carat weight, focal length, edition size, ABV — read off `productSpecs(row)`, not the fields every store shows.
|
|
118
118
|
|
|
119
|
-
⚑ **Ribbons belong in both views** — grid and product page. They are the merchant's own merchandising ("Limited", "Last pieces"), and each links to its filtered listing (`/collection?ribbon_id=<id>`). `productRibbons(row)` hands you `{id, name}` **objects** — render `r.name`, key the link on `r.id`; the entry itself in JSX is React's "Objects are not valid as a React child". Never render a bare "Ribbons:" label with nothing after it. ⚑ **A ribbon link inside a card that is itself a link nests `<a>` in `<a>`** — invalid
|
|
119
|
+
⚑ **Ribbons belong in both views** — grid and product page. They are the merchant's own merchandising ("Limited", "Last pieces"), and each links to its filtered listing (`/collection?ribbon_id=<id>`). `productRibbons(row)` hands you `{id, name}` **objects** — render `r.name`, key the link on `r.id`; the entry itself in JSX is React's "Objects are not valid as a React child". Never render a bare "Ribbons:" label with nothing after it. ⚑ **A ribbon link inside a card that is itself a link nests `<a>` in `<a>`** — invalid, React warns. In the grid use plain labels, or link the image and title rather than the whole card; keep ribbon links on the product page.
|
|
120
120
|
|
|
121
121
|
**Rails** (featured row, "new in") are the same hook with a filter (`{ featured: true, per_page: 4 }`) — `featured` is the merchant's own flag, so the rail stays curated store data instead of hardcoded slugs. ⚑ Any filter may legitimately match nothing — render *nothing* then, never a heading over an empty row.
|
|
122
122
|
|
|
@@ -302,7 +302,7 @@ function CheckoutForm() {
|
|
|
302
302
|
|
|
303
303
|
**`<AddressFields>` is the one shipped component — use it, never hand-roll the address form.** It owns what hand-rolled forms get wrong: the state/province field appears with the right options once a country is picked (shipping rates and taxes match on country *plus* state, so a form without it mis-prices US/CA/AU orders with no error anywhere), every field keeps its `autoComplete` token (what makes browser autofill work), required marks arm on first blur, and the server's "we don't ship there" lands on the country field. `which="shipping"` renders null until `shipToDifferent` is on — the deliver-elsewhere checkbox itself is yours, wired to `c.shipToDifferent` / `c.setShipToDifferent`.
|
|
304
304
|
|
|
305
|
-
It ships **no CSS
|
|
305
|
+
It ships **no CSS** bar a `max-width:100%` cap on the selects (an unstyled checkout must not scroll sideways): every element carries `data-part` (`address-fields`, `field`, `label`, `control`, `required`, `error`) plus `data-key` (the field) and `data-span` (1 or 2 — the field's natural width in a two-column grid), so style it in your `index.css` via `[data-part]` selectors or pass `className`/`classes={{ field, label, control, error }}`. ⚑ **`data-part` sits on the element, not a wrapper** — `select[data-part="control"]`, never `[data-part="control"] input`: the descendant form matches nothing and ships the form unstyled. Props: `includeCompany` (default false), `includePhone` (default true), `omit={["…"]}`, `labels={{ postcode: "ZIP code" }}` (over `addressFieldSpec`'s plain-convention defaults), `selectPlaceholder`, and two escape hatches — `inputRender` swaps the control only (spread the handed `dom` props onto your input), `fieldRender` replaces the whole labeled block. `c.missingBillingFields` stays the live list of what is still missing, if you want your own per-field marks.
|
|
306
306
|
|
|
307
307
|
⚑ **The `stage === "submitted"` guard goes above the empty-cart branch** — placing an order clears the cart before the browser navigates, and without the guard the page flashes an empty bag over a just-placed order.
|
|
308
308
|
|
|
@@ -13,7 +13,10 @@ import { REQUIRED_BILLING_FIELDS } from "./address";
|
|
|
13
13
|
* `autoComplete` tokens are what make browser autofill work, and the server's
|
|
14
14
|
* "we don't ship there" belongs on the country field.
|
|
15
15
|
*
|
|
16
|
-
* It renders semantic fields and nothing else
|
|
16
|
+
* It renders semantic fields and nothing else. No CSS ships with it bar one
|
|
17
|
+
* containment guard — `max-width: 100%` on the selects, because their
|
|
18
|
+
* intrinsic width is the longest country name and an unstyled checkout would
|
|
19
|
+
* otherwise scroll sideways on a phone. It caps; it never sizes. Every
|
|
17
20
|
* element carries `data-part` (`field`, `label`, `control`, `required`,
|
|
18
21
|
* `error`) plus `data-key`/`data-span`/`data-invalid`, so the store's own
|
|
19
22
|
* classes attach via `className`/`classes` props or `[data-part]` selectors.
|
|
@@ -116,6 +119,13 @@ export function AddressFields({
|
|
|
116
119
|
<select
|
|
117
120
|
data-part="control"
|
|
118
121
|
className={classes?.control}
|
|
122
|
+
// The one style that ships: an unconstrained <select> takes the
|
|
123
|
+
// intrinsic width of its widest option, and the country list
|
|
124
|
+
// ("United Arab Emirates") is wider than a phone. Without this
|
|
125
|
+
// the checkout scrolls sideways on mobile before the store has
|
|
126
|
+
// written a single class. Caps only — any width the store sets
|
|
127
|
+
// still applies.
|
|
128
|
+
style={{ maxWidth: "100%" }}
|
|
119
129
|
aria-invalid={invalid || undefined}
|
|
120
130
|
aria-required={f.required || undefined}
|
|
121
131
|
{...dom}
|