create-cartbase 0.1.9 → 0.1.10

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": "create-cartbase",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "Scaffold a Cartbase storefront: npm create cartbase my-store",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -353,8 +353,13 @@ code-first through the error-copy maps, never raw API strings.
353
353
  - **Props contract** — `{client, cart, customer, availableShippingMethods,
354
354
  availablePaymentMethods, countryCode?, countries?, paymentMethodFilter?,
355
355
  orderConfirmedPath?, onOrderPlaced?,
356
- resolveTrackingMetadata?, logError?}`. `countries` is caller-supplied
357
- (Cartbase regions embed NO countries array). Fee prediction is never
356
+ resolveTrackingMetadata?, logError?}`. `countries` is OPTIONAL and is an
357
+ override: omit it and the hook calls `GET /api/store/countries` itself, so
358
+ the address form offers the countries the store's Markets declare, or the
359
+ whole catalogue when it declares none (0.11.0 — before that, omitting it
360
+ produced a ONE-country read-only field). A failure to load the list never
361
+ blocks a checkout: it falls back to `countryCode` and reports through
362
+ `logError` as `country_list_failed`. Fee prediction is never
358
363
  hardcoded: each method LISTING entry carries its own
359
364
  `fee_amount`/`fee_label`. `logError` replaces the legacy log writers
360
365
  (all production log points preserved). Returns the full orchestration
@@ -412,9 +417,16 @@ code-first through the error-copy maps, never raw API strings.
412
417
  `addressesInRegion`.
413
418
  - **Props contract** — everything from the hook (`formData`,
414
419
  `handleFormChange`, `handleFieldBlur`, `regionCountries`, `addressInput`,
415
- `addressError`, `pulseFields`); `hideCountry?` for single-country stores
416
- (single-entry lists render a readonly localized country field).
417
- - **Settings** regions/countries (via the caller-supplied list).
420
+ `addressError`, `pulseFields`); `hideCountry?` for single-country stores.
421
+ The country field is a NATIVE select carrying the chosen country's flag,
422
+ and it keeps `autocomplete="country"` on purpose: a custom listbox would
423
+ draw a flag per row but lose browser autofill and the phone's own picker,
424
+ and a checkout does not trade autofill for decoration. A list of exactly
425
+ one country still renders as a read-only name, which is right for a store
426
+ that truly sells to one country and is no longer the default for everyone
427
+ else (0.11.0).
428
+ - **Settings** — Admin → Settings → Markets → Regions, read through
429
+ `GET /api/store/countries`.
418
430
 
419
431
  ### `<CheckoutShippingMethodList />` — `checkout/shipping-method-list`
420
432
 
@@ -1092,12 +1104,27 @@ Shared storefront chrome, production-proven.
1092
1104
 
1093
1105
  ### `<CountrySelect regions value? onChange />` — `common/country-select`
1094
1106
 
1095
- - **Purpose** — region picker. SDK-forced divergence from the source:
1096
- Cartbase regions carry NO `countries[]` embed on the store surface, so
1097
- the select lists REGIONS (`api/regions.listRegions`), valued by region
1098
- id; persistence is app-owned via `onChange` (usually
1099
- `carts.updateCart(client, cartId, {region_id})` + a cookie).
1100
- - **Settings** Regions (Settings Regions): which rows exist.
1107
+ - **Purpose** — the market picker for a header or footer. It lists REGIONS
1108
+ (`api/regions.listRegions`), valued by region id; persistence is app-owned
1109
+ via `onChange` (usually `carts.updateCart(client, cartId, {region_id})` +
1110
+ a cookie). The name is a leftover from the ported source and is wrong for
1111
+ what it does: it selects a market, not a country. Use
1112
+ `listCountries` + `CountryFlag` when you want a country.
1113
+ - **Settings** — Admin → Settings → Markets → Regions: which rows exist.
1114
+
1115
+ ### `<CountryFlag code title? className? />` — `common/country-flag`
1116
+
1117
+ - **Purpose** — a country's flag from its ISO-3166 alpha-2 code, at the size
1118
+ of the text beside it. An unknown or empty code renders a neutral box that
1119
+ holds the same space, so a field does not jump when a shopper picks their
1120
+ country.
1121
+ - **Why a sprite and not an emoji** — Windows ships no flag glyphs, so an
1122
+ emoji flag reads as the two letters "BG" there. This draws `flag-icons`
1123
+ SVG under a CSS class, which renders the same on every platform, and it is
1124
+ the same sprite the Cartbase admin uses, so one country treatment runs
1125
+ across the admin and the storefront.
1126
+ - **Accessibility** — decorative and `aria-hidden`: a flag never carries
1127
+ meaning the name beside it does not. Pass `title` when it stands alone.
1101
1128
 
1102
1129
  ### `<LanguageSelect locales currentLocale onChange labels? />` — `common/language-select`
1103
1130
 
@@ -108,6 +108,69 @@ curl -s "$BASE/api/store/regions/reg_doesnotexist$RUN" \
108
108
 
109
109
  ---
110
110
 
111
+ ## GET /api/store/countries
112
+
113
+ - **Purpose** — the countries a shopper may choose at checkout.
114
+
115
+ **The rule is the platform's, not your storefront's.** The store's own
116
+ Markets decide where it sells, and a store that has decided nothing yet
117
+ sells to the whole world. So this endpoint answers with:
118
+
119
+ - the countries the store's regions declare, when it has declared any
120
+ (`"restricted": true`), or
121
+ - the whole 250-entry ISO catalogue, when it has declared none
122
+ (`"restricted": false`).
123
+
124
+ Either way your code hardcodes no country list and invents no fallback of
125
+ its own. A merchant changes the answer in Admin → Settings → Markets →
126
+ Regions, where a region owns its countries.
127
+
128
+ ```jsonc
129
+ // query (all optional)
130
+ {
131
+ "q": "bulg", // case-insensitive substring on the displayed name
132
+ "limit": 300, // 1–300, default 300 — the whole catalogue in one call
133
+ "offset": 0
134
+ }
135
+ ```
136
+
137
+ - **Response** — ordered by `display_name`, the name a shopper reads.
138
+
139
+ ```jsonc
140
+ {
141
+ "countries": [
142
+ {
143
+ "iso_2": "bg", // lowercase alpha-2: the cart address value
144
+ "display_name": "Bulgaria", // what a shopper reads
145
+ "name": "BULGARIA", // the catalogue's uppercase form
146
+ "region_id": "reg_01..." // null when the store has not placed it
147
+ }
148
+ ],
149
+ "count": 250,
150
+ "offset": 0,
151
+ "limit": 300,
152
+ "restricted": false
153
+ }
154
+ ```
155
+
156
+ ```bash
157
+ # every country the store offers, and which of the two lists you received
158
+ curl -s "$BASE/api/store/countries" -H "x-client-id: $CLIENT_ID" \
159
+ | grep -q '"restricted"'
160
+ ```
161
+
162
+ - **SDK** — `listCountries(client, query?)` from
163
+ `@cartbase/storefront/api/regions`.
164
+ - **Components** — the checkout address form's country select.
165
+ `useCheckoutOrchestration` CALLS THIS FOR YOU when you do not pass a
166
+ `countries` list, so a new store offers every country with no wiring;
167
+ pass your own list only to override the store's answer. The chosen
168
+ country's flag is drawn by `CountryFlag`
169
+ (`@cartbase/storefront/common/country-flag`).
170
+ - **Settings** — Admin → Settings → Markets → Regions.
171
+
172
+ ---
173
+
111
174
  ## GET /api/store/currencies
112
175
 
113
176
  - **Purpose** — list the currencies ENABLED on this store (the shared
@@ -9,7 +9,7 @@
9
9
  "typecheck": "tsc --noEmit"
10
10
  },
11
11
  "dependencies": {
12
- "@cartbase/storefront": "^0.10.0",
12
+ "@cartbase/storefront": "^0.11.0",
13
13
  "next": "16.2.4",
14
14
  "react": "19.2.4",
15
15
  "react-dom": "19.2.4"
@@ -32,8 +32,19 @@ export function CheckoutPageClient({
32
32
  customer={null}
33
33
  availableShippingMethods={shippingOptions}
34
34
  availablePaymentMethods={paymentProviders}
35
- countryCode="bg"
36
- countries={[{ iso_2: "bg", display_name: "Bulgaria" }]}
35
+ // NO COUNTRY LIST HERE, on purpose (2026-09-13). This file is the
36
+ // seed for every scaffolded store, and it used to pass
37
+ // `countries={[{iso_2:"bg", display_name:"Bulgaria"}]}` with
38
+ // `countryCode="bg"`, so every store on earth shipped a checkout
39
+ // offering one country, Bulgaria, in a box the shopper could not
40
+ // change. The list belongs to the store, not to this file: omit the
41
+ // prop and the hook reads GET /api/store/countries, which answers
42
+ // with the countries the store's Markets declare, or with the whole
43
+ // world when it has declared none.
44
+ //
45
+ // A single-market store adds `countryCode="xx"` here to preselect
46
+ // its country, and a store that sells to exactly one country gets
47
+ // the read-only field automatically, because its Markets say so.
37
48
  // Per-store rule (the documented paymentMethodFilter seam): this
38
49
  // reference store checks out offline via its merchant methods only
39
50
  // (pp_* kill: method entries carry payment_method_id, processors