@base44/app-plugin-commerce 0.6.7 → 0.7.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 CHANGED
@@ -85,12 +85,18 @@ 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 router** in your app:
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:
89
89
  ```jsx
90
- import AdminApp from "@/commerce/admin";
90
+ import AdminApp, { AdminRoutes } from "@/commerce/admin";
91
91
  // inside your <Routes>:
92
- <Route path="/store-admin/*" element={<AdminApp />} />
92
+ <Route path="/store-admin" element={<AdminApp />}>
93
+ <Route index element={<Dashboard />} />
94
+ <Route path="orders" element={<OrdersList />} />
95
+ {/* …products, customers, coupons, reports… */}
96
+ <Route path="*" element={<AdminRoutes />} /> {/* editors, settings, webhooks */}
97
+ </Route>
93
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" }] }`
94
100
  6. **Grant yourself the `admin` role** (Base44 dashboard → users, or `users.inviteUser(email, "admin")`). The admin UI refuses non-admins.
95
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.
96
102
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@base44/app-plugin-commerce",
3
- "version": "0.6.7",
3
+ "version": "0.7.0",
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",
@@ -217,8 +217,10 @@
217
217
  "\n" +
218
218
  " 1. No deps to add: sonner, recharts and react-markdown ship with the default\n" +
219
219
  " Base44 template — check package.json and npm i only what is truly missing\n" +
220
- ' 2. Mount the admin router: <Route path="/store-admin/*" element={<AdminApp />} />\n' +
221
- " plus the mandatory /order-received route (useOrderReturn + your markup)\n" +
220
+ ' 2. Mount the admin in src/App.jsx: <Route path="/store-admin" element={<AdminApp />}>\n' +
221
+ " with the main screens as literal child <Route>s (that file is what the platform\n" +
222
+ ' discovers pages from) and <Route path="*" element={<AdminRoutes />} /> for the\n' +
223
+ " rest, plus the mandatory /order-received route (useOrderReturn + your markup)\n" +
222
224
  " 3. Seed the store — one commerce/seed-store call (store_name required) takes the\n" +
223
225
  " catalog, currency, shipping locations and payment methods\n" +
224
226
  " 4. CLI installs only: npx base44 agents push (the hosted runtime syncs agents on write)"
@@ -117,7 +117,7 @@ batch (above).
117
117
 
118
118
  | Topic | Open when | Size |
119
119
  |---|---|---|
120
- | [`install/01-install.md`](./install/01-install.md) | installing — routes you to 02 and 03 | 6K |
120
+ | [`install/01-install.md`](./install/01-install.md) | installing — routes you to 02 and 03 | 8K |
121
121
  | [`install/02-storefront.md`](./install/02-storefront.md) | building storefront pages | 35K |
122
122
  | [`install/03-data.md`](./install/03-data.md) | seeding catalog, shipping rates/zones, payments; re-callable per slice | 11K |
123
123
  | [`docs/entities.md`](./docs/entities.md) | any direct entity read/write ("which entity holds X") | 11K |
@@ -1,13 +1,14 @@
1
1
  ---
2
2
  stage: install/01
3
3
  read_when: "The commerce kit's files were just copied into the app, or you are installing it now."
4
- skip_when: "The admin already mounts at /store-admin/* behind the shipped AuthGuard and / routes somewhere real."
4
+ skip_when: "src/App.jsx already declares the admin's screens as literal <Route> JSX under a /store-admin layout route, and / routes somewhere real."
5
5
  forget_when: "The checklist at the bottom of this file passes (admin mounts, / routes somewhere real, /order-received exists)."
6
6
  carry_forward:
7
+ - "The admin's six picker-visible routes are literal <Route> JSX in src/App.jsx (the platform discovers pages by reading that file — an array or a .map() discovers nothing); the rest run off its <Route path=\"*\" element={<AdminRoutes />} />."
7
8
  - "Admin enforcement is three layers — AuthGuard (UI), admin-only entity RLS, requireAdmin() in every admin function. Never weaken any of them."
