@cartbase/storefront 0.16.0 → 0.17.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 +2 -1
- package/src/api/http.ts +51 -1
- package/src/cart-drawer/cross-sell-carousel.tsx +1 -1
- package/src/cart-drawer/cross-sell-sidebar.tsx +1 -1
- package/src/cart-drawer/free-gift.tsx +1 -1
- package/src/cart-drawer/item/index.tsx +1 -1
- package/src/cart-drawer/item/upsell.tsx +1 -1
- package/src/lib/media-image.tsx +39 -0
- package/src/order/order-item.tsx +1 -1
- package/src/products/image-gallery.tsx +1 -1
- package/src/products/thumbnail.tsx +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cartbase/storefront",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Storefront SDK + UI component library for Cartbase stores: typed API client, checkout orchestration, cart drawer, product/catalog components, tracking. Source-shipped TypeScript — add it to transpilePackages.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"./lib/hooks/use-intersection": "./src/lib/hooks/use-intersection.ts",
|
|
57
57
|
"./lib/hooks/use-toggle-state": "./src/lib/hooks/use-toggle-state.ts",
|
|
58
58
|
"./lib/cookie-names": "./src/lib/cookie-names.ts",
|
|
59
|
+
"./lib/media-image": "./src/lib/media-image.tsx",
|
|
59
60
|
"./lib/platform": "./src/lib/platform.ts",
|
|
60
61
|
"./lib/visitor": "./src/lib/visitor.ts",
|
|
61
62
|
"./platform": "./src/platform/index.ts",
|
package/src/api/http.ts
CHANGED
|
@@ -42,6 +42,42 @@ export interface StorefrontClientConfig {
|
|
|
42
42
|
export type QueryValue = string | number | boolean | null | undefined
|
|
43
43
|
export type Query = Record<string, QueryValue | QueryValue[]>
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* The reads the platform's CDN holds for anonymous shoppers (store-api.md
|
|
47
|
+
* § Caching). Such a request carries the store's publishable key in its
|
|
48
|
+
* address as `pk`, so the edge can tell one store's answers from another's,
|
|
49
|
+
* and the shopper's locale as `locale`. A request with a customer token
|
|
50
|
+
* carries neither: its prices are the shopper's own, and an address without
|
|
51
|
+
* the key is never held and never served from the hold. Carts, checkout,
|
|
52
|
+
* consent, customers and every write are never on this list.
|
|
53
|
+
*/
|
|
54
|
+
const CACHEABLE_READ_PREFIXES = [
|
|
55
|
+
"/api/store/store",
|
|
56
|
+
"/api/store/menus",
|
|
57
|
+
"/api/store/regions",
|
|
58
|
+
"/api/store/collections",
|
|
59
|
+
"/api/store/products",
|
|
60
|
+
"/api/store/product-variants",
|
|
61
|
+
"/api/store/product-categories",
|
|
62
|
+
"/api/store/product-types",
|
|
63
|
+
"/api/store/product-tags",
|
|
64
|
+
"/api/store/reviews",
|
|
65
|
+
"/api/store/currencies",
|
|
66
|
+
"/api/store/countries",
|
|
67
|
+
"/api/store/locales",
|
|
68
|
+
"/api/store/pages",
|
|
69
|
+
"/api/store/blogs",
|
|
70
|
+
"/api/store/metaobjects",
|
|
71
|
+
"/api/store/url-redirects",
|
|
72
|
+
"/api/store/return-reasons",
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
export function isCacheableStoreRead(pathname: string): boolean {
|
|
76
|
+
return CACHEABLE_READ_PREFIXES.some(
|
|
77
|
+
(prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`)
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
45
81
|
export interface RequestOptions {
|
|
46
82
|
method?: "GET" | "POST" | "DELETE"
|
|
47
83
|
query?: Query
|
|
@@ -92,9 +128,23 @@ export class StorefrontClient {
|
|
|
92
128
|
const locale = this.config.getLocale ? await this.config.getLocale() : null
|
|
93
129
|
if (locale && !headers["x-locale"]) headers["x-locale"] = locale
|
|
94
130
|
|
|
131
|
+
// An anonymous catalogue read carries its key (and locale) in the
|
|
132
|
+
// address, so the platform's CDN may hold the answer for this store.
|
|
133
|
+
const method = opts.method ?? "GET"
|
|
134
|
+
const anonymous = !headers.authorization
|
|
135
|
+
if (
|
|
136
|
+
method === "GET" &&
|
|
137
|
+
anonymous &&
|
|
138
|
+
this.config.publishableKey &&
|
|
139
|
+
isCacheableStoreRead(url.pathname)
|
|
140
|
+
) {
|
|
141
|
+
url.searchParams.set("pk", this.config.publishableKey)
|
|
142
|
+
if (locale) url.searchParams.set("locale", locale)
|
|
143
|
+
}
|
|
144
|
+
|
|
95
145
|
const doFetch = this.config.fetch ?? fetch
|
|
96
146
|
const res = await doFetch(url.toString(), {
|
|
97
|
-
method
|
|
147
|
+
method,
|
|
98
148
|
headers,
|
|
99
149
|
body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
|
|
100
150
|
cache: opts.cache,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import NextImage, { type ImageProps } from "next/image"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The store's picture element (store-speed-law card, step 3, 2026-09-15).
|
|
5
|
+
*
|
|
6
|
+
* Product pictures live on the Cartbase media CDN, and Next resizes them on
|
|
7
|
+
* the store's own project, per device and in the modern formats, which the
|
|
8
|
+
* scaffold allows through `images.remotePatterns` (next.config.ts). The
|
|
9
|
+
* scaffold used to ship `images.unoptimized: true` instead, so no picture
|
|
10
|
+
* was ever resized: a phone downloaded the merchant's full upload.
|
|
11
|
+
*
|
|
12
|
+
* A picture from any other host (a catalogue not yet rehosted, a demo
|
|
13
|
+
* placeholder, a merchant's own asset host) renders as it is instead of
|
|
14
|
+
* failing the page: Next refuses to resize a host the config does not
|
|
15
|
+
* list, so this element switches resizing off for those. A merchant who
|
|
16
|
+
* adds their host to `remotePatterns` gets resizing there too by passing
|
|
17
|
+
* `unoptimized={false}` where they render it, or by using `next/image`
|
|
18
|
+
* directly.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** The public base of the media CDN (REGISTRY.md, CDN domain). */
|
|
22
|
+
export const MEDIA_HOST = "cartbase.cloud"
|
|
23
|
+
|
|
24
|
+
/** Is this picture served by the media CDN (or by the app itself)? */
|
|
25
|
+
export function isResizableSrc(src: ImageProps["src"]): boolean {
|
|
26
|
+
if (typeof src !== "string") return true
|
|
27
|
+
if (src.startsWith("/")) return true
|
|
28
|
+
try {
|
|
29
|
+
const host = new URL(src).hostname
|
|
30
|
+
return host === MEDIA_HOST || host.endsWith(`.${MEDIA_HOST}`)
|
|
31
|
+
} catch {
|
|
32
|
+
return false
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function StoreImage(props: ImageProps) {
|
|
37
|
+
const unoptimized = props.unoptimized ?? !isResizableSrc(props.src)
|
|
38
|
+
return <NextImage {...props} unoptimized={unoptimized} />
|
|
39
|
+
}
|
package/src/order/order-item.tsx
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* (`@cartbase/storefront/api/products`; images arrive rank-ordered from the
|
|
6
6
|
* API). Server-safe.
|
|
7
7
|
*/
|
|
8
|
-
import Image from "
|
|
8
|
+
import { StoreImage as Image } from "../lib/media-image"
|
|
9
9
|
import type { StoreProductImage } from "../api/products"
|
|
10
10
|
|
|
11
11
|
type ImageGalleryProps = {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* + `StoreProduct.images`). Server-safe (no "use client"): renders inside
|
|
5
5
|
* RSC product grids.
|
|
6
6
|
*/
|
|
7
|
-
import Image from "
|
|
7
|
+
import { StoreImage as Image } from "../lib/media-image"
|
|
8
8
|
import React from "react"
|
|
9
9
|
import { ImageOff } from "lucide-react"
|
|
10
10
|
|