@base44/app-plugin-commerce 0.7.0 → 0.7.1

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 CHANGED
@@ -85,7 +85,7 @@ From your existing Base44 app:
85
85
  npx npq install <only the missing names> # npq audits the package before npm installs it
86
86
  ```
87
87
  See [`src/commerce/admin/README.md`](./src/commerce/admin/README.md) for the exact shadcn component list.
88
- 5. **Mount the admin** as a layout route in your app's `src/App.jsx` — the platform discovers an app's pages by reading the literal `<Route>` JSX in that file, so the screens the store owner should reach from the builder are declared there and the rest run off a splat handled by the kit's own router:
88
+ 5. **Mount the admin** as a layout route in your app's `src/App.jsx` — Base44 discovers an app's pages by reading the literal `<Route>` JSX in that file, so the screens that should be listed as pages are declared there and the rest run off a splat handled by the kit's own router:
89
89
  ```jsx
90
90
  import AdminApp, { AdminRoutes } from "@/commerce/admin";
91
91
  // inside your <Routes>:
@@ -96,7 +96,7 @@ From your existing Base44 app:
96
96
  <Route path="*" element={<AdminRoutes />} /> {/* editors, settings, webhooks */}
97
97
  </Route>
98
98
  ```
99
- Name the picker's section for them in `base44/ui.jsonc` (app-owned — edit in place): `{ "version": 1, "sections": [{ "path": "/store-admin/*", "name": "Store Management" }] }`
99
+ Name the section they group under in `base44/ui.jsonc` (app-owned — edit in place): `{ "version": 1, "sections": [{ "path": "/store-admin/*", "name": "Store Management" }] }`
100
100
  6. **Grant yourself the `admin` role** (Base44 dashboard → users, or `users.inviteUser(email, "admin")`). The admin UI refuses non-admins.
101
101
  7. **Seed the store.** Either open `/store-admin` and click **Initialize store defaults** on the first-run setup screen, or call `commerce/seed-store` directly — it creates the settings groups, the payment gateway rows (`offline` enabled, `card` off — enable it only with a provider wired) and — unless you pass your own `locations` — a fallback Shipping & Tax Location, plus the catalog: pass `products` (whole products with attributes — variants, categories, ribbons and taxonomy are created internally) or `with_sample_data: true` for the generic demo. Either way pass `store_name` (the app's name) — it is required on a first seed and becomes both the email subject prefix and the sender name. Once the `general` settings group exists the store counts as ready and the first-run screen stops appearing. Worked example: [`skills/commerce/install/03-data.md`](./skills/commerce/install/03-data.md); the full payload contract: [`skills/commerce/docs/api-admin.md`](./skills/commerce/docs/api-admin.md). Shipping zones are part of the same call — `locations` takes `continents: ["EU"]` and `rest_of_world: true`, so "€20 in Europe, €100 worldwide" is six lines.
102
102
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@base44/app-plugin-commerce",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
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",
@@ -8,7 +8,7 @@ carry_forward:
8
8
  - "Admin enforcement is three layers — AuthGuard (UI), admin-only entity RLS, requireAdmin() in every admin function. Never weaken any of them."
9
9
  - "/order-received must exist as a route: every payment link returns there, and confirming is what marks an order paid."
10
10
  - "The storefront header shows a visible \"Store manager\" link to /store-admin when the signed-in user's role is admin, and nothing for everyone else."
11
- - "Interleave: start image generation first → mount admin + build the storefront while images render → seed when the real image_url values are back (never the /__generating__/ placeholder) → payments last."
11
+ - "Interleave: start image generation first → mount admin + build the storefront → seed with the image_url the generate_image result already returned (never poll, never write a function to fetch URLs) → payments last."
12
12
  - "Entities are dotted + bracket-syntax only (`base44.entities[\"commerce.X\"]`); the map is ../docs/entities.md — never scan base44/entities/."
13
13
  ---
14
14
 
@@ -25,9 +25,13 @@ carry_forward:
25
25
  Image generation is the slowest step and nothing depends on it until seed time; the storefront doesn't wait on live data either.
26
26
 
27
27
  1. **Start image generation first** — every product image, before anything else.
28
- ⚑ The tool hands back a `/__generating__/…` placeholder immediately and the real
29
- `image_url` only when it finishes. Never write the placeholder into a file or a
30
- seed payload: it resolves to nothing and ships a broken image.
28
+ **The result already carries the real `image_url`** alongside a `placeholder_url`
29
+ — use `image_url` and move on. Nothing is pending, there is nothing to poll, and a
30
+ backend function calling `Core.GenerateImage` to "fetch the real URLs" is pure waste:
31
+ you already have them. If a `/__generating__/…` value does reach a file or a seed
32
+ payload, the platform swaps it for the real URL after the turn (in files *and* entity
33
+ records) — so a placeholder that renders broken in a mid-build preview is expected and
34
+ must not be "fixed".
31
35
  2. **Mount the admin (below) and build the storefront** while images render. Every
32
36
  request and response shape the pages build against is written out in
33
37
  [`./02-storefront.md`](./02-storefront.md), so they are written from the docs,
@@ -47,7 +51,7 @@ import AdminApp, { AdminRoutes } from "@/commerce/admin";
47
51
  import Dashboard from "@/commerce/admin/pages/Dashboard"; // …and orders/OrdersList,
48
52
  // products/ProductsList, customers/CustomersList, coupons/CouponsList, reports/Reports
49
53
 
50
- {/* Literal JSX — the picker reads this file, it never runs it. Do not refactor into a map. */}
54
+ {/* Literal JSX — the platform reads this file, it never runs it. Do not refactor into a map. */}
51
55
  <Route path="/store-admin" element={<AdminApp />}>
52
56
  <Route index element={<Dashboard />} />
53
57
  <Route path="orders" element={<OrdersList />} />
@@ -61,13 +65,13 @@ import Dashboard from "@/commerce/admin/pages/Dashboard"; // …and orders/Ord
61
65
  <Route path="/order-received" element={<OrderReceived />} /> {/* mandatory — see below */}
62
66
  ```
63
67
 
64
- - **Those seven lines, as they are.** An array, a spread or a `.map()` discovers *zero* routes. The splat is what keeps the picker to six real pages instead of 26: discovery ignores `path="*"`, so the editors (`orders/:id`), the settings tabs and the webhook screens stay navigable without appearing there and `<AdminRoutes />` still serves the admin's own 404.
68
+ - **Those seven lines, as they are.** The splat is what keeps the app's listed pages to six instead of 26: `path="*"` is skipped, so the editors, settings tabs and webhook screens stay navigable without appearing there, and `<AdminRoutes />` still serves the admin's own 404.
65
69
  - **Don't add `settings` to the list** — it is a tabbed layout around a nested route, so it only renders correctly from the splat.
66
70
  - **Elsewhere than `/store-admin`**: change the layout route's path and pass the prefix — `<AdminApp basePath="/backoffice" />`; the children are unchanged.
67
71
  - **Name the group** in `base44/ui.jsonc` — app-owned, so edit it in place, keep any other keys, never recreate a deleted one: `{ "version": 1, "sections": [{ "path": "/store-admin/*", "name": "Store Management" }] }`
68
72
  - **Give `/` something** — a blank app has no `/` route, and "page not found" at the app's own URL reads like a broken install.
69
- - **Link the admin from the storefront header** — else the merchant's only way in is typing the URL. Resolve the user once (`base44.auth.me()`; rejection/no session = not an admin, never blocking the page) and show a plainly visible "Store manager" link to `/store-admin` when `role === "admin"`, nothing at all for everyone else.
70
- - **`/order-received` is mandatory**, even offline-only: every payment link returns there and confirming is what marks an order paid — without it a paying customer hits a 404 and the order stays unpaid. It is one hook, `useOrderReturn()` ([`./02-storefront.md`](./02-storefront.md)); a different path must be set in Settings → General (`general.order_received_path`).
73
+ - **Link the admin from the storefront header** — otherwise the merchant has no way in but typing the URL. Resolve the signed-in user once (`base44.auth.me()`, rejection/no session = not an admin, never blocking the page) and render a plainly visible "Store manager" link to `/store-admin` in the header when `role === "admin"` — and nothing at all for everyone else.
74
+ - **`/order-received` is mandatory**, even offline-only: every payment link returns there, and confirming is what marks an order paid — without it a paying customer hits a 404 and the order stays unpaid. The page is one hook, `useOrderReturn()` ([`./02-storefront.md`](./02-storefront.md)). A different path must be set in Settings → General (`general.order_received_path`).
71
75
 
72
76
  ## Admin-role enforcement — do not weaken
73
77
 
@@ -48,7 +48,7 @@ import AdminApp from "@/commerce/admin";
48
48
  <Route path="/product/:slug" element={<ProductPage />} />
49
49
  {/* /bag, /checkout, and /order-received — which is mandatory */}
50
50
  </Route>
51
- <Route path="/store-admin" element={<AdminApp />}>…</Route> {/* own chrome; screens per ./01-install.md */}
51
+ <Route path="/store-admin" element={<AdminApp />}>…</Route> {/* own chrome, outside the provider */}
52
52
  </Routes>
53
53
  </BrowserRouter>
54
54
 
@@ -71,6 +71,8 @@ function StoreLayout() {
71
71
 
72
72
  The cost driver of a generated storefront is not wiring — it is decoration repeated inline. Encode identity **once**: in `index.css`, set the palette and type scale, then define the store's recurring surfaces as **10–15 composable classes** in Tailwind's components layer, named in *this* store's language (`.panel`, `.btn-cta`, `.label-mono`, `.field`, `.choice-row`, a heading scale, a price style — whatever *this* store repeats). Pages then carry short class names plus a couple of layout utilities. ⚑ **A utility run that appears twice becomes a class.**
73
73
 
74
+ ⚑ **Two `@apply` forms Tailwind rejects, and each breaks the whole build.** **Any** `/opacity` on a bracketed value is not a class — `text-[var(--ink)]/40` and `border-[#1B2422]/8` fail alike — so write plain CSS: `border-color: color-mix(in srgb, var(--ink) 40%, transparent)`. Translucent brand colours are the commonest thing a `.panel`/`.field` class wants, so reach for `color-mix` first. And `@apply group` is invalid; `group` is a marker for the element's `className`. Either is a postcss error that fails Vite, so *every* page renders blank — which reads as a broken import and sends you into `src/commerce/`. **Nothing rendering? Read `index.css` first.**
75
+
74
76
  The store's words work the same way: the states these hooks hand you recur across pages (an empty bag, an unbuyable product, an undeliverable address), so write that copy once in the store's voice — a small map per surface, as the sections below show. It is the half of a store's identity a kit cannot ship.
75
77
 
76
78
  **Concentrate identity; don't diffuse it.** The classes carry the look everywhere; on top of them, spend bespoke markup on **one or two signature moments per page** — the hero, the one product-page module that shows what these products are judged on — and render everything else as conventions in the classes. **The product page stays the storefront's richest surface**, and that richness is semantic: what the controls and rows *show*, which costs words rather than chrome. One navigation affordance per control (thumbnails *or* arrows, never both plus dots); checkout, bag and order-received are convention surfaces.
@@ -26,10 +26,9 @@ Tailwind + shadcn/ui + React Router) to get a full store back office.
26
26
  ```
27
27
 
28
28
  4. Mount it as a layout route in the app's own `src/App.jsx` — that is the file
29
- the platform discovers pages from, so the screens the store owner should be
30
- able to open from the builder are declared there literally, and the rest go
31
- to `<AdminRoutes />` on a splat. The full step is the skill's
32
- `install/01-install.md`.
29
+ Base44 discovers an app's pages from, so the screens that should be listed as
30
+ pages are declared there literally, and the rest go to `<AdminRoutes />` on a
31
+ splat. The full step is the skill's `install/01-install.md`.
33
32
 
34
33
  ```jsx
35
34
  import AdminApp, { AdminRoutes } from "@/commerce/admin";
@@ -12,13 +12,13 @@ export { default as AdminRoutes } from "./routes";
12
12
  /**
13
13
  * The store admin application.
14
14
  *
15
- * Mount it as a *layout route* in the app's own `src/App.jsx`, declare the few
16
- * screens the store owner should be able to open from the builder as literal
17
- * `<Route>` JSX under it, and hand everything deeper to `<AdminRoutes />` on a
18
- * splat. The platform discovers an app's pages by reading that file, so a screen
19
- * declared anywhere else is unreachable from the page picker — and a splat is
20
- * ignored by discovery, which is what keeps the editors and settings tabs out of
21
- * it while leaving them navigable.
15
+ * Mount it as a *layout route* in the app's own `src/App.jsx`: declare the few
16
+ * screens that should be listed among the app's pages there, as literal `<Route>`
17
+ * JSX, and hand everything deeper to `<AdminRoutes />` on a splat. Base44
18
+ * discovers an app's pages by reading that file rather than running it, so a
19
+ * route declared anywhere else is reachable by URL but never listed — and a
20
+ * splat is skipped by discovery, which is what keeps the editors and the
21
+ * settings tabs off the list while leaving them navigable.
22
22
  *
23
23
  * <Route path="/store-admin" element={<AdminApp />}>
24
24
  * <Route index element={<Dashboard />} />
@@ -94,8 +94,8 @@ function UnmatchedRoute() {
94
94
  /**
95
95
  * Every admin route, matched relative to the mount point.
96
96
  *
97
- * The app's `src/App.jsx` declares the handful of screens the builder's page
98
- * picker should listthe picker only sees literal `<Route>` JSX in that file —
97
+ * The app's `src/App.jsx` declares the handful of screens that should be listed
98
+ * among the app's pages discovery only sees literal `<Route>` JSX in that file —
99
99
  * and sends the rest here on a splat: `<Route path="*" element={<AdminRoutes />} />`.
100
100
  * A descendant `<Routes>` needs exactly that trailing splat to resolve against.
101
101
  * The overlap is deliberate: a path declared in App.jsx wins there, and the same