8
9
  - "/order-received must exist as a route: every payment link returns there, and confirming is what marks an order paid."
9
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."
10
- - "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."
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
12
  - "Entities are dotted + bracket-syntax only (`base44.entities[\"commerce.X\"]`); the map is ../docs/entities.md — never scan base44/entities/."
12
13
  ---
13
14
 
@@ -24,13 +25,9 @@ carry_forward:
24
25
  Image generation is the slowest step and nothing depends on it until seed time; the storefront doesn't wait on live data either.
25
26
 
26
27
  1. **Start image generation first** — every product image, before anything else.
27
- **The result already carries the real `image_url`** alongside a `placeholder_url`
28
- — use `image_url` and move on. Nothing is pending, there is nothing to poll, and a
29
- backend function calling `Core.GenerateImage` to "fetch the real URLs" is pure waste:
30
- you already have them. If a `/__generating__/…` value does reach a file or a seed
31
- payload, the platform swaps it for the real URL after the turn (in files *and* entity
32
- records) — so a placeholder that renders broken in a mid-build preview is expected and
33
- must not be "fixed".
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.
34
31
  2. **Mount the admin (below) and build the storefront** while images render. Every
35
32
  request and response shape the pages build against is written out in
36
33
  [`./02-storefront.md`](./02-storefront.md), so they are written from the docs,
@@ -41,21 +38,36 @@ Image generation is the slowest step and nothing depends on it until seed time;
41
38
 
42
39
  The only dependency edges are *image URLs → seed payload* and *seed done → real products on the pages*.
43
40
 
44
- ## Mount the admin router
41
+ ## Mount the admin
45
42
 
46
- ```jsx
47
- import AdminApp from "@/commerce/admin";
48
- import { Navigate } from "react-router-dom";
43
+ The admin mounts as a **layout route in the app's own `src/App.jsx`**: the six screens the store owner opens from the builder are declared there as literal `<Route>` JSX, and everything deeper goes to `<AdminRoutes />` on a splat. The platform discovers an app's pages by reading that file — a screen declared anywhere else is unreachable from the page picker.
49
44
 
50
- <Route path="/store-admin/*" element={<AdminApp />} />
45
+ ```jsx
46
+ import AdminApp, { AdminRoutes } from "@/commerce/admin";
47
+ import Dashboard from "@/commerce/admin/pages/Dashboard"; // …and orders/OrdersList,
48
+ // products/ProductsList, customers/CustomersList, coupons/CouponsList, reports/Reports
49
+
50
+ {/* Literal JSX — the picker reads this file, it never runs it. Do not refactor into a map. */}
51
+ <Route path="/store-admin" element={<AdminApp />}>
52
+ <Route index element={<Dashboard />} />
53
+ <Route path="orders" element={<OrdersList />} />
54
+ <Route path="products" element={<ProductsList />} />
55
+ <Route path="customers" element={<CustomersList />} />
56
+ <Route path="coupons" element={<CouponsList />} />
57
+ <Route path="reports" element={<Reports />} />
58
+ <Route path="*" element={<AdminRoutes />} /> {/* editors, settings, webhooks */}
59
+ </Route>
51
60
  <Route path="/" element={<Navigate to="/store-admin" replace />} /> {/* until a storefront exists */}
52
61
  <Route path="/order-received" element={<OrderReceived />} /> {/* mandatory — see below */}
