create-cartbase 0.1.6 → 0.1.8

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.
@@ -1,226 +1,233 @@
1
- # Build a storefront
2
-
3
- **This runbook takes you from a blank Next.js app to a completed checkout
4
- against your Cartbase store.** It is written to be followed by a developer
5
- or handed to a coding agent as-is. Each step names the domain doc that
6
- carries the full contracts (shapes, curls, error codes, settings). Follow
7
- the steps in order — later steps assume earlier wiring exists. If anything
8
- here is unclear or wrong, it's a documentation bug — report it.
9
-
10
- What you need before starting (all three are in your Cartbase admin under
11
- **Settings**):
12
-
13
- | Input | Example | Where it goes |
14
- |---|---|---|
15
- | API origin | `https://admin.bitfar.co` | `NEXT_PUBLIC_CARTBASE_URL` |
16
- | Store client id | `1e7a4c02-9b31-4f7e-8d2a-5c6f90ab12cd` (uuid) | `NEXT_PUBLIC_CARTBASE_CLIENT_ID` |
17
- | Publishable API key | `pk_…` (optional, channel scope) | `NEXT_PUBLIC_CARTBASE_PUBLISHABLE_KEY` |
18
-
19
- Sanity-check the store before writing any code:
20
-
21
- ```bash
22
- # The regions listing is the cheapest liveness + auth probe.
23
- curl -sf "$BASE/api/store/regions" -H "x-client-id: $CLIENT_ID" | grep -q '"regions"'
24
- ```
25
-
26
- ## Step 1 — Scaffold + package
27
-
28
- Create the Next.js app (App Router) and add the package:
29
-
30
- ```bash
31
- # doc-noexecscaffolding happens in YOUR repo, not against the API.
32
- bunx create-next-app@latest my-store --ts --app --tailwind
33
- cd my-store && bun add @cartbase/storefront
34
- ```
35
-
36
- - The package is **source-shipped TypeScript** add
37
- `transpilePackages: ["@cartbase/storefront"]` to `next.config` or nothing
38
- from it will compile.
39
- - **Styling is two imports and nothing else.** Tailwind 4 is CSS-first, so
40
- there is no config file and no preset to register. In your `globals.css`:
41
-
42
- ```css
43
- @import "tailwindcss";
44
- @import "@cartbase/storefront/theme";
45
-
46
- /* The package ships TypeScript source, so Tailwind must scan it or the
47
- components render unstyled. */
48
- @source "../../node_modules/@cartbase/storefront/src";
49
-
50
- /* Dark mode is class-based: put `.dark` on <html>. */
51
- @custom-variant dark (&:where(.dark, .dark *));
52
- ```
53
-
54
- The theme **ships filled**, so the store renders designed before you choose
55
- a single value. To make it yours, override the token values (`--primary`,
56
- `--background`, `--radius-base`, the font families) after the imports.
57
- Never redefine the token *names*: components reference them, and the whole
58
- library repaints from the values alone.
59
- - Pin the package version storefronts never float `latest`.
60
- - Monorepo caveat: when the app lives in a workspace, set
61
- `outputFileTracingRoot` in `next.config` Next infers the root from
62
- the nearest stray lockfile, and a wrong root silently breaks
63
- page-segment hydration in dev (buttons render but nothing responds).
64
-
65
- ## Step 2 The client seam
66
-
67
- Construct ONE `StorefrontClient` per scope and pass it to every SDK call
68
- (all SDK functions take the client as first argument — see any domain doc's
69
- SDK line):
70
-
71
- - **Server** (RSC, server actions): construct per request; `getAuthToken`
72
- reads the customer session cookie; `getLocale` reads the locale cookie.
73
- - **Browser**: construct once; token from your session store.
74
-
75
- Auth model (recap — full detail in [auth.md](auth.md)):
76
- `x-client-id` always; `x-publishable-api-key` when the store gave you one
77
- (it scopes the catalog and carts to the key's sales channels);
78
- `authorization: Bearer <jwt>` once a customer is logged in.
79
-
80
- **CORS (bites every browser client):** the store API sends NO CORS
81
- headers today, so browser-side SDK calls (cart drawer mutations, the
82
- whole checkout orchestration) only work same-origin. Unless your
83
- storefront is served from the Cartbase deployment origin itself, proxy the
84
- store surface through your own origin in Next.js one rewrite does it
85
- and give the BROWSER client `window.location.origin` as `baseUrl`
86
- (server-side calls hit `NEXT_PUBLIC_CARTBASE_URL` directly and are
87
- unaffected; see `examples/storefront/next.config.ts` for the working
88
- rewrite):
89
-
90
- ```js
91
- // next.config.tsproxy browser SDK traffic to the API origin
92
- async rewrites() {
93
- return [{ source: "/api/store/:path*",
94
- destination: `${process.env.NEXT_PUBLIC_CARTBASE_URL}/api/store/:path*` }]
95
- }
96
- ```
97
-
98
- ## Step 3 Store configuration bootstrap
99
-
100
- Fetch once at layout level, cache per the docs' cache headers:
101
-
102
- 1. [regions.md](regions.md) — regions (→ region_id for pricing + payment
103
- providers), currencies, supported locales.
104
- 2. [integrations.md](integrations.md) — `GET /api/store/integrations`: which
105
- carriers are enabled (pickup points/lockers for checkout), COD fee
106
- presence, and the **tracking block** (pixel/GA4/GTM/ads public ids).
107
- 3. [consent.md](consent.md) the CMP config for the consent banner.
108
-
109
- ## Step 4 Layout: consent, tracking, navigation
110
-
111
- Order inside `<body>` matters (contracts in [components.md](components.md)
112
- and [consent.md](consent.md)):
113
-
114
- 1. `<ConsentInit>` FIRST child of body static and synchronous, never
115
- awaits a fetch (first-hit consent race otherwise).
116
- 2. Tracking mounts (`<MetaPixel>`, `<GA4>`, GTM) gated on the consent
117
- state and fed by the integrations tracking block, never by env vars.
118
- 3. Navigation from [menus.md](menus.md) — `main-menu` / `footer` handles;
119
- an unknown handle 404s and must render as "no nav", never crash.
120
-
121
- ## Step 5Catalog
122
-
123
- - [products.md](products.md) listing + PDP. Always pass a pricing context
124
- (`currency_code` or `region_id`) or prices come back undecorated; render
125
- `variant.calculated_price`, fall back to base `prices[]`. **Never cache a
126
- `calculated_price` response shared when a customer JWT was present**
127
- prices vary by customer group.
128
- - [collections.md](collections.md)collection pages use the membership
129
- endpoint (`/collections/:id/products`) which honors the admin's sort.
130
- - [categories.md](categories.md) — category tree, tags, types.
131
- - [search.md](search.md) search page: `q` + facets from the response
132
- (render buckets, apply via the documented query params), typo-tolerant,
133
- synonym-aware. Related products on the PDP come from the same doc.
134
- - SEO: `seo_title`/`seo_description` fields with title/description
135
- fallbacks; path conventions are `/products/<handle>`,
136
- `/collections/<handle>`, `/categories/<handle>`.
137
-
138
- ## Step 6 Content
139
-
140
- - [content.md](content.md) `/pages/<handle>` and `/blogs/<handle>` routes;
141
- body HTML is server-sanitized, safe to render raw. Every store seeds
142
- policy pages (`privacy-policy`, `terms-of-service`, `refund-policy`,
143
- `shipping-policy`) — link them in the footer.
144
- - [metaobjects.md](metaobjects.md) — merchant-defined content (size charts
145
- via the product→metafield→metaobject chain).
146
- - [redirects.md](redirects.md) — call ONLY from your `not-found` handler;
147
- 301 when `to_path` is non-null. Never on regular page loads.
148
-
149
- ## Step 7 Cart
150
-
151
- [carts.md](carts.md): create the cart lazily on first add-to-cart with the
152
- region + (optionally) sales channel; persist `cart.id` in a cookie; all cart
153
- mutations return the decorated cart totals are SERVER truth, render them
154
- verbatim, never compute client-side. Line items, quantity updates, deletes,
155
- and the customer-attach call after login are all in that doc.
156
- [gift-cards.md](gift-cards.md): the apply/remove endpoints + the three
157
- decoration fields (`gift_cards[]`, `gift_card_total`,
158
- `gift_card_remainder`) your summary UI must render.
159
-
160
- ## Step 8Checkout
161
-
162
- [checkout.md](checkout.md) is the authoritative sequence. In brief:
163
-
164
- 1. List shipping options and payment providers **with `cart_id`** — the
165
- server filters both through the merchant's checkout rules; your UI never
166
- hides methods on its own.
167
- 2. Carrier pickers (office/locker) come from the integrations config
168
- ([integrations.md](integrations.md)); the chosen point goes into
169
- `carrier_metadata`.
170
- 3. Buy click = `prepare-checkout` (ONE call: address + shipping method +
171
- payment session at the final amount) for card: Stripe
172
- `confirmPayment(client_secret)` `complete`. For COD: `complete`
173
- directly. Gift-card-covered carts skip the provider entirely.
174
- 4. Handle the documented failure codes (`checkout_method_hidden`,
175
- `account_required`, `gift_card_insufficient_balance`, cart-vs-order
176
- union on complete) — each has a UI recovery path described in the doc.
177
- 5. `sync-payment-amount` after any total-changing edit on the payment step;
178
- `refresh-payment-if-terminal` only from Elements `loaderror` / aged-cart
179
- mount.
180
- 6. Order confirmation renders from [orders.md](orders.md)
181
- (`/orders/display/:displayId` embeds items, fulfillments, tracking).
182
-
183
- ## Step 9Customer accounts
184
-
185
- [auth.md](auth.md): passwordless email-code login (request verify →
186
- Bearer session). [customers.md](customers.md): profile, addresses,
187
- order history, issued documents (invoices). Respect `accounts_mode`
188
- (store setting): `required` blocks guest checkout with `403
189
- account_required`; `disabled` means render no account UI at all. Gate B2B
190
- content on `customer.account_status === "approved"`.
191
-
192
- ## Step 10 Reviews
193
-
194
- [reviews.md](reviews.md): the PDP widget (`/reviews/widget` aggregate +
195
- first page + display options in one call) and the token wizard route for
196
- email CTA links (`/review/<token>`): validate token submit rating+body
197
- optional photo step mints the reward code. Resume rules are in the doc.
198
-
199
- ## Step 11Tracking events
200
-
201
- [components.md](components.md) tracking section: fire client events via the
202
- package helpers; **Purchase MUST use `eventID = "purchase_" +
203
- order.display_id`** so Meta dedupes browser Pixel against the server CAPI
204
- event; write the attribution keys (fbp/fbc/anon-id/ga session) into
205
- `cart.metadata` (consent-gated) so server events inherit them.
206
-
207
- ## Step 12 — Go-live checklist
208
-
209
- - [ ] Pricing context passed on every catalog surface; no shared caching of
210
- JWT-priced responses.
211
- - [ ] Consent banner renders for an unconfigured store (defaults are
212
- server-applied); tags mount only behind consent.
213
- - [ ] Checkout completes: card (Stripe live), COD (fee renders from config),
214
- gift card (partial + full cover).
215
- - [ ] Not-found handler consults redirects; policy pages linked.
216
- - [ ] Order confirmation + emails render totals identical to the cart.
217
- - [ ] Locale switching keeps cart + session; EUR everywhere.
218
- - [ ] The store's publishable key (if any) is set — B2B catalogs are
219
- key-scoped and look "missing products" without it.
220
-
221
- ## Step 13 Ship it
222
-
223
- [deploy.md](deploy.md): send the app to Cartbase hosting permanent
224
- preview URL on every deploy, **Publish** to go live, deploy history as
225
- rollback. Hosted storefronts get the three inputs above injected
226
- automatically, so there is nothing to configure.
1
+ # Build a storefront
2
+
3
+ **This runbook takes you from a blank Next.js app to a completed checkout
4
+ against your Cartbase store.** It is written to be followed by a developer
5
+ or handed to a coding agent as-is. Each step names the domain doc that
6
+ carries the full contracts (shapes, curls, error codes, settings). Follow
7
+ the steps in order — later steps assume earlier wiring exists. If anything
8
+ here is unclear or wrong, it's a documentation bug — report it.
9
+
10
+ What you need before starting: ONE value, the store's publishable key.
11
+ Your Cartbase admin hands it out on the **Storefront** page (and lists it
12
+ under Settings → API keys).
13
+
14
+ | Input | Example | Where it goes |
15
+ |---|---|---|
16
+ | Publishable API key | `pk_…` | `NEXT_PUBLIC_CARTBASE_PUBLISHABLE_KEY` |
17
+
18
+ The key names the store on its own and scopes the catalog to the key's
19
+ sales channels. The API origin is the platform's, `https://admin.cartbase.ai`
20
+ (`NEXT_PUBLIC_CARTBASE_URL` only overrides it for a local or staging
21
+ platform). The store's id is not an input: hosted builds receive it, and
22
+ `GET /api/store/store` answers it for anything that needs it.
23
+
24
+ Sanity-check the store before writing any code:
25
+
26
+ ```bash
27
+ # The regions listing is the cheapest liveness + auth probe.
28
+ curl -sf "$BASE/api/store/regions" -H "x-publishable-api-key: $PUBLISHABLE_KEY" | grep -q '"regions"'
29
+ ```
30
+
31
+ ## Step 1 Scaffold + package
32
+
33
+ Create the Next.js app (App Router) and add the package:
34
+
35
+ ```bash
36
+ # doc-noexec scaffolding happens in YOUR repo, not against the API.
37
+ bunx create-next-app@latest my-store --ts --app --tailwind
38
+ cd my-store && bun add @cartbase/storefront
39
+ ```
40
+
41
+ - The package is **source-shipped TypeScript** — add
42
+ `transpilePackages: ["@cartbase/storefront"]` to `next.config` or nothing
43
+ from it will compile.
44
+ - **Styling is two imports and nothing else.** Tailwind 4 is CSS-first, so
45
+ there is no config file and no preset to register. In your `globals.css`:
46
+
47
+ ```css
48
+ @import "tailwindcss";
49
+ @import "@cartbase/storefront/theme";
50
+
51
+ /* The package ships TypeScript source, so Tailwind must scan it or the
52
+ components render unstyled. */
53
+ @source "../../node_modules/@cartbase/storefront/src";
54
+
55
+ /* Dark mode is class-based: put `.dark` on <html>. */
56
+ @custom-variant dark (&:where(.dark, .dark *));
57
+ ```
58
+
59
+ The theme **ships filled**, so the store renders designed before you choose
60
+ a single value. To make it yours, override the token values (`--primary`,
61
+ `--background`, `--radius-base`, the font families) after the imports.
62
+ Never redefine the token *names*: components reference them, and the whole
63
+ library repaints from the values alone.
64
+ - Pin the package version — storefronts never float `latest`.
65
+ - Monorepo caveat: when the app lives in a workspace, set
66
+ `outputFileTracingRoot` in `next.config` — Next infers the root from
67
+ the nearest stray lockfile, and a wrong root silently breaks
68
+ page-segment hydration in dev (buttons render but nothing responds).
69
+
70
+ ## Step 2 — The client seam
71
+
72
+ Construct ONE `StorefrontClient` per scope and pass it to every SDK call
73
+ (all SDK functions take the client as first argument — see any domain doc's
74
+ SDK line):
75
+
76
+ - **Server** (RSC, server actions): construct per request; `getAuthToken`
77
+ reads the customer session cookie; `getLocale` reads the locale cookie.
78
+ - **Browser**: construct once; token from your session store.
79
+
80
+ Auth model (recap full detail in [auth.md](auth.md)):
81
+ `x-publishable-api-key` always (it names the store and scopes the catalog
82
+ and carts to the key's sales channels; the SDK sends it for you);
83
+ `authorization: Bearer <jwt>` once a customer is logged in. `x-client-id`
84
+ is the platform's own door and is accepted in the key's place; the
85
+ endpoint docs' curls use it because the docs harness runs as the platform.
86
+
87
+ **CORS (bites every browser client):** the store API sends NO CORS
88
+ headers today, so browser-side SDK calls (cart drawer mutations, the
89
+ whole checkout orchestration) only work same-origin. Unless your
90
+ storefront is served from the Cartbase deployment origin itself, proxy the
91
+ store surface through your own origin in Next.js one rewrite does it
92
+ and give the BROWSER client `window.location.origin` as `baseUrl`
93
+ (server-side calls hit `NEXT_PUBLIC_CARTBASE_URL` directly and are
94
+ unaffected; see `examples/storefront/next.config.ts` for the working
95
+ rewrite):
96
+
97
+ ```js
98
+ // next.config.tsproxy browser SDK traffic to the API origin
99
+ async rewrites() {
100
+ return [{ source: "/api/store/:path*",
101
+ destination: `${process.env.NEXT_PUBLIC_CARTBASE_URL}/api/store/:path*` }]
102
+ }
103
+ ```
104
+
105
+ ## Step 3 Store configuration bootstrap
106
+
107
+ Fetch once at layout level, cache per the docs' cache headers:
108
+
109
+ 1. [regions.md](regions.md)regions (→ region_id for pricing + payment
110
+ providers), currencies, supported locales.
111
+ 2. [integrations.md](integrations.md) — `GET /api/store/integrations`: which
112
+ carriers are enabled (pickup points/lockers for checkout), COD fee
113
+ presence, and the **tracking block** (pixel/GA4/GTM/ads public ids).
114
+ 3. [consent.md](consent.md) the CMP config for the consent banner.
115
+
116
+ ## Step 4Layout: consent, tracking, navigation
117
+
118
+ Order inside `<body>` matters (contracts in [components.md](components.md)
119
+ and [consent.md](consent.md)):
120
+
121
+ 1. `<ConsentInit>` FIRST child of body static and synchronous, never
122
+ awaits a fetch (first-hit consent race otherwise).
123
+ 2. Tracking mounts (`<MetaPixel>`, `<GA4>`, GTM) gated on the consent
124
+ state and fed by the integrations tracking block, never by env vars.
125
+ 3. Navigation from [menus.md](menus.md) `main-menu` / `footer` handles;
126
+ an unknown handle 404s and must render as "no nav", never crash.
127
+
128
+ ## Step 5 Catalog
129
+
130
+ - [products.md](products.md) — listing + PDP. Always pass a pricing context
131
+ (`currency_code` or `region_id`) or prices come back undecorated; render
132
+ `variant.calculated_price`, fall back to base `prices[]`. **Never cache a
133
+ `calculated_price` response shared when a customer JWT was present**
134
+ prices vary by customer group.
135
+ - [collections.md](collections.md) collection pages use the membership
136
+ endpoint (`/collections/:id/products`) which honors the admin's sort.
137
+ - [categories.md](categories.md) — category tree, tags, types.
138
+ - [search.md](search.md)search page: `q` + facets from the response
139
+ (render buckets, apply via the documented query params), typo-tolerant,
140
+ synonym-aware. Related products on the PDP come from the same doc.
141
+ - SEO: `seo_title`/`seo_description` fields with title/description
142
+ fallbacks; path conventions are `/products/<handle>`,
143
+ `/collections/<handle>`, `/categories/<handle>`.
144
+
145
+ ## Step 6 — Content
146
+
147
+ - [content.md](content.md) `/pages/<handle>` and `/blogs/<handle>` routes;
148
+ body HTML is server-sanitized, safe to render raw. Every store seeds
149
+ policy pages (`privacy-policy`, `terms-of-service`, `refund-policy`,
150
+ `shipping-policy`) — link them in the footer.
151
+ - [metaobjects.md](metaobjects.md) merchant-defined content (size charts
152
+ via the product→metafield→metaobject chain).
153
+ - [redirects.md](redirects.md)call ONLY from your `not-found` handler;
154
+ 301 when `to_path` is non-null. Never on regular page loads.
155
+
156
+ ## Step 7 Cart
157
+
158
+ [carts.md](carts.md): create the cart lazily on first add-to-cart with the
159
+ region + (optionally) sales channel; persist `cart.id` in a cookie; all cart
160
+ mutations return the decorated cart totals are SERVER truth, render them
161
+ verbatim, never compute client-side. Line items, quantity updates, deletes,
162
+ and the customer-attach call after login are all in that doc.
163
+ [gift-cards.md](gift-cards.md): the apply/remove endpoints + the three
164
+ decoration fields (`gift_cards[]`, `gift_card_total`,
165
+ `gift_card_remainder`) your summary UI must render.
166
+
167
+ ## Step 8 Checkout
168
+
169
+ [checkout.md](checkout.md) is the authoritative sequence. In brief:
170
+
171
+ 1. List shipping options and payment providers **with `cart_id`** — the
172
+ server filters both through the merchant's checkout rules; your UI never
173
+ hides methods on its own.
174
+ 2. Carrier pickers (office/locker) come from the integrations config
175
+ ([integrations.md](integrations.md)); the chosen point goes into
176
+ `carrier_metadata`.
177
+ 3. Buy click = `prepare-checkout` (ONE call: address + shipping method +
178
+ payment session at the final amount) → for card: Stripe
179
+ `confirmPayment(client_secret)` → `complete`. For COD: `complete`
180
+ directly. Gift-card-covered carts skip the provider entirely.
181
+ 4. Handle the documented failure codes (`checkout_method_hidden`,
182
+ `account_required`, `gift_card_insufficient_balance`, cart-vs-order
183
+ union on complete)each has a UI recovery path described in the doc.
184
+ 5. `sync-payment-amount` after any total-changing edit on the payment step;
185
+ `refresh-payment-if-terminal` only from Elements `loaderror` / aged-cart
186
+ mount.
187
+ 6. Order confirmation renders from [orders.md](orders.md)
188
+ (`/orders/display/:displayId` embeds items, fulfillments, tracking).
189
+
190
+ ## Step 9 Customer accounts
191
+
192
+ [auth.md](auth.md): passwordless email-code login (request → verify →
193
+ Bearer session). [customers.md](customers.md): profile, addresses,
194
+ order history, issued documents (invoices). Respect `accounts_mode`
195
+ (store setting): `required` blocks guest checkout with `403
196
+ account_required`; `disabled` means render no account UI at all. Gate B2B
197
+ content on `customer.account_status === "approved"`.
198
+
199
+ ## Step 10Reviews
200
+
201
+ [reviews.md](reviews.md): the PDP widget (`/reviews/widget` aggregate +
202
+ first page + display options in one call) and the token wizard route for
203
+ email CTA links (`/review/<token>`): validate token submit rating+body
204
+ optional photo step mints the reward code. Resume rules are in the doc.
205
+
206
+ ## Step 11 — Tracking events
207
+
208
+ [components.md](components.md) tracking section: fire client events via the
209
+ package helpers; **Purchase MUST use `eventID = "purchase_" +
210
+ order.display_id`** so Meta dedupes browser Pixel against the server CAPI
211
+ event; write the attribution keys (fbp/fbc/anon-id/ga session) into
212
+ `cart.metadata` (consent-gated) so server events inherit them.
213
+
214
+ ## Step 12 Go-live checklist
215
+
216
+ - [ ] Pricing context passed on every catalog surface; no shared caching of
217
+ JWT-priced responses.
218
+ - [ ] Consent banner renders for an unconfigured store (defaults are
219
+ server-applied); tags mount only behind consent.
220
+ - [ ] Checkout completes: card (Stripe live), COD (fee renders from config),
221
+ gift card (partial + full cover).
222
+ - [ ] Not-found handler consults redirects; policy pages linked.
223
+ - [ ] Order confirmation + emails render totals identical to the cart.
224
+ - [ ] Locale switching keeps cart + session; EUR everywhere.
225
+ - [ ] The store's publishable key (if any) is set — B2B catalogs are
226
+ key-scoped and look "missing products" without it.
227
+
228
+ ## Step 13 — Ship it
229
+
230
+ [deploy.md](deploy.md): send the app to Cartbase hosting — permanent
231
+ preview URL on every deploy, **Publish** to go live, deploy history as
232
+ rollback. Hosted storefronts get the three inputs above injected
233
+ automatically, so there is nothing to configure.
@@ -58,6 +58,7 @@ One file per domain. For each endpoint, in order:
58
58
  | [search.md](search.md) | Search, facets, related products |
59
59
  | [collections.md](collections.md) | Collections + membership listings |
60
60
  | [categories.md](categories.md) | Categories, tags, types |
61
+ | [store.md](store.md) | The store's identity: name, slug, brand |
61
62
  | [regions.md](regions.md) | Regions, currencies, locales |
62
63
  | [carts.md](carts.md) | Cart lifecycle + line items |
63
64
  | [gift-cards.md](gift-cards.md) | Gift-card tender on carts |
@@ -217,7 +217,7 @@ pure UI over the theme tokens.
217
217
  ---
218
218
 
219
219
  ## Family: lib (`@cartbase/storefront/lib/*`)
220
- Pure helpers — no React except `dual-price`, no fetches. The SDK never
220
+ Pure helpers — no React except `price`, no fetches. The SDK never
221
221
  invents server truths: prices/totals arrive computed from the API
222
222
  (`variant.calculated_price`, `cart.total`); these helpers only select and
223
223
  format.
@@ -226,10 +226,11 @@ format.
226
226
  - `lib/money` — `convertToLocale({amount, currency_code, …})` Intl currency
227
227
  formatting (amounts are decimal EUR major units per the store contract);
228
228
  `noDivisionCurrencies`.
229
- - `lib/dual-price` — `<DualPrice amount currencyCode />`: EUR price with the
230
- statutory BGN dual display (1 EUR = 1.95583 лв., Bulgarian dual-display
231
- law through 2026-08); non-EUR currencies render single. Also exports
232
- `EUR_TO_BGN_RATE`.
229
+ - `lib/price` — `<Price amount currencyCode className />`: the amount in its
230
+ own currency, formatted through `convertToLocale`. Every price in the
231
+ library renders through it. It shows ONE currency; a store that wants a
232
+ second one displayed adds it in its own locale layer rather than in
233
+ every price.
233
234
  - `lib/cart-helpers` — `isProductLine` / `isFeeLine` / `productTotal` /
234
235
  `productItemCount` / `findFeeLine` + `COD_FEE_METADATA_KEY`. THE single
235
236
  source of truth for hiding the backend-injected COD-fee line in cart
@@ -412,7 +413,7 @@ code-first through the error-copy maps, never raw API strings.
412
413
  `fields.billingDetails.address` override — the strict-completeness
413
414
  IntegrationError fix). `PaymentButton` = the Buy button: re-entry-guarded
414
415
  click → `performBuyClick`, cycling processing narration, translated
415
- inline errors, DualPrice total.
416
+ inline errors, Price total.
416
417
  - **SDK calls** — renders `checkout.listPaymentProviders` results via the
417
418
  hook's `hasCard`/`hasCod`; the click path runs the hook's calls.
418
419
  - **Props contract** — hook state + `buyButtonNotReady(Reason?)`,
@@ -964,7 +965,7 @@ component also takes a `labels` prop pick.
964
965
  ### `<OrderTotals totals currencyCode items? methodFeeLabel? />` — `order/order-totals`
965
966
 
966
967
  - **Purpose** — the money breakdown: Subtotal / Shipping (FREE badge at
967
- 0) / COD fee / Discount (negated) / Tax / Total, all via `DualPrice`.
968
+ 0) / COD fee / Discount (negated) / Tax / Total, all via `Price`.
968
969
  - **Data seam** — `OrderTotalsSource` (the decorated cart satisfies it:
969
970
  `item_subtotal`, `shipping_subtotal`, `discount_total`, `tax_total`,
970
971
  `total`, `payment_method_fee_total`, `payment_method_fee_label`); the summary snapshot adapts
