@base44/app-plugin-commerce 0.6.10 → 0.6.12
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.12",
|
|
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",
|
|
@@ -194,7 +194,7 @@ Build your layout from — all optional, **not one component style**:
|
|
|
194
194
|
|
|
195
195
|
⚑ **Never `.map()` the whole list into one grey label/value table** — that is the single most reliable tell of a generated product page. Design the two or three rows that carry *this* catalog's meaning as what they are (a weight set in the display face, a composition as bars, a provenance beside its place); let the rest fall through to the plain row, and don't feel obliged to keep them in one block — a spec can sit under the gallery, beside the price, or inside the description. Branch on `s.key` too where one particular modifier deserves its own treatment regardless of type. ⚑ **Look a spec up with `findSpec(rows, "care")`** (ignores case, spaces, `_`, `-`): meta keys are free text (`care`, `Care`, `Care Instructions`), so `rows.find(s => s.label === "Care")` silently never matches and renders the fallback forever. `[]` means no section at all.
|
|
196
196
|
- **Breadcrumbs** — from `categories` (`/collection?category_id=${c.id}`); skip on a flat catalog. Ribbons (`productRibbons(product)`) are labels, not breadcrumbs.
|
|
197
|
-
- **Reviews, only if the store wants them** — no review UI is a complete outcome (then no star ratings on cards either: an average of nothing is `0`). ⚑ **Both shapes are exact —
|
|
197
|
+
- **Reviews, only if the store wants them** — no review UI is a complete outcome (then no star ratings on cards either: an average of nothing is `0`). ⚑ **Both shapes are exact** — entity-style names (`content`, `reviewer_name`) throw on submit and render blank in the list:
|
|
198
198
|
|
|
199
199
|
```jsx
|
|
200
200
|
p.reviews // { items, page, per_page, has_next }
|
|
@@ -17,7 +17,7 @@ Reviews are **part of the happy path**, not an extra: the backend always shipped
|
|
|
17
17
|
Both live on the storefront client in `@/commerce/utils` — in React, `useStorefront()` is that client:
|
|
18
18
|
|
|
19
19
|
- **`getProductReviews(slugOrRef, { page, per_page })`** → `{ items, page, per_page, has_next, average_rating, rating_count }`. The same reviews `get-product` returns — page or refresh the list without re-fetching the page; `useProduct(slug, { reviewsPerPage })` sizes the first one.
|
|
20
|
-
- **`submitReview({ product_id, review?, rating?, reviewer?, email? })`** → `{ review_id, status, verified }`. **Text or stars — at least one**: `review` is the body text, `rating` is **1–5 stars** (0 counts as unrated); stars-only and text-only are both valid, only both missing rejects. `reviewer` is the display name. ⚑ **
|
|
20
|
+
- **`submitReview({ product_id, review?, rating?, reviewer?, email? })`** → `{ review_id, status, verified }`. **Text or stars — at least one**: `review` is the body text, `rating` is **1–5 stars** (0 counts as unrated); stars-only and text-only are both valid, only both missing rejects. `reviewer` is the display name. ⚑ **One set of names, both directions** — the payload above, rows back as `{ id, reviewer, review, rating, verified, created_date }`. Not the entity's columns: a near-miss (`content`, `reviewer_name`, `reviewer_email`, …) throws client-side rather than being aliased, since the same wrong names would render the list blank too. It **rejects** with `email_required` | `review_incomplete` (neither text nor stars) | `invalid_rating` | `not_found` — catch it, read `storefrontErrorCode(e)`, and land each code on its own field, so a failed submit says what to fix instead of resolving into nothing. After an approved submission, refresh the list yourself so the review actually appears.
|
|
21
21
|
|
|
22
22
|
## What ships
|
|
23
23
|
|
|
@@ -24,13 +24,32 @@
|
|
|
24
24
|
* payload) returns an action's payload as-is.
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Near-miss payload keys, rejected rather than aliased: the same names come
|
|
29
|
+
* back on the review rows, so a store that posts `content` reads
|
|
30
|
+
* `reviewer_name` too — aliasing the submit would leave every author blank
|
|
31
|
+
* with nothing failing to say why.
|
|
32
|
+
*/
|
|
33
|
+
const REVIEW_FIELD_FIXES = {
|
|
34
|
+
content: "review",
|
|
35
|
+
text: "review",
|
|
36
|
+
body: "review",
|
|
37
|
+
comment: "review",
|
|
38
|
+
reviewer_name: "reviewer",
|
|
39
|
+
name: "reviewer",
|
|
40
|
+
author: "reviewer",
|
|
41
|
+
reviewer_email: "email",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const REVIEW_SIGNATURE = "submitReview({ product_id, review, rating, reviewer, email })";
|
|
45
|
+
|
|
46
|
+
/** Throw when a payload uses a near-miss name and leaves the real field empty. */
|
|
47
|
+
function assertReviewFieldNames(payload) {
|
|
48
|
+
const wrong = Object.keys(payload).filter((k) => {
|
|
49
|
+
const canonical = REVIEW_FIELD_FIXES[k];
|
|
50
|
+
return canonical && !String(payload[canonical] ?? "").trim();
|
|
51
|
+
});
|
|
52
|
+
if (wrong.length) throw new Error(`${REVIEW_SIGNATURE} — got: ${wrong.join(", ")}`);
|
|
34
53
|
}
|
|
35
54
|
|
|
36
55
|
/** The stable error code a failed storefront call carries, if any. */
|
|
@@ -126,12 +145,10 @@ export function createStorefront(base44, { storageKey = "cart_token", storage }
|
|
|
126
145
|
* the keys it did receive, instead of letting the server answer about a
|
|
127
146
|
* field the customer filled in.
|
|
128
147
|
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* `reviewer_name`, `
|
|
133
|
-
* form written against the entity still submits. The reviews you render
|
|
134
|
-
* back always use the API's names: `{ reviewer, review, rating, verified }`.
|
|
148
|
+
* **One set of names, both directions** — `{ product_id, review, rating,
|
|
149
|
+
* reviewer, email }` in, `{ id, reviewer, review, rating, verified,
|
|
150
|
+
* created_date }` back. Not the entity's columns: a near-miss (`content`,
|
|
151
|
+
* `reviewer_name`, `reviewer_email`, …) throws rather than being aliased.
|
|
135
152
|
*
|
|
136
153
|
* Resolves to `{ review_id, status, verified }` — **`status` is
|
|
137
154
|
* `"approved"` or `"hold"` depending on the store's `auto_approve_reviews`
|
|
@@ -140,26 +157,15 @@ export function createStorefront(base44, { storageKey = "cart_token", storage }
|
|
|
140
157
|
* `invalid_rating` | `not_found`.
|
|
141
158
|
*/
|
|
142
159
|
submitReview(payload = {}) {
|
|
143
|
-
const { product_id, rating } = payload;
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
// both are present; anything still unrecognized throws below rather than
|
|
150
|
-
// vanishing.
|
|
151
|
-
const review = firstFilled(payload, ["review", "content", "text", "body", "comment"]);
|
|
152
|
-
const reviewer = firstFilled(payload, ["reviewer", "reviewer_name", "name", "author"]);
|
|
153
|
-
const email = firstFilled(payload, ["email", "reviewer_email"]);
|
|
160
|
+
const { product_id, review, rating, reviewer, email } = payload;
|
|
161
|
+
// A wrong field name used to vanish in the destructure: the text never
|
|
162
|
+
// left the browser and the server answered "review is required" about a
|
|
163
|
+
// field the customer had filled in. Name the mistake instead, here,
|
|
164
|
+
// where the fix also applies to the list the store renders back.
|
|
165
|
+
assertReviewFieldNames(payload);
|
|
154
166
|
const rated = rating != null && Number(rating) >= 1;
|
|
155
|
-
if (!review && !rated) {
|
|
156
|
-
|
|
157
|
-
// field it didn't receive, which is never the one that is wrong.
|
|
158
|
-
throw new Error(
|
|
159
|
-
`submitReview needs \`review\` text or a \`rating\` of 1-5. Received: ${
|
|
160
|
-
Object.keys(payload).join(", ") || "nothing"
|
|
161
|
-
}. The payload is { product_id, review, rating, reviewer, email } — not the entity's column names.`,
|
|
162
|
-
);
|
|
167
|
+
if (!String(review ?? "").trim() && !rated) {
|
|
168
|
+
throw new Error(`${REVIEW_SIGNATURE} — needs review text or a rating of 1-5`);
|
|
163
169
|
}
|
|
164
170
|
return inv("commerce/storefront-catalog", {
|
|
165
171
|
action: "submit-review",
|