@base44/app-plugin-commerce 0.7.0 → 0.8.0
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/README.md +4 -10
- package/base44/entities/commerce.ProductReview.jsonc +2 -2
- package/base44/functions/commerce/storefront-catalog/entry.ts +7 -3
- package/package.json +1 -1
- package/scripts/install.js +2 -4
- package/skills/commerce/SKILL.md +9 -3
- package/skills/commerce/docs/api-storefront.md +3 -3
- package/skills/commerce/install/01-install.md +18 -30
- package/skills/commerce/install/02-storefront.md +23 -9
- package/skills/commerce/references/admin-localization.md +95 -0
- package/skills/commerce/references/reviews.md +7 -6
- package/src/commerce/admin/README.md +36 -15
- package/src/commerce/admin/bot/StoreAdminBot.jsx +12 -11
- package/src/commerce/admin/components/AddressForm.jsx +13 -12
- package/src/commerce/admin/components/ConfirmDialog.jsx +5 -4
- package/src/commerce/admin/components/CountrySelect.jsx +12 -8
- package/src/commerce/admin/components/DataTable.jsx +3 -2
- package/src/commerce/admin/components/DateRangePicker.jsx +11 -10
- package/src/commerce/admin/components/EmptyState.jsx +2 -1
- package/src/commerce/admin/components/MediaUploader.jsx +3 -2
- package/src/commerce/admin/components/MetaDataEditor.jsx +4 -3
- package/src/commerce/admin/components/SearchSelect.jsx +4 -3
- package/src/commerce/admin/context/SettingsContext.jsx +12 -25
- package/src/commerce/admin/hooks/useMoney.js +3 -2
- package/src/commerce/admin/i18n/index.js +54 -0
- package/src/commerce/admin/i18n/locales/de.js +896 -0
- package/src/commerce/admin/i18n/locales/en.js +900 -0
- package/src/commerce/admin/i18n/locales/es.js +896 -0
- package/src/commerce/admin/i18n/locales/fr.js +896 -0
- package/src/commerce/admin/i18n/locales/ja.js +896 -0
- package/src/commerce/admin/i18n/locales/pt.js +896 -0
- package/src/commerce/admin/index.jsx +6 -30
- package/src/commerce/admin/layout/AccessDenied.jsx +8 -9
- package/src/commerce/admin/layout/AuthGuard.jsx +2 -1
- package/src/commerce/admin/layout/Sidebar.jsx +29 -18
- package/src/commerce/admin/layout/Topbar.jsx +4 -3
- package/src/commerce/admin/lib/api.js +3 -2
- package/src/commerce/admin/lib/constants.js +44 -43
- package/src/commerce/admin/lib/format.js +4 -3
- package/src/commerce/admin/lib/paths.js +2 -3
- package/src/commerce/admin/pages/Dashboard.jsx +25 -24
- package/src/commerce/admin/pages/coupons/CouponEditor.jsx +55 -57
- package/src/commerce/admin/pages/coupons/CouponsList.jsx +22 -21
- package/src/commerce/admin/pages/customers/CustomerEditor.jsx +31 -30
- package/src/commerce/admin/pages/customers/CustomersList.jsx +19 -18
- package/src/commerce/admin/pages/orders/OrderEditor.jsx +75 -71
- package/src/commerce/admin/pages/orders/OrdersList.jsx +16 -15
- package/src/commerce/admin/pages/orders/components/AddProductDialog.jsx +12 -11
- package/src/commerce/admin/pages/orders/components/DownloadPermissionsPanel.jsx +14 -13
- package/src/commerce/admin/pages/orders/components/LineItemsTable.jsx +12 -11
- package/src/commerce/admin/pages/orders/components/OrderNotesPanel.jsx +11 -10
- package/src/commerce/admin/pages/orders/components/PaymentPanel.jsx +22 -22
- package/src/commerce/admin/pages/orders/components/RefundPanel.jsx +18 -17
- package/src/commerce/admin/pages/orders/components/TotalsBox.jsx +18 -17
- package/src/commerce/admin/pages/products/Categories.jsx +14 -13
- package/src/commerce/admin/pages/products/ProductEditor.jsx +16 -15
- package/src/commerce/admin/pages/products/ProductsList.jsx +41 -36
- package/src/commerce/admin/pages/products/Reviews.jsx +54 -46
- package/src/commerce/admin/pages/products/components/AttributesSection.jsx +45 -39
- package/src/commerce/admin/pages/products/components/ProductDataPanel.jsx +8 -7
- package/src/commerce/admin/pages/products/components/PublishBox.jsx +10 -9
- package/src/commerce/admin/pages/products/components/TaxonomyPanel.jsx +18 -17
- package/src/commerce/admin/pages/products/components/tabs/DownloadsTab.jsx +11 -10
- package/src/commerce/admin/pages/products/components/tabs/LinkedTab.jsx +9 -10
- package/src/commerce/admin/pages/products/components/tabs/ModifiersTab.jsx +3 -9
- package/src/commerce/admin/pages/products/components/tabs/PriceInventoryTab.jsx +76 -78
- package/src/commerce/admin/pages/reports/Reports.jsx +33 -32
- package/src/commerce/admin/pages/settings/EmailsSettings.jsx +36 -39
- package/src/commerce/admin/pages/settings/GeneralSettings.jsx +13 -14
- package/src/commerce/admin/pages/settings/InventorySettings.jsx +12 -13
- package/src/commerce/admin/pages/settings/LocationEditor.jsx +48 -47
- package/src/commerce/admin/pages/settings/PaymentsSettings.jsx +32 -33
- package/src/commerce/admin/pages/settings/SettingsLayout.jsx +13 -12
- package/src/commerce/admin/pages/settings/ShippingTaxSettings.jsx +31 -31
- package/src/commerce/admin/pages/settings/useGroupForm.jsx +4 -3
- package/src/commerce/admin/pages/status/WebhookEditor.jsx +34 -31
- package/src/commerce/admin/pages/status/Webhooks.jsx +11 -10
- package/src/commerce/admin/routes.jsx +31 -43
- package/src/commerce/storefront/pickers.jsx +19 -5
- package/src/commerce/storefront/useCheckout.jsx +28 -3
- package/src/commerce/utils/storefront.js +49 -2
|
@@ -69,6 +69,10 @@ function resolvePaymentMethod(gateways, picked) {
|
|
|
69
69
|
* `not_needed` (virtual cart — render nothing). `singleShippingMethod` flags
|
|
70
70
|
* the one-option case and `chosenShippingMethod` is filled for it, so a single
|
|
71
71
|
* option is never left unselected and never rendered as a picker of one.
|
|
72
|
+
* **A pick applies optimistically**: `chosenShippingMethod` reflects it from
|
|
73
|
+
* the click, while the priced view catches up in the background
|
|
74
|
+
* (`shippingChoicePending` is true for that window) — a radio that waits for
|
|
75
|
+
* the server to look checked reads as a picker that ignored the click.
|
|
72
76
|
* - **Payment choice.** `paymentMethods` come from store info (their ONLY
|
|
73
77
|
* source — `cart.payment_gateways` is always undefined). One enabled gateway
|
|
74
78
|
* is selected from the first render that has store info. The selection is
|
|
@@ -193,18 +197,33 @@ export function useCheckout(options = {}) {
|
|
|
193
197
|
const shippingStatus = cart?.shipping_status ?? null;
|
|
194
198
|
const shippingMethods = cart?.available_shipping_methods ?? [];
|
|
195
199
|
const singleShippingMethod = shippingMethods.length === 1;
|
|
200
|
+
// The optimistic pick: `choose-shipping-method` is a priced round trip, and a
|
|
201
|
+
// radio that only looks checked once the response lands reads as a picker
|
|
202
|
+
// that ignored the click. The pick covers exactly the in-flight window — it
|
|
203
|
+
// clears when its own call settles, so the confirmed view (or, on failure,
|
|
204
|
+
// the cart's stored choice) takes back over without a flicker.
|
|
205
|
+
const [pickedShippingMethod, setPickedShippingMethod] = useState("");
|
|
196
206
|
// The backend auto-selects when it offers exactly one rate (`auto_selected`)
|
|
197
207
|
// and echoes it back on the cart, so the lookup normally finds it. The
|
|
198
208
|
// fallback covers the seam where a freshly repriced address offers one rate
|
|
199
209
|
// the cart's stored id hasn't caught up with: a single option is selected,
|
|
200
210
|
// and must read as selected, from the moment it is offered.
|
|
201
211
|
const chosenShippingMethod =
|
|
212
|
+
shippingMethods.find((m) => m.id === pickedShippingMethod) ??
|
|
202
213
|
shippingMethods.find((m) => m.id === cart?.chosen_shipping_method) ??
|
|
203
214
|
(singleShippingMethod ? shippingMethods[0] : null);
|
|
204
215
|
const chooseShippingMethod = useCallback(
|
|
205
|
-
(methodId) =>
|
|
216
|
+
(methodId) => {
|
|
217
|
+
setPickedShippingMethod(methodId);
|
|
218
|
+
return runCart(() => client.chooseShippingMethod(methodId)).finally(() =>
|
|
219
|
+
// Only the call's own pick is cleared — a newer click stays optimistic
|
|
220
|
+
// until its own call settles (the queue serializes them in order).
|
|
221
|
+
setPickedShippingMethod((cur) => (cur === methodId ? "" : cur)),
|
|
222
|
+
);
|
|
223
|
+
},
|
|
206
224
|
[client, runCart],
|
|
207
225
|
);
|
|
226
|
+
const shippingChoicePending = !!pickedShippingMethod;
|
|
208
227
|
|
|
209
228
|
// ── payment choice (gateways live on store info ONLY) ────────────────────
|
|
210
229
|
// The pick is state; the *method* is derived, so a lone gateway is selected
|
|
@@ -224,9 +243,14 @@ export function useCheckout(options = {}) {
|
|
|
224
243
|
else if (!cart || !cart.items?.length) blockers.push("empty_cart");
|
|
225
244
|
if (missingBilling.length) blockers.push("billing_incomplete");
|
|
226
245
|
if (shipToDifferent && !complete) blockers.push("shipping_address_incomplete");
|
|
227
|
-
if (syncPending || syncing) blockers.push("shipping_recalculating");
|
|
246
|
+
if (syncPending || syncing || shippingChoicePending) blockers.push("shipping_recalculating");
|
|
228
247
|
if (shippingStatus === "missing_address") blockers.push("shipping_address_required");
|
|
229
|
-
|
|
248
|
+
// A pick in flight already shows as selected — flagging "choose one" over a
|
|
249
|
+
// visibly chosen radio contradicts the screen; the transient code above
|
|
250
|
+
// covers the window, and the blocker returns if the pick fails.
|
|
251
|
+
if (shippingStatus === "choice_required" && !shippingChoicePending) {
|
|
252
|
+
blockers.push("shipping_method_required");
|
|
253
|
+
}
|
|
230
254
|
if (shippingStatus === "none_available" || addressError?.code === "shipping_not_available") {
|
|
231
255
|
blockers.push("shipping_not_available");
|
|
232
256
|
}
|
|
@@ -322,6 +346,7 @@ export function useCheckout(options = {}) {
|
|
|
322
346
|
chosenShippingMethod,
|
|
323
347
|
singleShippingMethod,
|
|
324
348
|
chooseShippingMethod,
|
|
349
|
+
shippingChoicePending,
|
|
325
350
|
// payment choice
|
|
326
351
|
paymentMethods,
|
|
327
352
|
paymentMethod,
|
|
@@ -24,6 +24,34 @@
|
|
|
24
24
|
* payload) returns an action's payload as-is.
|
|
25
25
|
*/
|
|
26
26
|
|
|
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(", ")}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
27
55
|
/** The stable error code a failed storefront call carries, if any. */
|
|
28
56
|
export function storefrontErrorCode(e) {
|
|
29
57
|
return e?.response?.data?.code ?? e?.data?.code ?? e?.code ?? null;
|
|
@@ -111,7 +139,16 @@ export function createStorefront(base44, { storageKey = "cart_token", storage }
|
|
|
111
139
|
/**
|
|
112
140
|
* Submit a review. `email` is required for a guest and ignored for a
|
|
113
141
|
* signed-in caller (the session's email always wins, so nobody can
|
|
114
|
-
* review as someone else). `
|
|
142
|
+
* review as someone else). At least one of `review` (the text) or
|
|
143
|
+
* `rating` (1–5 stars) is required — a stars-only submission is valid,
|
|
144
|
+
* and so is text-only; with neither this **throws synchronously**, naming
|
|
145
|
+
* the keys it did receive, instead of letting the server answer about a
|
|
146
|
+
* field the customer filled in.
|
|
147
|
+
*
|
|
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.
|
|
115
152
|
*
|
|
116
153
|
* Resolves to `{ review_id, status, verified }` — **`status` is
|
|
117
154
|
* `"approved"` or `"hold"` depending on the store's `auto_approve_reviews`
|
|
@@ -119,7 +156,17 @@ export function createStorefront(base44, { storageKey = "cart_token", storage }
|
|
|
119
156
|
* moderation. Rejects with `email_required` | `review_incomplete` |
|
|
120
157
|
* `invalid_rating` | `not_found`.
|
|
121
158
|
*/
|
|
122
|
-
submitReview(
|
|
159
|
+
submitReview(payload = {}) {
|
|
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);
|
|
166
|
+
const rated = rating != null && Number(rating) >= 1;
|
|
167
|
+
if (!String(review ?? "").trim() && !rated) {
|
|
168
|
+
throw new Error(`${REVIEW_SIGNATURE} — needs review text or a rating of 1-5`);
|
|
169
|
+
}
|
|
123
170
|
return inv("commerce/storefront-catalog", {
|
|
124
171
|
action: "submit-review",
|
|
125
172
|
product_id,
|