53
62
  ```
54
63
 
55
- - **The `/*` splat is required** without it every nested admin link 404s. Mounting elsewhere: `<AdminApp basePath="/backoffice" />` (prefix without the splat).
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.
65
+ - **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
+ - **Elsewhere than `/store-admin`**: change the layout route's path and pass the prefix — `<AdminApp basePath="/backoffice" />`; the children are unchanged.
67
+ - **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" }] }`
56
68
  - **Give `/` something** — a blank app has no `/` route, and "page not found" at the app's own URL reads like a broken install.
57
- - **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.
58
- - **`/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`).
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`).
59
71
 
60
72
  ## Admin-role enforcement — do not weaken
61
73
 
@@ -69,7 +81,7 @@ Storefront functions are public on purpose (per-action verification, above). To
69
81
 
70
82
  ## Done — forget this file
71
83
 
72
- - [ ] `/store-admin/*` mounted with the splat, behind the shipped `AuthGuard`; the three enforcement layers untouched.
84
+ - [ ] The `/store-admin` layout route is in `src/App.jsx` with its six literal `<Route>` screens and the `path="*"` → `<AdminRoutes />` splat; `base44/ui.jsonc` names the section; the three enforcement layers untouched.
73
85
  - [ ] `/` routes somewhere real; `/order-received` is a route.
74
86
  - [ ] The storefront header shows a visible `/store-admin` link to signed-in admins, and to nobody else.
75
87
  - [ ] Anonymous function invocation is allowed in the app's settings.
@@ -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 />} /> {/* own chrome, outside the provider */}
51
+ <Route path="/store-admin" element={<AdminApp />}>…</Route> {/* own chrome; screens per ./01-install.md */}
52
52
  </Routes>
53
53
  </BrowserRouter>
54
54
 
@@ -71,8 +71,6 @@ 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
-
76
74
  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.
77
75
 
78
76
  **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.
@@ -25,12 +25,21 @@ Tailwind + shadcn/ui + React Router) to get a full store back office.
25
25
  npx npq install <only the missing names> # npq audits the package before npm installs it
26
26
  ```
27
27
 
28
- 4. Mount the app in your router:
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
33
 
30
34
  ```jsx
31
- import AdminApp from "@/commerce/admin";
32
-
33
- <Route path="/store-admin/*" element={<AdminApp />} />
35
+ import AdminApp, { AdminRoutes } from "@/commerce/admin";
36
+
37
+ <Route path="/store-admin" element={<AdminApp />}>
38
+ <Route index element={<Dashboard />} />
39
+ <Route path="orders" element={<OrdersList />} />
40
+ {/* …products, customers, coupons, reports… */}
41
+ <Route path="*" element={<AdminRoutes />} />
42
+ </Route>
34
43
  // mounted elsewhere? → <AdminApp basePath="/backoffice" />
35
44
  ```
36
45
 
@@ -75,8 +84,8 @@ npx shadcn@latest add <component>
75
84
  ## Layout of this folder
76
85
 
77
86
  ```
78
- index.jsx AdminApp: providers → auth guard → layout → routes
79
- routes.jsx Route table + <AdminRoutes/>
87
+ index.jsx AdminApp: providers → auth guard → layout → <Outlet/>
88
+ routes.jsx Route table + <AdminRoutes/> (the splat handler App.jsx delegates to)
80
89
  layout/ AdminLayout, Sidebar, Topbar, AuthGuard (admin-role gate), AccessDenied
81
90
  bot/ StoreAdminBot (chat panel over the commerce/StoreAdmin agent), Markdown (GFM renderer)
82
91
  context/ SettingsContext (store settings + first-run seeding), BasePathContext
@@ -1,4 +1,5 @@
1
1
  import React from "react";
2
+ import { useOutlet } from "react-router-dom";
2
3
  import { Toaster } from "sonner";
3
4
  import AuthGuard from "./layout/AuthGuard";
4
5
  import AdminLayout from "./layout/AdminLayout";
@@ -6,27 +7,50 @@ import AdminRoutes from "./routes";
6
7
  import { SettingsProvider } from "./context/SettingsContext";
7
8
  import { BasePathProvider } from "./context/BasePathContext";
8
9
 
