@cartbase/storefront 0.17.0 → 0.18.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.
Files changed (107) hide show
  1. package/LICENSE +21 -21
  2. package/package.json +258 -256
  3. package/src/api/auth.ts +108 -108
  4. package/src/api/carts.ts +523 -523
  5. package/src/api/categories.ts +184 -184
  6. package/src/api/checkout.ts +526 -526
  7. package/src/api/collections.ts +130 -130
  8. package/src/api/consent.ts +75 -75
  9. package/src/api/content.ts +125 -125
  10. package/src/api/customers.ts +303 -303
  11. package/src/api/gift-cards.ts +112 -112
  12. package/src/api/http.ts +180 -180
  13. package/src/api/index.ts +30 -30
  14. package/src/api/menus.ts +77 -77
  15. package/src/api/metaobjects.ts +136 -136
  16. package/src/api/orders.ts +290 -290
  17. package/src/api/products.ts +43 -1
  18. package/src/api/redirects.ts +37 -37
  19. package/src/api/regions.ts +200 -200
  20. package/src/api/reviews.ts +259 -259
  21. package/src/api/search.ts +163 -163
  22. package/src/api/store.ts +35 -35
  23. package/src/api/types.ts +91 -91
  24. package/src/cart-drawer/context.tsx +277 -164
  25. package/src/cart-drawer/cross-sell-carousel.tsx +211 -211
  26. package/src/cart-drawer/cross-sell-sidebar.tsx +158 -158
  27. package/src/cart-drawer/gift-wrap.tsx +82 -82
  28. package/src/cart-drawer/index.ts +71 -68
  29. package/src/cart-drawer/item/index.tsx +162 -162
  30. package/src/cart-drawer/item/quantity.tsx +8 -14
  31. package/src/cart-drawer/item/upsell.tsx +110 -110
  32. package/src/cart-drawer/labels.ts +123 -123
  33. package/src/cart-drawer/mutation-queue.ts +78 -0
  34. package/src/cart-drawer/notes.tsx +131 -131
  35. package/src/cart-drawer/sticky-footer.tsx +73 -73
  36. package/src/cart-drawer/summary-breakdown.tsx +197 -197
  37. package/src/checkout/address-error-copy.ts +117 -117
  38. package/src/checkout/boxnow-locker-selector.tsx +410 -410
  39. package/src/checkout/compare-addresses.ts +40 -40
  40. package/src/checkout/discount-section.tsx +218 -218
  41. package/src/checkout/error-copy-codes.ts +63 -63
  42. package/src/checkout/geocode.ts +154 -154
  43. package/src/checkout/gift-card-section.tsx +224 -224
  44. package/src/checkout/index.ts +72 -72
  45. package/src/checkout/labels.ts +495 -495
  46. package/src/checkout/payment-button.tsx +372 -372
  47. package/src/checkout/payment-error-copy.ts +154 -154
  48. package/src/checkout/promotion-error-copy.ts +91 -91
  49. package/src/checkout/use-checkout-orchestration.ts +1592 -1592
  50. package/src/common/country-flag.tsx +52 -52
  51. package/src/common/country-select.tsx +11 -11
  52. package/src/common/index.ts +20 -20
  53. package/src/common/market-select.tsx +57 -57
  54. package/src/index.ts +12 -12
  55. package/src/lib/country-name.ts +59 -59
  56. package/src/lib/get-product-price.ts +133 -133
  57. package/src/lib/media-image.tsx +39 -39
  58. package/src/lib/payment-constants.ts +53 -53
  59. package/src/lib/platform.ts +13 -13
  60. package/src/lib/price.tsx +39 -39
  61. package/src/lib/store-api-error.ts +36 -36
  62. package/src/lib/variant-caption.ts +32 -32
  63. package/src/locales/bg.ts +467 -467
  64. package/src/locales/context.ts +37 -37
  65. package/src/locales/en.ts +26 -26
  66. package/src/locales/es.ts +466 -466
  67. package/src/locales/index.ts +19 -19
  68. package/src/locales/provider.tsx +59 -59
  69. package/src/locales/types.ts +77 -77
  70. package/src/order/index.ts +62 -62
  71. package/src/order/labels.ts +79 -79
  72. package/src/order/order-totals.tsx +250 -250
  73. package/src/primitives/select-field.tsx +93 -93
  74. package/src/products/image-gallery.tsx +43 -43
  75. package/src/products/index.ts +68 -59
  76. package/src/products/option-select.tsx +67 -67
  77. package/src/products/product-actions-wrapper.tsx +63 -62
  78. package/src/products/product-actions.tsx +151 -150
  79. package/src/products/product-price.tsx +69 -69
  80. package/src/products/product-promises.tsx +61 -61
  81. package/src/products/product-specs.tsx +65 -65
  82. package/src/products/product-tabs.tsx +123 -123
  83. package/src/products/product-template.tsx +163 -162
  84. package/src/products/purchase-options.tsx +130 -130
  85. package/src/products/sets.ts +110 -0
  86. package/src/products/use-product-actions.ts +199 -164
  87. package/src/products/variant-matching.ts +71 -71
  88. package/src/products/variant-url.ts +74 -74
  89. package/src/reviews-ui/index.ts +73 -73
  90. package/src/store/category-template.tsx +136 -136
  91. package/src/store/index.ts +40 -40
  92. package/src/tracking/chatgpt-pixel.tsx +99 -99
  93. package/src/tracking/consent-init.tsx +62 -62
  94. package/src/tracking/events.ts +348 -348
  95. package/src/tracking/ga4.tsx +93 -93
  96. package/src/tracking/google-ads.ts +84 -84
  97. package/src/tracking/gtm.tsx +60 -60
  98. package/src/tracking/inline-script.ts +49 -49
  99. package/src/tracking/oaiq.ts +206 -206
  100. package/src/tracking/tiktok-pixel.tsx +91 -91
  101. package/src/tracking/track-init.tsx +56 -56
  102. package/src/tracking/track-order-purchase.tsx +122 -122
  103. package/src/tracking/ttq.ts +180 -180
  104. package/src/tracking/use-tracking-config.ts +54 -54
  105. package/theme/index.css +25 -25
  106. package/theme/theme.css +150 -150
  107. package/theme/tokens.css +106 -106
