@cartbase/storefront 0.1.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/package.json +226 -0
- package/src/api/auth.ts +108 -0
- package/src/api/carts.ts +506 -0
- package/src/api/categories.ts +184 -0
- package/src/api/checkout.ts +440 -0
- package/src/api/collections.ts +130 -0
- package/src/api/consent.ts +75 -0
- package/src/api/content.ts +125 -0
- package/src/api/customers.ts +307 -0
- package/src/api/gift-cards.ts +112 -0
- package/src/api/http.ts +122 -0
- package/src/api/index.ts +29 -0
- package/src/api/integrations.ts +130 -0
- package/src/api/menus.ts +77 -0
- package/src/api/metaobjects.ts +136 -0
- package/src/api/orders.ts +290 -0
- package/src/api/products.ts +303 -0
- package/src/api/redirects.ts +37 -0
- package/src/api/regions.ts +141 -0
- package/src/api/reviews.ts +259 -0
- package/src/api/search.ts +133 -0
- package/src/api/types.ts +91 -0
- package/src/cart-drawer/cart-drawer.tsx +86 -0
- package/src/cart-drawer/context.tsx +569 -0
- package/src/cart-drawer/continue-shopping.tsx +28 -0
- package/src/cart-drawer/cross-sell-carousel.tsx +212 -0
- package/src/cart-drawer/cross-sell-sidebar.tsx +158 -0
- package/src/cart-drawer/empty.tsx +52 -0
- package/src/cart-drawer/free-gift.tsx +71 -0
- package/src/cart-drawer/gift-wrap.tsx +83 -0
- package/src/cart-drawer/header.tsx +52 -0
- package/src/cart-drawer/index.ts +69 -0
- package/src/cart-drawer/item/index.tsx +164 -0
- package/src/cart-drawer/item/quantity.tsx +100 -0
- package/src/cart-drawer/item/upsell.tsx +110 -0
- package/src/cart-drawer/item/variant.tsx +46 -0
- package/src/cart-drawer/labels-bg.ts +72 -0
- package/src/cart-drawer/labels.ts +119 -0
- package/src/cart-drawer/notes.tsx +131 -0
- package/src/cart-drawer/payment-badges.tsx +96 -0
- package/src/cart-drawer/promo-banner.tsx +43 -0
- package/src/cart-drawer/rewards-points.tsx +78 -0
- package/src/cart-drawer/sticky-footer.tsx +73 -0
- package/src/cart-drawer/summary-breakdown.tsx +196 -0
- package/src/cart-drawer/template.tsx +225 -0
- package/src/cart-drawer/tiered-progress.tsx +168 -0
- package/src/checkout/address-error-copy.ts +119 -0
- package/src/checkout/address-form.tsx +224 -0
- package/src/checkout/address-select.tsx +79 -0
- package/src/checkout/boxnow-locker-selector.tsx +410 -0
- package/src/checkout/checkout-client.tsx +222 -0
- package/src/checkout/company-details.tsx +94 -0
- package/src/checkout/compare-addresses.ts +40 -0
- package/src/checkout/context.tsx +76 -0
- package/src/checkout/discount-section.tsx +218 -0
- package/src/checkout/econt-office-selector.tsx +332 -0
- package/src/checkout/error-message.tsx +25 -0
- package/src/checkout/geocode.ts +154 -0
- package/src/checkout/gift-card-section.tsx +224 -0
- package/src/checkout/index.ts +74 -0
- package/src/checkout/labels-bg.ts +128 -0
- package/src/checkout/labels.ts +263 -0
- package/src/checkout/line-item-card.tsx +152 -0
- package/src/checkout/order-summary.tsx +524 -0
- package/src/checkout/payment-button.tsx +373 -0
- package/src/checkout/payment-error-copy.ts +269 -0
- package/src/checkout/payment-method-list.tsx +365 -0
- package/src/checkout/payment-wrapper.tsx +102 -0
- package/src/checkout/promotion-error-copy.ts +124 -0
- package/src/checkout/shipping-method-list.tsx +335 -0
- package/src/checkout/stripe-wrapper.tsx +165 -0
- package/src/checkout/use-checkout-orchestration.ts +1504 -0
- package/src/common/cart-button-client.tsx +39 -0
- package/src/common/cart-button.tsx +28 -0
- package/src/common/country-select.tsx +65 -0
- package/src/common/delete-button.tsx +66 -0
- package/src/common/index.ts +17 -0
- package/src/common/language-select.tsx +78 -0
- package/src/common/localized-link.tsx +45 -0
- package/src/common/skeleton.tsx +29 -0
- package/src/index.ts +12 -0
- package/src/lib/cart-helpers.ts +113 -0
- package/src/lib/dual-price.tsx +73 -0
- package/src/lib/get-percentage-diff.ts +5 -0
- package/src/lib/get-product-price.ts +133 -0
- package/src/lib/hooks/use-intersection.ts +30 -0
- package/src/lib/hooks/use-toggle-state.ts +25 -0
- package/src/lib/money.ts +73 -0
- package/src/lib/payment-constants.ts +66 -0
- package/src/lib/product.ts +22 -0
- package/src/lib/sort-products.ts +63 -0
- package/src/lib/store-api-error.ts +36 -0
- package/src/lib/utils.ts +16 -0
- package/src/order/context.tsx +32 -0
- package/src/order/index.ts +63 -0
- package/src/order/labels-bg.ts +39 -0
- package/src/order/labels.ts +79 -0
- package/src/order/order-address-card.tsx +47 -0
- package/src/order/order-completed-template.tsx +165 -0
- package/src/order/order-confirmation-header.tsx +65 -0
- package/src/order/order-delivery-card.tsx +258 -0
- package/src/order/order-help-section.tsx +47 -0
- package/src/order/order-item.tsx +201 -0
- package/src/order/order-items-list.tsx +52 -0
- package/src/order/order-payment-card.tsx +95 -0
- package/src/order/order-timeline.tsx +141 -0
- package/src/order/order-totals.tsx +245 -0
- package/src/primitives/field.tsx +125 -0
- package/src/primitives/select-field.tsx +77 -0
- package/src/primitives/ui/accordion.tsx +61 -0
- package/src/primitives/ui/button.tsx +68 -0
- package/src/primitives/ui/collapsible.tsx +16 -0
- package/src/primitives/ui/dialog.tsx +112 -0
- package/src/primitives/ui/input.tsx +30 -0
- package/src/primitives/ui/label.tsx +31 -0
- package/src/primitives/ui/popover.tsx +38 -0
- package/src/primitives/ui/select.tsx +163 -0
- package/src/primitives/ui/sheet.tsx +131 -0
- package/src/primitives/ui/tabs.tsx +62 -0
- package/src/products/context.tsx +34 -0
- package/src/products/image-gallery.tsx +43 -0
- package/src/products/index.ts +44 -0
- package/src/products/labels-bg.ts +35 -0
- package/src/products/labels.ts +57 -0
- package/src/products/mobile-actions.tsx +180 -0
- package/src/products/option-select.tsx +67 -0
- package/src/products/preview-price.tsx +36 -0
- package/src/products/product-actions-wrapper.tsx +58 -0
- package/src/products/product-actions.tsx +217 -0
- package/src/products/product-info.tsx +43 -0
- package/src/products/product-preview.tsx +49 -0
- package/src/products/product-price.tsx +69 -0
- package/src/products/product-tabs.tsx +169 -0
- package/src/products/product-template.tsx +114 -0
- package/src/products/purchase-options.tsx +130 -0
- package/src/products/related-products.tsx +86 -0
- package/src/products/thumbnail.tsx +71 -0
- package/src/products/variant-matching.ts +71 -0
- package/src/reviews-ui/helpers.ts +174 -0
- package/src/reviews-ui/index.ts +74 -0
- package/src/reviews-ui/labels-bg.ts +91 -0
- package/src/reviews-ui/labels.ts +199 -0
- package/src/reviews-ui/photo-upload.tsx +345 -0
- package/src/reviews-ui/review-list.tsx +249 -0
- package/src/reviews-ui/review-widget.tsx +224 -0
- package/src/reviews-ui/review-wizard.tsx +560 -0
- package/src/reviews-ui/star-badge.tsx +104 -0
- package/src/reviews-ui/wizard-state.ts +81 -0
- package/src/store/category-template.tsx +129 -0
- package/src/store/collection-template.tsx +139 -0
- package/src/store/index.ts +41 -0
- package/src/store/labels-bg.ts +22 -0
- package/src/store/labels.ts +52 -0
- package/src/store/paginated-products.tsx +116 -0
- package/src/store/pagination.tsx +103 -0
- package/src/store/search-params.ts +256 -0
- package/src/store/search-template.tsx +249 -0
- package/src/store/skeleton-product-grid.tsx +26 -0
- package/src/store/sort-select.tsx +81 -0
- package/src/store/store-template.tsx +65 -0
- package/src/tracking/attribution.ts +418 -0
- package/src/tracking/consent-banner.tsx +355 -0
- package/src/tracking/consent-init.tsx +44 -0
- package/src/tracking/consent.ts +243 -0
- package/src/tracking/fbq.ts +168 -0
- package/src/tracking/ga4.tsx +49 -0
- package/src/tracking/get-tracking-attribution.ts +224 -0
- package/src/tracking/get-tracking-config.ts +50 -0
- package/src/tracking/gtag.ts +200 -0
- package/src/tracking/index.ts +133 -0
- package/src/tracking/meta-pixel.tsx +166 -0
- package/src/tracking/rybbit-events.ts +242 -0
- package/src/tracking/rybbit.tsx +40 -0
- package/src/tracking/types.ts +185 -0
- package/src/tracking/use-engagement-time.ts +58 -0
- package/tailwind-preset.cjs +72 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @cartbase/storefront/api/products — products + product variants.
|
|
3
|
+
*
|
|
4
|
+
* The catalog read surface. Response shapes are the canonical store product
|
|
5
|
+
* object every discovery endpoint reuses (search results, collection
|
|
6
|
+
* membership pages, related products) — build ONE product-card renderer
|
|
7
|
+
* against `StoreProduct` and reuse it everywhere.
|
|
8
|
+
*
|
|
9
|
+
* Pricing: pass `currency_code` (or `region_id`) to receive
|
|
10
|
+
* `variant.calculated_price` (group-aware when a customer JWT is attached).
|
|
11
|
+
* Raw `prices` embeds contain BASE rows only — price-list rows are stripped
|
|
12
|
+
* server-side (leak rule) and surface exclusively through
|
|
13
|
+
* `calculated_price`. Docs: docs/storefront/products.md.
|
|
14
|
+
*/
|
|
15
|
+
import type { StorefrontClient } from "./http"
|
|
16
|
+
import type {
|
|
17
|
+
CalculatedPrice,
|
|
18
|
+
IsoDateString,
|
|
19
|
+
ListEnvelope,
|
|
20
|
+
PaginationQuery,
|
|
21
|
+
PricingContextQuery,
|
|
22
|
+
} from "./types"
|
|
23
|
+
|
|
24
|
+
/** Raw base price row (`prices` table). Amounts are EUR-style major units. */
|
|
25
|
+
export interface StorePriceRow {
|
|
26
|
+
id: string
|
|
27
|
+
amount: number
|
|
28
|
+
currency_code: string
|
|
29
|
+
min_quantity: number | null
|
|
30
|
+
max_quantity: number | null
|
|
31
|
+
price_set_id: string
|
|
32
|
+
/** Always null on the store surface — price-list rows are stripped. */
|
|
33
|
+
price_list_id: string | null
|
|
34
|
+
created_at: IsoDateString
|
|
35
|
+
updated_at: IsoDateString
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The wire shape of `variant.prices`: a price-set LINK embed, not a flat
|
|
40
|
+
* array. Base rows live at `prices[].price_set.prices[]`.
|
|
41
|
+
*/
|
|
42
|
+
export interface StoreVariantPriceSetLink {
|
|
43
|
+
price_set: { prices: StorePriceRow[] | null } | null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** `variant.options[]` — the option VALUE with its parent option embedded. */
|
|
47
|
+
export interface StoreVariantOptionValueLink {
|
|
48
|
+
value: {
|
|
49
|
+
id: string
|
|
50
|
+
value: string
|
|
51
|
+
option_id: string
|
|
52
|
+
metadata: Record<string, unknown> | null
|
|
53
|
+
option: StoreProductOption | null
|
|
54
|
+
} | null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface StoreProductOptionValue {
|
|
58
|
+
id: string
|
|
59
|
+
value: string
|
|
60
|
+
option_id: string
|
|
61
|
+
metadata: Record<string, unknown> | null
|
|
62
|
+
created_at: IsoDateString
|
|
63
|
+
updated_at: IsoDateString
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface StoreProductOption {
|
|
67
|
+
id: string
|
|
68
|
+
title: string
|
|
69
|
+
product_id: string
|
|
70
|
+
metadata: Record<string, unknown> | null
|
|
71
|
+
created_at: IsoDateString
|
|
72
|
+
updated_at: IsoDateString
|
|
73
|
+
/** Present on `product.options[]`; absent inside variant option links. */
|
|
74
|
+
values?: StoreProductOptionValue[]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface StoreProductImage {
|
|
78
|
+
id: string
|
|
79
|
+
url: string
|
|
80
|
+
rank: number
|
|
81
|
+
product_id: string
|
|
82
|
+
metadata: Record<string, unknown> | null
|
|
83
|
+
created_at: IsoDateString
|
|
84
|
+
updated_at: IsoDateString
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface StoreProductVariant {
|
|
88
|
+
id: string
|
|
89
|
+
title: string
|
|
90
|
+
product_id: string
|
|
91
|
+
sku: string | null
|
|
92
|
+
barcode: string | null
|
|
93
|
+
ean: string | null
|
|
94
|
+
upc: string | null
|
|
95
|
+
thumbnail: string | null
|
|
96
|
+
allow_backorder: boolean
|
|
97
|
+
manage_inventory: boolean
|
|
98
|
+
variant_rank: number
|
|
99
|
+
weight: number | null
|
|
100
|
+
length: number | null
|
|
101
|
+
height: number | null
|
|
102
|
+
width: number | null
|
|
103
|
+
hs_code: string | null
|
|
104
|
+
origin_country: string | null
|
|
105
|
+
mid_code: string | null
|
|
106
|
+
material: string | null
|
|
107
|
+
metadata: Record<string, unknown> | null
|
|
108
|
+
created_at: IsoDateString
|
|
109
|
+
updated_at: IsoDateString
|
|
110
|
+
options: StoreVariantOptionValueLink[]
|
|
111
|
+
/** Base-price rows via the price-set link embed (leak rule applied). */
|
|
112
|
+
prices: StoreVariantPriceSetLink[]
|
|
113
|
+
/**
|
|
114
|
+
* Present when a pricing context was resolvable (`currency_code` or
|
|
115
|
+
* `region_id` given); null otherwise, and null when no price matches the
|
|
116
|
+
* context. Varies by customer group — never cache shared.
|
|
117
|
+
*/
|
|
118
|
+
calculated_price: CalculatedPrice | null
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface StoreProduct {
|
|
122
|
+
id: string
|
|
123
|
+
title: string
|
|
124
|
+
subtitle: string | null
|
|
125
|
+
description: string | null
|
|
126
|
+
handle: string
|
|
127
|
+
status: "published"
|
|
128
|
+
thumbnail: string | null
|
|
129
|
+
is_giftcard: boolean
|
|
130
|
+
discountable: boolean
|
|
131
|
+
collection_id: string | null
|
|
132
|
+
type_id: string | null
|
|
133
|
+
external_id: string | null
|
|
134
|
+
weight: number | null
|
|
135
|
+
length: number | null
|
|
136
|
+
height: number | null
|
|
137
|
+
width: number | null
|
|
138
|
+
hs_code: string | null
|
|
139
|
+
origin_country: string | null
|
|
140
|
+
mid_code: string | null
|
|
141
|
+
material: string | null
|
|
142
|
+
/** SEO override; null = fall back to `title`. */
|
|
143
|
+
seo_title: string | null
|
|
144
|
+
/** SEO override; null = fall back to the plain-text `description`. */
|
|
145
|
+
seo_description: string | null
|
|
146
|
+
metadata: Record<string, unknown> | null
|
|
147
|
+
created_at: IsoDateString
|
|
148
|
+
updated_at: IsoDateString
|
|
149
|
+
variants: StoreProductVariant[]
|
|
150
|
+
images: StoreProductImage[]
|
|
151
|
+
options: StoreProductOption[]
|
|
152
|
+
collection: { id: string; title: string; handle: string } | null
|
|
153
|
+
categories: Array<{ category: { id: string; name: string; handle: string } | null }>
|
|
154
|
+
tags: Array<{ tag: { id: string; value: string } | null }>
|
|
155
|
+
type: { id: string; value: string } | null
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface ListProductsQuery extends PricingContextQuery, PaginationQuery {
|
|
159
|
+
/** Case-insensitive substring match on title (full search: use /search). */
|
|
160
|
+
q?: string
|
|
161
|
+
/** Product ids (joined as CSV on the wire). */
|
|
162
|
+
id?: string[]
|
|
163
|
+
handle?: string
|
|
164
|
+
collection_id?: string[]
|
|
165
|
+
category_id?: string[]
|
|
166
|
+
tag_id?: string[]
|
|
167
|
+
type_id?: string[]
|
|
168
|
+
/**
|
|
169
|
+
* Sort column; prefix with `-` for descending (e.g. `-created_at`,
|
|
170
|
+
* the default).
|
|
171
|
+
*/
|
|
172
|
+
order?: string
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface ProductListResponse extends ListEnvelope {
|
|
176
|
+
products: StoreProduct[]
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export interface ProductResponse {
|
|
180
|
+
product: StoreProduct
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface ListProductVariantsQuery extends PricingContextQuery, PaginationQuery {
|
|
184
|
+
/** Variant ids as CSV in ONE string (wire format of the route). */
|
|
185
|
+
id?: string
|
|
186
|
+
product_id?: string
|
|
187
|
+
sku?: string
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface ProductVariantListResponse extends ListEnvelope {
|
|
191
|
+
variants: StoreProductVariant[]
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export interface ProductVariantResponse {
|
|
195
|
+
variant: StoreProductVariant
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* List published products (newest first by default).
|
|
200
|
+
*
|
|
201
|
+
* Auth: anon (`x-client-id`); optional `x-publishable-api-key` restricts the
|
|
202
|
+
* catalog to products linked to the key's sales channels (a channel-scoped
|
|
203
|
+
* key with no matching products returns an empty list). Optional Bearer JWT
|
|
204
|
+
* makes `calculated_price` customer-group-aware.
|
|
205
|
+
*
|
|
206
|
+
* Errors: 400 `missing_client_id`, 400 `validation_failed`,
|
|
207
|
+
* 400 `invalid_publishable_key`, 400 `invalid_region`.
|
|
208
|
+
* Settings: price lists (B2B pricing), sales-channel product links,
|
|
209
|
+
* publishable-key channel bindings.
|
|
210
|
+
*/
|
|
211
|
+
export async function listProducts(
|
|
212
|
+
client: StorefrontClient,
|
|
213
|
+
query?: ListProductsQuery
|
|
214
|
+
): Promise<ProductListResponse> {
|
|
215
|
+
return client.get("/api/store/products", { query: { ...query } })
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Retrieve one product by id (`prod_…`) or handle — storefronts deep-link by
|
|
220
|
+
* handle.
|
|
221
|
+
*
|
|
222
|
+
* Auth: anon (`x-client-id`); optional publishable key + Bearer JWT as on
|
|
223
|
+
* the list. Errors: 404 `not_found` (unknown, draft — RLS-hidden — or
|
|
224
|
+
* outside the key's channels: invisible, not forbidden),
|
|
225
|
+
* 400 `invalid_publishable_key`, 400 `invalid_region`.
|
|
226
|
+
*/
|
|
227
|
+
export async function retrieveProduct(
|
|
228
|
+
client: StorefrontClient,
|
|
229
|
+
idOrHandle: string,
|
|
230
|
+
query?: PricingContextQuery
|
|
231
|
+
): Promise<ProductResponse> {
|
|
232
|
+
return client.get(`/api/store/products/${encodeURIComponent(idOrHandle)}`, {
|
|
233
|
+
query: { ...query },
|
|
234
|
+
})
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* List product variants (cart-line hydration). `id` accepts a CSV of variant
|
|
239
|
+
* ids for multi-lookup. Ordered by `variant_rank`.
|
|
240
|
+
*
|
|
241
|
+
* Auth: anon (`x-client-id`); optional Bearer JWT for group-aware
|
|
242
|
+
* `calculated_price`. NOTE: this listing is NOT publishable-key
|
|
243
|
+
* channel-scoped (scope applies to product reads). Errors:
|
|
244
|
+
* 400 `missing_client_id`, 400 `validation_failed`, 400 `invalid_region`.
|
|
245
|
+
*/
|
|
246
|
+
export async function listProductVariants(
|
|
247
|
+
client: StorefrontClient,
|
|
248
|
+
query?: ListProductVariantsQuery
|
|
249
|
+
): Promise<ProductVariantListResponse> {
|
|
250
|
+
return client.get("/api/store/product-variants", { query: { ...query } })
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Retrieve one variant by id, options + base prices hydrated,
|
|
255
|
+
* `calculated_price` when a pricing context is given.
|
|
256
|
+
*
|
|
257
|
+
* Auth: anon (`x-client-id`). Errors: 404 `not_found`, 400 `invalid_region`.
|
|
258
|
+
*/
|
|
259
|
+
export async function retrieveProductVariant(
|
|
260
|
+
client: StorefrontClient,
|
|
261
|
+
variantId: string,
|
|
262
|
+
query?: PricingContextQuery
|
|
263
|
+
): Promise<ProductVariantResponse> {
|
|
264
|
+
return client.get(`/api/store/product-variants/${encodeURIComponent(variantId)}`, {
|
|
265
|
+
query: { ...query },
|
|
266
|
+
})
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** A subscription offer on a product (PDP purchase options). */
|
|
270
|
+
export interface StoreSellingPlan {
|
|
271
|
+
id: string
|
|
272
|
+
name: string
|
|
273
|
+
interval: "day" | "week" | "month" | "year"
|
|
274
|
+
interval_count: number
|
|
275
|
+
/** null = catalog price (the plan only sets the cadence). */
|
|
276
|
+
pricing_type: "percent" | "fixed" | null
|
|
277
|
+
pricing_value: number | null
|
|
278
|
+
min_cycles: number | null
|
|
279
|
+
max_cycles: number | null
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export interface SellingPlanListResponse {
|
|
283
|
+
selling_plans: StoreSellingPlan[]
|
|
284
|
+
count: number
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* The subscription plans a product can be purchased with — render as PDP
|
|
289
|
+
* purchase options (one-time vs each plan). The chosen plan id goes on the
|
|
290
|
+
* cart line (`carts.addLineItem` `selling_plan_id`); the server applies the
|
|
291
|
+
* plan price. Empty list = product is one-time only.
|
|
292
|
+
*
|
|
293
|
+
* Auth: anon (`x-client-id`); publishable-key channel scope applies (same
|
|
294
|
+
* rule as the product read). Errors: 404 `not_found`.
|
|
295
|
+
*/
|
|
296
|
+
export async function listSellingPlans(
|
|
297
|
+
client: StorefrontClient,
|
|
298
|
+
idOrHandle: string
|
|
299
|
+
): Promise<SellingPlanListResponse> {
|
|
300
|
+
return client.get(
|
|
301
|
+
`/api/store/products/${encodeURIComponent(idOrHandle)}/selling-plans`
|
|
302
|
+
)
|
|
303
|
+
}
|
|
@@ -0,0 +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
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @cartbase/storefront/api/regions — regions, currencies, locales.
|
|
3
|
+
*
|
|
4
|
+
* Catalog-context primitives every storefront resolves at boot: which
|
|
5
|
+
* regions exist (→ `region_id` for the pricing context), which currencies
|
|
6
|
+
* the store has enabled, which locales it serves. All endpoints are anon
|
|
7
|
+
* reads (`x-client-id` scoping via RLS). Docs: docs/storefront/regions.md.
|
|
8
|
+
*/
|
|
9
|
+
import type { StorefrontClient } from "./http"
|
|
10
|
+
import type { IsoDateString, ListEnvelope, PaginationQuery } from "./types"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A store region (`barter_commerce.regions` row). NOTE: barter regions carry
|
|
14
|
+
* NO embedded `countries` array on the store surface (divergence from
|
|
15
|
+
* Medusa's Store API — tax scoping is by `tax_regions` server-side).
|
|
16
|
+
*/
|
|
17
|
+
export interface StoreRegion {
|
|
18
|
+
id: string
|
|
19
|
+
name: string
|
|
20
|
+
/** Lowercase ISO currency, e.g. "eur" — feeds the pricing context. */
|
|
21
|
+
currency_code: string
|
|
22
|
+
automatic_taxes: boolean
|
|
23
|
+
metadata: Record<string, unknown> | null
|
|
24
|
+
created_at: IsoDateString
|
|
25
|
+
updated_at: IsoDateString
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Shared-catalog currency row, scoped to the store's enabled set on list. */
|
|
29
|
+
export interface StoreCurrency {
|
|
30
|
+
/** Lowercase ISO code, e.g. "eur". */
|
|
31
|
+
code: string
|
|
32
|
+
name: string
|
|
33
|
+
symbol: string
|
|
34
|
+
symbol_native: string | null
|
|
35
|
+
decimal_digits: number
|
|
36
|
+
rounding: number
|
|
37
|
+
created_at: IsoDateString
|
|
38
|
+
updated_at: IsoDateString
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ListRegionsQuery extends PaginationQuery {
|
|
42
|
+
/** Case-insensitive substring match on region name. */
|
|
43
|
+
q?: string
|
|
44
|
+
/** Exact match (lowercased server-side). */
|
|
45
|
+
currency_code?: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface RegionListResponse extends ListEnvelope {
|
|
49
|
+
regions: StoreRegion[]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface RegionResponse {
|
|
53
|
+
region: StoreRegion
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ListCurrenciesQuery extends PaginationQuery {
|
|
57
|
+
/** Exact code match (lowercased server-side). */
|
|
58
|
+
code?: string
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface CurrencyListResponse extends ListEnvelope {
|
|
62
|
+
currencies: StoreCurrency[]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface CurrencyResponse {
|
|
66
|
+
currency: StoreCurrency
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface LocaleListResponse {
|
|
70
|
+
/** Locale codes, store default first, then alphabetical (e.g. ["en","bg"]). */
|
|
71
|
+
locales: string[]
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* List the store's regions, ordered by name.
|
|
76
|
+
*
|
|
77
|
+
* Auth: anon (`x-client-id`). Errors: 400 `missing_client_id`,
|
|
78
|
+
* 400 `validation_failed`. Settings: regions are created in
|
|
79
|
+
* Admin → Settings → Regions; `limit` 1–200 (default 50).
|
|
80
|
+
*/
|
|
81
|
+
export async function listRegions(
|
|
82
|
+
client: StorefrontClient,
|
|
83
|
+
query?: ListRegionsQuery
|
|
84
|
+
): Promise<RegionListResponse> {
|
|
85
|
+
return client.get("/api/store/regions", { query: { ...query } })
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Retrieve one region by id.
|
|
90
|
+
*
|
|
91
|
+
* Auth: anon (`x-client-id`). Errors: 404 `not_found` (unknown id,
|
|
92
|
+
* soft-deleted, or another tenant's region — invisible, not forbidden).
|
|
93
|
+
*/
|
|
94
|
+
export async function retrieveRegion(
|
|
95
|
+
client: StorefrontClient,
|
|
96
|
+
regionId: string
|
|
97
|
+
): Promise<RegionResponse> {
|
|
98
|
+
return client.get(`/api/store/regions/${encodeURIComponent(regionId)}`)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* List the currencies ENABLED on this store (the `store_currencies` link
|
|
103
|
+
* filters the shared currency catalog). A store with none enabled returns an
|
|
104
|
+
* empty list, ordered by code.
|
|
105
|
+
*
|
|
106
|
+
* Auth: anon (`x-client-id`). Errors: 400 `missing_client_id`,
|
|
107
|
+
* 400 `validation_failed`. Settings: Admin → Settings → Store → currencies.
|
|
108
|
+
*/
|
|
109
|
+
export async function listCurrencies(
|
|
110
|
+
client: StorefrontClient,
|
|
111
|
+
query?: ListCurrenciesQuery
|
|
112
|
+
): Promise<CurrencyListResponse> {
|
|
113
|
+
return client.get("/api/store/currencies", { query: { ...query } })
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Retrieve one currency by code (case-insensitive).
|
|
118
|
+
*
|
|
119
|
+
* Auth: anon (`x-client-id`). Errors: 404 `not_found`.
|
|
120
|
+
*
|
|
121
|
+
* CODE-TRUTH NOTE: unlike the list, the single read is NOT filtered by the
|
|
122
|
+
* store's enabled set — any currency in the shared catalog resolves. Treat
|
|
123
|
+
* the list as the authority on what the store supports.
|
|
124
|
+
*/
|
|
125
|
+
export async function retrieveCurrency(
|
|
126
|
+
client: StorefrontClient,
|
|
127
|
+
code: string
|
|
128
|
+
): Promise<CurrencyResponse> {
|
|
129
|
+
return client.get(`/api/store/currencies/${encodeURIComponent(code)}`)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* List the store's supported locale codes (default locale first). Use to
|
|
134
|
+
* build the storefront language switcher and to set the client's `x-locale`.
|
|
135
|
+
*
|
|
136
|
+
* Auth: anon (`x-client-id`). Errors: 400 `missing_client_id`.
|
|
137
|
+
* Settings: Admin → Settings → Store → locales (per-store store_locales).
|
|
138
|
+
*/
|
|
139
|
+
export async function listLocales(client: StorefrontClient): Promise<LocaleListResponse> {
|
|
140
|
+
return client.get("/api/store/locales")
|
|
141
|
+
}
|