10
+ export { default as AdminRoutes } from "./routes";
11
+
9
12
  /**
10
13
  * The store admin application.
11
14
  *
12
- * Mount inside your app's router:
13
- * <Route path="/store-admin/*" element={<AdminApp />} />
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.
22
+ *
23
+ * <Route path="/store-admin" element={<AdminApp />}>
24
+ * <Route index element={<Dashboard />} />
25
+ * <Route path="orders" element={<OrdersList />} />
26
+ * …
27
+ * <Route path="*" element={<AdminRoutes />} />
28
+ * </Route>
14
29
  *
15
30
  * If mounted somewhere other than /store-admin, pass the prefix:
16
- * <Route path="/backoffice/*" element={<AdminApp basePath="/backoffice" />} />
31
+ * <Route path="/backoffice" element={<AdminApp basePath="/backoffice" />}>
32
+ *
33
+ * The providers belong here, above the outlet, and must not be repeated per
34
+ * route: SettingsProvider fetches commerce.StoreSettings on mount, so wrapping
35
+ * each route would cost a round trip and a remounted sidebar on every admin
36
+ * navigation.
17
37
  *
18
38
  * Requires an authenticated user with role "admin" (enforced by AuthGuard,
19
39
  * and independently by entity RLS + requireAdmin() in backend functions).
20
40
  */
21
41
  export default function AdminApp({ basePath = "/store-admin" }) {
42
+ // Installs from before the routes moved into App.jsx mount the whole admin
43
+ // behind one splat route (`<Route path="/store-admin/*" element={<AdminApp />} />`)
44
+ // and declare no children, so nothing ever fills the outlet. Fall back to the
45
+ // kit's own router there rather than drawing an empty page area under the
46
+ // sidebar; those apps keep working until an agent rewrites their App.jsx.
47
+ const outlet = useOutlet();
22
48
  return (
23
49
  <BasePathProvider value={basePath}>
24
50
  <Toaster richColors position="top-right" />
25
51
  <AuthGuard>
26
52
  <SettingsProvider>
27
- <AdminLayout>
28
- <AdminRoutes />
29
- </AdminLayout>
53
+ <AdminLayout>{outlet ?? <AdminRoutes />}</AdminLayout>
30
54
  </SettingsProvider>
31
55
  </AuthGuard>
32
56
  </BasePathProvider>
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Mount-path handling for the admin app.
3
3
  *
4
- * The admin is mounted with a splat route `<Route path="/store-admin/*">` and both
5
- * halves of that pattern leak into places they shouldn't: the `basePath` prop
4
+ * Installs from before the routes moved into App.jsx mount the admin with a splat
5
+ * route `<Route path="/store-admin/*">` and both halves of that pattern leak
6
+ * into places they shouldn't: the `basePath` prop
6
7
  * gets the pattern pasted in verbatim, and the literal URL `/store-admin/*` gets
7
8
  * opened (pattern copied into the address bar, or a nav link built from the
8
9
  * route table). Neither is a real page, so both are normalized here rather than
@@ -91,7 +91,20 @@ function UnmatchedRoute() {
91
91
  return isMountPatternPath(params["*"]) ? <Navigate to={href()} replace /> : <NotFound />;
92
92
  }
93
93
 
94
- /** All admin routes. Rendered inside AdminLayout; mount AdminApp at `/store-admin/*`. */
94
+ /**
95
+ * Every admin route, matched relative to the mount point.
96
+ *
97
+ * The app's `src/App.jsx` declares the handful of screens the builder's page
98
+ * picker should list — the picker only sees literal `<Route>` JSX in that file —
99
+ * and sends the rest here on a splat: `<Route path="*" element={<AdminRoutes />} />`.
100
+ * A descendant `<Routes>` needs exactly that trailing splat to resolve against.
101
+ * The overlap is deliberate: a path declared in App.jsx wins there, and the same
102
+ * entry here keeps this table complete for anything App.jsx does not name —
103
+ * `orders/:id` and the settings tabs among them.
104
+ *
105
+ * With no splat and no children at all — an install predating the move into
106
+ * App.jsx — `index.jsx` renders this component directly instead.
107
+ */
95
108
  export default function AdminRoutes() {
96
109
  return (
97
110
  <Routes>