@@ -1,37 +1,37 @@
1
- /**
2
- * @cartbase/storefront/api/redirects — 404-path URL redirect lookup
3
- * (seo-listings card 21).
4
- *
5
- * Ground truth: src/app/api/store/url-redirects/route.ts +
6
- * src/lib/content/url-redirects.ts.
7
- *
8
- * HOT-PATH RULE: call this ONLY from the storefront's not-found handler
9
- * (the 404 path) — never on regular page loads. Exact-match lookup,
10
- * index-covered on (client_id, from_path). When `to_path` is non-null the
11
- * storefront issues a 301 to it.
12
- */
13
-
14
- import type { StorefrontClient } from "./http"
15
-
16
- export interface RedirectLookupResponse {
17
- /** The path you asked about (echoed). */
18
- path: string
19
- /** Redirect target, or null when no redirect exists — render the 404. */
20
- to_path: string | null
21
- }
22
-
23
- /**
24
- * GET /api/store/url-redirects?path=… — look up a redirect for a missed
25
- * pathname (e.g. `/products/old-handle`).
26
- *
27
- * Auth: anon (x-client-id).
28
- * Errors: 400 validation_failed (missing/empty path) · 400 missing_client_id.
29
- * Settings: redirects are created in admin (and automatically on handle
30
- * renames of products/collections/pages/posts).
31
- */
32
- export async function lookupRedirect(
33
- client: StorefrontClient,
34
- path: string
35
- ): Promise<RedirectLookupResponse> {
36
- return client.get("/api/store/url-redirects", { query: { path } })
37
- }
1
+ /**
2
+ * @cartbase/storefront/api/redirects — 404-path URL redirect lookup
3
+ * (seo-listings card 21).
4
+ *
5
+ * Ground truth: src/app/api/store/url-redirects/route.ts +
6
+ * src/lib/content/url-redirects.ts.
7
+ *
8
+ * HOT-PATH RULE: call this ONLY from the storefront's not-found handler
9
+ * (the 404 path) — never on regular page loads. Exact-match lookup,
10
+ * index-covered on (client_id, from_path). When `to_path` is non-null the
11
+ * storefront issues a 301 to it.
12
+ */
13
+
14
+ import type { StorefrontClient } from "./http"
15
+
16
+ export interface RedirectLookupResponse {
17
+ /** The path you asked about (echoed). */
18
+ path: string
19
+ /** Redirect target, or null when no redirect exists — render the 404. */
20
+ to_path: string | null
21
+ }
22
+
23
+ /**
24
+ * GET /api/store/url-redirects?path=… — look up a redirect for a missed
25
+ * pathname (e.g. `/products/old-handle`).
26
+ *
27
+ * Auth: anon (x-client-id).
28
+ * Errors: 400 validation_failed (missing/empty path) · 400 missing_client_id.
29
+ * Settings: redirects are created in admin (and automatically on handle
30
+ * renames of products/collections/pages/posts).
31
+ */
32
+ export async function lookupRedirect(
33
+ client: StorefrontClient,
34
+ path: string
35
+ ): Promise<RedirectLookupResponse> {
36
+ return client.get("/api/store/url-redirects", { query: { path } })
37
+ }
@@ -1,200 +1,200 @@
1
- /**
2
- * @cartbase/storefront/api/regions — regions, countries, currencies, locales.
3
- *
4
- * Catalog-context primitives every storefront resolves at boot: which
5
- * regions exist (→ `region_id` for the pricing context), which countries the
6
- * store sells to, which currencies it has enabled, which locales it serves.
7
- * All endpoints are anon reads (`x-client-id` scoping via RLS). Docs:
8
- * docs/storefront/regions.md.
9
- */
10
- import type { StorefrontClient } from "./http"
11
- import type { IsoDateString, ListEnvelope, PaginationQuery } from "./types"
12
-
13
- /**
14
- * A store region (`barter_commerce.regions` row). NOTE: Cartbase regions carry
15
- * NO embedded `countries` array on the store surface (divergence from
16
- * Medusa's Store API — tax scoping is by `tax_regions` server-side). The
17
- * countries are their own call, `listCountries`, and each row carries the
18
- * `region_id` it belongs to.
19
- */
20
- export interface StoreRegion {
21
- id: string
22
- name: string
23
- /** Lowercase ISO currency, e.g. "eur" — feeds the pricing context. */
24
- currency_code: string
25
- automatic_taxes: boolean
26
- metadata: Record<string, unknown> | null
27
- created_at: IsoDateString
28
- updated_at: IsoDateString
29
- }
30
-
31
- /** Shared-catalog currency row, scoped to the store's enabled set on list. */
32
- export interface StoreCurrency {
33
- /** Lowercase ISO code, e.g. "eur". */
34
- code: string
35
- name: string
36
- symbol: string
37
- symbol_native: string | null
38
- decimal_digits: number
39
- rounding: number
40
- created_at: IsoDateString
41
- updated_at: IsoDateString
42
- }
43
-
44
- /**
45
- * A country a shopper may choose (`barter_commerce.countries` row, the store's
46
- * own tenant catalogue).
47
- */
48
- export interface StoreCountry {
49
- /** Lowercase ISO-3166 alpha-2, e.g. "bg" — the value a cart address takes. */
50
- iso_2: string
51
- /** What a shopper reads, e.g. "Bulgaria". The code is never shown as copy. */
52
- display_name: string
53
- /** The catalogue's uppercase form, e.g. "BULGARIA". */
54
- name: string
55
- /** The region this country belongs to, null when the store has not placed it. */
56
- region_id: string | null
57
- }
58
-
59
- export interface ListCountriesQuery extends PaginationQuery {
60
- /** Case-insensitive substring match on the displayed name. */
61
- q?: string
62
- }
63
-
64
- export interface CountryListResponse extends ListEnvelope {
65
- countries: StoreCountry[]
66
- /**
67
- * True when this list IS the store's Markets declaration; false when the
68
- * store has declared nothing and this is the whole ISO catalogue.
69
- */
70
- restricted: boolean
71
- }
72
-
73
- export interface ListRegionsQuery extends PaginationQuery {
74
- /** Case-insensitive substring match on region name. */
75
- q?: string
76
- /** Exact match (lowercased server-side). */
77
- currency_code?: string
78
- }
79
-
80
- export interface RegionListResponse extends ListEnvelope {
81
- regions: StoreRegion[]
82
- }
83
-
84
- export interface RegionResponse {
85
- region: StoreRegion
86
- }
87
-
88
- export interface ListCurrenciesQuery extends PaginationQuery {
89
- /** Exact code match (lowercased server-side). */
90
- code?: string
91
- }
92
-
93
- export interface CurrencyListResponse extends ListEnvelope {
94
- currencies: StoreCurrency[]
95
- }
96
-
97
- export interface CurrencyResponse {
98
- currency: StoreCurrency
99
- }
100
-
101
- export interface LocaleListResponse {
102
- /** Locale codes, store default first, then alphabetical (e.g. ["en","bg"]). */
103
- locales: string[]
104
- }
105
-
106
- /**
107
- * List the store's regions, ordered by name.
108
- *
109
- * Auth: anon (`x-client-id`). Errors: 400 `missing_client_id`,
110
- * 400 `validation_failed`. Settings: regions are created in
111
- * Admin → Settings → Regions; `limit` 1–200 (default 50).
112
- */
113
- export async function listRegions(
114
- client: StorefrontClient,
115
- query?: ListRegionsQuery
116
- ): Promise<RegionListResponse> {
117
- return client.get("/api/store/regions", { query: { ...query } })
118
- }
119
-
120
- /**
121
- * Retrieve one region by id.
122
- *
123
- * Auth: anon (`x-client-id`). Errors: 404 `not_found` (unknown id,
124
- * soft-deleted, or another tenant's region — invisible, not forbidden).
125
- */
126
- export async function retrieveRegion(
127
- client: StorefrontClient,
128
- regionId: string
129
- ): Promise<RegionResponse> {
130
- return client.get(`/api/store/regions/${encodeURIComponent(regionId)}`)
131
- }
132
-
133
- /**
134
- * List the countries a shopper may choose at checkout, ordered by the name
135
- * they read.
136
- *
137
- * THE RULE IS THE PLATFORM'S, NOT THE STOREFRONT'S: the store's own Markets
138
- * decide where it sells, and a store that has decided nothing yet sells to
139
- * the whole world. So this answers with the countries the store's regions
140
- * declare, and with the whole 250-entry catalogue when it declares none;
141
- * `restricted` says which you got. A storefront therefore never hardcodes a
142
- * country list, and never has to invent a fallback of its own.
143
- *
144
- * `useCheckoutOrchestration` calls this for you when you do not pass it a
145
- * `countries` list, so a new store offers every country with no wiring at
146
- * all. Pass your own list only to override the store's answer.
147
- *
148
- * Auth: anon (`x-client-id`). Errors: 400 `missing_client_id`,
149
- * 400 `validation_failed`. Settings: Admin → Settings → Markets → Regions,
150
- * where a region owns its countries; `limit` 1–300 (default 300, so one
151
- * call carries the entire catalogue).
152
- */
153
- export async function listCountries(
154
- client: StorefrontClient,
155
- query?: ListCountriesQuery
156
- ): Promise<CountryListResponse> {
157
- return client.get("/api/store/countries", { query: { ...query } })
158
- }
159
-
160
- /**
161
- * List the currencies ENABLED on this store (the `store_currencies` link
162
- * filters the shared currency catalog). A store with none enabled returns an
163
- * empty list, ordered by code.
164
- *
165
- * Auth: anon (`x-client-id`). Errors: 400 `missing_client_id`,
166
- * 400 `validation_failed`. Settings: Admin → Settings → Store → currencies.
167
- */
168
- export async function listCurrencies(
169
- client: StorefrontClient,
170
- query?: ListCurrenciesQuery
171
- ): Promise<CurrencyListResponse> {
172
- return client.get("/api/store/currencies", { query: { ...query } })
173
- }
174
-
175
- /**
176
- * Retrieve one currency by code (case-insensitive).
177
- *
178
- * Auth: anon (`x-client-id`). Errors: 404 `not_found`.
179
- *
180
- * CODE-TRUTH NOTE: unlike the list, the single read is NOT filtered by the
181
- * store's enabled set — any currency in the shared catalog resolves. Treat
182
- * the list as the authority on what the store supports.
183
- */
184
- export async function retrieveCurrency(
185
- client: StorefrontClient,
186
- code: string
187
- ): Promise<CurrencyResponse> {
188
- return client.get(`/api/store/currencies/${encodeURIComponent(code)}`)
189
- }
190
-
191
- /**
192
- * List the store's supported locale codes (default locale first). Use to
193
- * build the storefront language switcher and to set the client's `x-locale`.
194
- *
195
- * Auth: anon (`x-client-id`). Errors: 400 `missing_client_id`.
196
- * Settings: Admin → Settings → Store → locales (per-store store_locales).
197
- */
198
- export async function listLocales(client: StorefrontClient): Promise<LocaleListResponse> {
199
- return client.get("/api/store/locales")
200
- }
1
+ /**
2
+ * @cartbase/storefront/api/regions — regions, countries, currencies, locales.
3
+ *
4
+ * Catalog-context primitives every storefront resolves at boot: which
5
+ * regions exist (→ `region_id` for the pricing context), which countries the
6
+ * store sells to, which currencies it has enabled, which locales it serves.
7
+ * All endpoints are anon reads (`x-client-id` scoping via RLS). Docs:
8
+ * docs/storefront/regions.md.
9
+ */
10
+ import type { StorefrontClient } from "./http"
11
+ import type { IsoDateString, ListEnvelope, PaginationQuery } from "./types"
12
+
13
+ /**
14
+ * A store region (`barter_commerce.regions` row). NOTE: Cartbase regions carry
15
+ * NO embedded `countries` array on the store surface (divergence from
16
+ * Medusa's Store API — tax scoping is by `tax_regions` server-side). The
17
+ * countries are their own call, `listCountries`, and each row carries the
18
+ * `region_id` it belongs to.
19
+ */
20
+ export interface StoreRegion {
21
+ id: string
22
+ name: string
23
+ /** Lowercase ISO currency, e.g. "eur" — feeds the pricing context. */
24
+ currency_code: string
25
+ automatic_taxes: boolean
26
+ metadata: Record<string, unknown> | null
27
+ created_at: IsoDateString
28
+ updated_at: IsoDateString
29
+ }
30
+
31
+ /** Shared-catalog currency row, scoped to the store's enabled set on list. */
32
+ export interface StoreCurrency {
33
+ /** Lowercase ISO code, e.g. "eur". */
34
+ code: string
35
+ name: string
36
+ symbol: string
37
+ symbol_native: string | null
38
+ decimal_digits: number
39
+ rounding: number
40
+ created_at: IsoDateString
41
+ updated_at: IsoDateString
42
+ }
43
+
44
+ /**
45
+ * A country a shopper may choose (`barter_commerce.countries` row, the store's
46
+ * own tenant catalogue).
47
+ */
48
+ export interface StoreCountry {
49
+ /** Lowercase ISO-3166 alpha-2, e.g. "bg" — the value a cart address takes. */
50
+ iso_2: string
51
+ /** What a shopper reads, e.g. "Bulgaria". The code is never shown as copy. */
52
+ display_name: string
53
+ /** The catalogue's uppercase form, e.g. "BULGARIA". */
54
+ name: string
55
+ /** The region this country belongs to, null when the store has not placed it. */
56
+ region_id: string | null
57
+ }
58
+
59
+ export interface ListCountriesQuery extends PaginationQuery {
60
+ /** Case-insensitive substring match on the displayed name. */
61
+ q?: string
62
+ }
63
+
64
+ export interface CountryListResponse extends ListEnvelope {
65
+ countries: StoreCountry[]
66
+ /**
67
+ * True when this list IS the store's Markets declaration; false when the
68
+ * store has declared nothing and this is the whole ISO catalogue.
69
+ */
70
+ restricted: boolean
71
+ }
72
+
73
+ export interface ListRegionsQuery extends PaginationQuery {
74
+ /** Case-insensitive substring match on region name. */
75
+ q?: string
76
+ /** Exact match (lowercased server-side). */
77
+ currency_code?: string
78
+ }
79
+
80
+ export interface RegionListResponse extends ListEnvelope {
81
+ regions: StoreRegion[]
82
+ }
83
+
84
+ export interface RegionResponse {
85
+ region: StoreRegion
86
+ }
87
+
88
+ export interface ListCurrenciesQuery extends PaginationQuery {
89
+ /** Exact code match (lowercased server-side). */
90
+ code?: string
91
+ }
92
+
93
+ export interface CurrencyListResponse extends ListEnvelope {
94
+ currencies: StoreCurrency[]
95
+ }
96
+
97
+ export interface CurrencyResponse {
98
+ currency: StoreCurrency
99
+ }
100
+
101
+ export interface LocaleListResponse {
102
+ /** Locale codes, store default first, then alphabetical (e.g. ["en","bg"]). */
103
+ locales: string[]
104
+ }
105
+
106
+ /**
107
+ * List the store's regions, ordered by name.
108
+ *
109
+ * Auth: anon (`x-client-id`). Errors: 400 `missing_client_id`,
110
+ * 400 `validation_failed`. Settings: regions are created in
111
+ * Admin → Settings → Regions; `limit` 1–200 (default 50).
112
+ */
113
+ export async function listRegions(
114
+ client: StorefrontClient,
115
+ query?: ListRegionsQuery
116
+ ): Promise<RegionListResponse> {
117
+ return client.get("/api/store/regions", { query: { ...query } })
118
+ }
119
+
120
+ /**
121
+ * Retrieve one region by id.
122
+ *
123
+ * Auth: anon (`x-client-id`). Errors: 404 `not_found` (unknown id,
124
+ * soft-deleted, or another tenant's region — invisible, not forbidden).
125
+ */
126
+ export async function retrieveRegion(
127
+ client: StorefrontClient,
128
+ regionId: string
129
+ ): Promise<RegionResponse> {
130
+ return client.get(`/api/store/regions/${encodeURIComponent(regionId)}`)
131
+ }
132
+
133
+ /**
134
+ * List the countries a shopper may choose at checkout, ordered by the name
135
+ * they read.
136
+ *
137
+ * THE RULE IS THE PLATFORM'S, NOT THE STOREFRONT'S: the store's own Markets
138
+ * decide where it sells, and a store that has decided nothing yet sells to
139
+ * the whole world. So this answers with the countries the store's regions
140
+ * declare, and with the whole 250-entry catalogue when it declares none;
141
+ * `restricted` says which you got. A storefront therefore never hardcodes a
142
+ * country list, and never has to invent a fallback of its own.
143
+ *
144
+ * `useCheckoutOrchestration` calls this for you when you do not pass it a
145
+ * `countries` list, so a new store offers every country with no wiring at
146
+ * all. Pass your own list only to override the store's answer.
147
+ *
148
+ * Auth: anon (`x-client-id`). Errors: 400 `missing_client_id`,
149
+ * 400 `validation_failed`. Settings: Admin → Settings → Markets → Regions,
150
+ * where a region owns its countries; `limit` 1–300 (default 300, so one
151
+ * call carries the entire catalogue).
152
+ */
153
+ export async function listCountries(
154
+ client: StorefrontClient,
155
+ query?: ListCountriesQuery
156
+ ): Promise<CountryListResponse> {
157
+ return client.get("/api/store/countries", { query: { ...query } })
158
+ }
159
+
160
+ /**
161
+ * List the currencies ENABLED on this store (the `store_currencies` link
162
+ * filters the shared currency catalog). A store with none enabled returns an
163
+ * empty list, ordered by code.
164
+ *
165
+ * Auth: anon (`x-client-id`). Errors: 400 `missing_client_id`,
166
+ * 400 `validation_failed`. Settings: Admin → Settings → Store → currencies.
167
+ */
168
+ export async function listCurrencies(
169
+ client: StorefrontClient,
170
+ query?: ListCurrenciesQuery
171
+ ): Promise<CurrencyListResponse> {
172
+ return client.get("/api/store/currencies", { query: { ...query } })
173
+ }
174
+
175
+ /**
176
+ * Retrieve one currency by code (case-insensitive).
177
+ *
178
+ * Auth: anon (`x-client-id`). Errors: 404 `not_found`.
179
+ *
180
+ * CODE-TRUTH NOTE: unlike the list, the single read is NOT filtered by the
181
+ * store's enabled set — any currency in the shared catalog resolves. Treat
182
+ * the list as the authority on what the store supports.
183
+ */
184
+ export async function retrieveCurrency(
185
+ client: StorefrontClient,
186
+ code: string
187
+ ): Promise<CurrencyResponse> {
188
+ return client.get(`/api/store/currencies/${encodeURIComponent(code)}`)
189
+ }
190
+
191
+ /**
192
+ * List the store's supported locale codes (default locale first). Use to
193
+ * build the storefront language switcher and to set the client's `x-locale`.
194
+ *
195
+ * Auth: anon (`x-client-id`). Errors: 400 `missing_client_id`.
196
+ * Settings: Admin → Settings → Store → locales (per-store store_locales).
197
+ */
198
+ export async function listLocales(client: StorefrontClient): Promise<LocaleListResponse> {
199
+ return client.get("/api/store/locales")
200
+ }