@@ -5,7 +5,10 @@ Cartbase builds and hosts your storefront. You send the app's source files
5
5
  running site: first on a **permanent preview URL**, then, when you press
6
6
  **Publish** (or call the publish endpoint), on the store's live domain.
7
7
  You never touch build servers, DNS, or hosting configuration; the platform
8
- provisions all of it on your first deploy.
8
+ provisions all of it the moment the store is created, and makes the first
9
+ deploy itself: the store's starting point, a complete storefront on your
10
+ own (still empty) catalog, already running on the preview link. Pull it
11
+ with the CLI, change anything, deploy it back.
9
12
 
10
13
  Two URLs exist per store, both created automatically:
11
14
 
@@ -56,24 +59,25 @@ Error codes you can hit: `empty_bundle`, `bundle_too_large`,
56
59
 
57
60
  ## Environment — provided, not configured
58
61
 
59
- Hosted storefronts receive the three runbook inputs automatically at
60
- build time; do **not** put them in the bundle (`.env*` files are blocked
62
+ Hosted storefronts receive their store's values automatically at build
63
+ time; do **not** put them in the bundle (`.env*` files are blocked
61
64
  anyway):
62
65
 
63
66
  | Variable | Value |
64
67
  |---|---|
65
- | `NEXT_PUBLIC_CARTBASE_URL` | The store's API origin |
66
- | `NEXT_PUBLIC_CARTBASE_CLIENT_ID` | The store's client id |
67
- | `NEXT_PUBLIC_CARTBASE_PUBLISHABLE_KEY` | The store's publishable key, when one exists |
68
+ | `NEXT_PUBLIC_CARTBASE_PUBLISHABLE_KEY` | The store's publishable key, the one input a storefront needs |
69
+ | `NEXT_PUBLIC_CARTBASE_URL` | The platform origin (a constant; injected so a hosted build never guesses) |
70
+ | `NEXT_PUBLIC_CARTBASE_CLIENT_ID` | The store's id, the platform's own door; a storefront never needs to copy it |
68
71
 
69
72
  Only these public values ever reach a storefront build — secret keys are
70
73
  never injected, so code that expects one is a design error.
71
74
 
72
75
  ## Deploy to preview
73
76
 
74
- `POST /api/admin/storefront/deploys` — the one ingestion door. The first
75
- call on a store also provisions its hosting (takes a few extra seconds);
76
- every later call is just a deploy.
77
+ `POST /api/admin/storefront/deploys` — the one ingestion door. Hosting is
78
+ provisioned when the store is born, so every call is just a deploy; on a
79
+ store whose hosting setup failed, the first call resumes it (a few extra
80
+ seconds).
77
81
 
78
82
  ```bash
79
83
  # doc-noexec — admin-session auth; run from an authenticated context.
@@ -153,7 +157,8 @@ curl -s "$BASE/api/admin/storefront"
153
157
  }
154
158
  ```
155
159
 
156
- `storefront` is `null` until the store's first deploy. `status:
160
+ `storefront` is present from the store's birth (`null` only on a store
161
+ created before hosting-at-birth that has never deployed). `status:
157
162
  "provisioning"` / `"failed"` (with `last_error`) describe hosting setup,
158
163
  not builds; a failed provisioning resumes automatically on the next
159
164
  deploy attempt.
@@ -88,7 +88,7 @@ window.Cartbase").
88
88
  ```tsx
89
89
  // app/layout.tsx, inside <body>, first children:
90
90
  <ConsentInit />
91
- <PlatformInit storeId={CARTBASE_CLIENT_ID} />
91
+ <PlatformInit storeId={store.id} /> // from GET /api/store/store (store.md)
92
92
  ```
93
93
 
94
94
  Also build-time/render-only — no store-API curl to demonstrate; the exact