@cartbase/storefront 0.17.0 → 0.18.1
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/LICENSE +21 -21
- package/package.json +3 -1
- package/src/api/auth.ts +108 -108
- package/src/api/carts.ts +523 -523
- package/src/api/categories.ts +184 -184
- package/src/api/checkout.ts +526 -526
- package/src/api/collections.ts +130 -130
- package/src/api/consent.ts +75 -75
- package/src/api/content.ts +125 -125
- package/src/api/customers.ts +303 -303
- package/src/api/gift-cards.ts +112 -112
- package/src/api/http.ts +180 -180
- package/src/api/index.ts +30 -30
- package/src/api/menus.ts +77 -77
- package/src/api/metaobjects.ts +136 -136
- package/src/api/orders.ts +290 -290
- package/src/api/products.ts +43 -1
- package/src/api/redirects.ts +37 -37
- package/src/api/regions.ts +200 -200
- package/src/api/reviews.ts +259 -259
- package/src/api/search.ts +163 -163
- package/src/api/store.ts +35 -35
- package/src/api/types.ts +91 -91
- package/src/cart-drawer/context.tsx +367 -168
- package/src/cart-drawer/cross-sell-carousel.tsx +211 -211
- package/src/cart-drawer/cross-sell-sidebar.tsx +158 -158
- package/src/cart-drawer/gift-wrap.tsx +82 -82
- package/src/cart-drawer/index.ts +73 -68
- package/src/cart-drawer/item/index.tsx +162 -162
- package/src/cart-drawer/item/quantity.tsx +8 -14
- package/src/cart-drawer/item/upsell.tsx +110 -110
- package/src/cart-drawer/labels.ts +123 -123
- package/src/cart-drawer/mutation-queue.ts +78 -0
- package/src/cart-drawer/notes.tsx +131 -131
- package/src/cart-drawer/sticky-footer.tsx +73 -73
- package/src/cart-drawer/summary-breakdown.tsx +197 -197
- package/src/checkout/address-error-copy.ts +117 -117
- package/src/checkout/boxnow-locker-selector.tsx +410 -410
- package/src/checkout/compare-addresses.ts +40 -40
- package/src/checkout/discount-section.tsx +218 -218
- package/src/checkout/error-copy-codes.ts +63 -63
- package/src/checkout/geocode.ts +154 -154
- package/src/checkout/gift-card-section.tsx +224 -224
- package/src/checkout/index.ts +72 -72
- package/src/checkout/labels.ts +495 -495
- package/src/checkout/payment-button.tsx +372 -372
- package/src/checkout/payment-error-copy.ts +154 -154
- package/src/checkout/promotion-error-copy.ts +91 -91
- package/src/checkout/use-checkout-orchestration.ts +1604 -1592
- package/src/common/country-flag.tsx +52 -52
- package/src/common/country-select.tsx +11 -11
- package/src/common/index.ts +20 -20
- package/src/common/market-select.tsx +57 -57
- package/src/index.ts +12 -12
- package/src/lib/cookie-names.ts +14 -0
- package/src/lib/country-name.ts +59 -59
- package/src/lib/get-product-price.ts +133 -133
- package/src/lib/media-image.tsx +39 -39
- package/src/lib/payment-constants.ts +53 -53
- package/src/lib/platform.ts +13 -13
- package/src/lib/price.tsx +39 -39
- package/src/lib/store-api-error.ts +36 -36
- package/src/lib/variant-caption.ts +32 -32
- package/src/locales/bg.ts +467 -467
- package/src/locales/context.ts +37 -37
- package/src/locales/en.ts +26 -26
- package/src/locales/es.ts +466 -466
- package/src/locales/index.ts +19 -19
- package/src/locales/provider.tsx +59 -59
- package/src/locales/types.ts +77 -77
- package/src/order/index.ts +62 -62
- package/src/order/labels.ts +79 -79
- package/src/order/order-totals.tsx +250 -250
- package/src/primitives/select-field.tsx +93 -93
- package/src/products/image-gallery.tsx +43 -43
- package/src/products/index.ts +68 -59
- package/src/products/option-select.tsx +67 -67
- package/src/products/product-actions-wrapper.tsx +63 -62
- package/src/products/product-actions.tsx +151 -150
- package/src/products/product-price.tsx +69 -69
- package/src/products/product-promises.tsx +61 -61
- package/src/products/product-specs.tsx +65 -65
- package/src/products/product-tabs.tsx +123 -123
- package/src/products/product-template.tsx +163 -162
- package/src/products/purchase-options.tsx +130 -130
- package/src/products/sets.ts +110 -0
- package/src/products/use-product-actions.ts +199 -164
- package/src/products/variant-matching.ts +71 -71
- package/src/products/variant-url.ts +74 -74
- package/src/reviews-ui/index.ts +73 -73
- package/src/store/category-template.tsx +136 -136
- package/src/store/index.ts +40 -40
- package/src/tracking/chatgpt-pixel.tsx +99 -99
- package/src/tracking/consent-init.tsx +62 -62
- package/src/tracking/events.ts +348 -348
- package/src/tracking/ga4.tsx +93 -93
- package/src/tracking/google-ads.ts +84 -84
- package/src/tracking/gtm.tsx +60 -60
- package/src/tracking/inline-script.ts +49 -49
- package/src/tracking/oaiq.ts +206 -206
- package/src/tracking/tiktok-pixel.tsx +91 -91
- package/src/tracking/track-init.tsx +56 -56
- package/src/tracking/track-order-purchase.tsx +122 -122
- package/src/tracking/ttq.ts +180 -180
- package/src/tracking/use-tracking-config.ts +54 -54
- package/theme/index.css +25 -25
- package/theme/theme.css +150 -150
- package/theme/tokens.css +106 -106
package/src/tracking/ttq.ts
CHANGED
|
@@ -1,180 +1,180 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Typed wrappers around the global `ttq` from the TikTok Pixel — the
|
|
5
|
-
* TikTok twin of ./fbq.ts, and deliberately the same shape so a surface
|
|
6
|
-
* that fires one can fire the other on the line below.
|
|
7
|
-
*
|
|
8
|
-
* Every helper no-ops when `window.ttq` is absent (pixel not configured,
|
|
9
|
-
* server render, script blocked), so callers need no guards.
|
|
10
|
-
*
|
|
11
|
-
* Event names are case-sensitive and taken from TikTok's supported pixel
|
|
12
|
-
* events table:
|
|
13
|
-
* https://business-api.tiktok.com/portal/docs/supported-pixel-events/v1.3
|
|
14
|
-
* `Purchase` is the current name; `CompletePayment` and `PlaceAnOrder` do
|
|
15
|
-
* not appear in the current tables at all.
|
|
16
|
-
*
|
|
17
|
-
* `event_id` is the THIRD argument, a separate object after the
|
|
18
|
-
* properties, per TikTok's own implementation guide:
|
|
19
|
-
*
|
|
20
|
-
* ttq.track('Purchase', { ...properties }, { event_id: 'tt_purchase_1044' })
|
|
21
|
-
*
|
|
22
|
-
* TikTok deduplicates on event_source_id + event + event_id and discards
|
|
23
|
-
* duplicates for 48 hours from the first event, which is how the browser
|
|
24
|
-
* Purchase and the server-side Events API Purchase collapse into one.
|
|
25
|
-
*
|
|
26
|
-
* `content_id` must be the SAME identifier the product catalogue uses,
|
|
27
|
-
* which on Cartbase is the PRODUCT id — the value the feed emits as
|
|
28
|
-
* <g:id> and the value Meta already receives. A variant id here matches
|
|
29
|
-
* no catalogue entry at all.
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
/** One product line inside `contents`. */
|
|
33
|
-
export type TikTokContentItem = {
|
|
34
|
-
content_id: string
|
|
35
|
-
content_name?: string
|
|
36
|
-
content_category?: string
|
|
37
|
-
brand?: string
|
|
38
|
-
quantity?: number
|
|
39
|
-
price?: number
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
type TikTokProperties = {
|
|
43
|
-
contents?: TikTokContentItem[]
|
|
44
|
-
content_id?: string
|
|
45
|
-
content_name?: string
|
|
46
|
-
content_type?: "product" | "product_group"
|
|
47
|
-
currency?: string
|
|
48
|
-
value?: number
|
|
49
|
-
quantity?: number
|
|
50
|
-
description?: string
|
|
51
|
-
order_id?: string
|
|
52
|
-
/** TikTok's new-versus-returning signal. Same computed fact the backend
|
|
53
|
-
* persists on the order and sends to Google Ads as `new_customer`. */
|
|
54
|
-
customer_type?: "new" | "returning"
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
type TtqFn = {
|
|
58
|
-
track: (
|
|
59
|
-
event: string,
|
|
60
|
-
properties?: TikTokProperties,
|
|
61
|
-
options?: { event_id?: string }
|
|
62
|
-
) => void
|
|
63
|
-
page: () => void
|
|
64
|
-
identify: (data: Record<string, unknown>) => void
|
|
65
|
-
grantConsent: () => void
|
|
66
|
-
revokeConsent: () => void
|
|
67
|
-
holdConsent: () => void
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function safeTtq(): TtqFn | null {
|
|
71
|
-
if (typeof window === "undefined") return null
|
|
72
|
-
const ttq = (window as unknown as { ttq?: Partial<TtqFn> }).ttq
|
|
73
|
-
if (!ttq || typeof ttq.track !== "function") return null
|
|
74
|
-
return ttq as TtqFn
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* THE purchase dedup key — must equal what the backend forwarder sends
|
|
79
|
-
* (`tiktokPurchaseEventId` in src/lib/tracking/constants.ts). Built from
|
|
80
|
-
* display_id here so the format cannot drift between the two sides, the
|
|
81
|
-
* same guarantee `trackPurchase` gives for Meta.
|
|
82
|
-
*/
|
|
83
|
-
export function tiktokPurchaseEventId(displayId: string | number): string {
|
|
84
|
-
return `tt_purchase_${displayId}`
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function trackTikTokViewContent(data: {
|
|
88
|
-
contentId: string
|
|
89
|
-
contentName?: string
|
|
90
|
-
currency: string
|
|
91
|
-
value: number
|
|
92
|
-
}): void {
|
|
93
|
-
const ttq = safeTtq()
|
|
94
|
-
if (!ttq) return
|
|
95
|
-
ttq.track("ViewContent", {
|
|
96
|
-
content_id: data.contentId,
|
|
97
|
-
content_type: "product",
|
|
98
|
-
content_name: data.contentName,
|
|
99
|
-
currency: data.currency,
|
|
100
|
-
value: data.value,
|
|
101
|
-
})
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export function trackTikTokAddToCart(data: {
|
|
105
|
-
contentId: string
|
|
106
|
-
contentName?: string
|
|
107
|
-
quantity: number
|
|
108
|
-
price: number
|
|
109
|
-
currency: string
|
|
110
|
-
value: number
|
|
111
|
-
}): void {
|
|
112
|
-
const ttq = safeTtq()
|
|
113
|
-
if (!ttq) return
|
|
114
|
-
ttq.track("AddToCart", {
|
|
115
|
-
contents: [
|
|
116
|
-
{
|
|
117
|
-
content_id: data.contentId,
|
|
118
|
-
content_name: data.contentName,
|
|
119
|
-
quantity: data.quantity,
|
|
120
|
-
price: data.price,
|
|
121
|
-
},
|
|
122
|
-
],
|
|
123
|
-
content_type: "product",
|
|
124
|
-
currency: data.currency,
|
|
125
|
-
value: data.value,
|
|
126
|
-
})
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function trackTikTokInitiateCheckout(data: {
|
|
130
|
-
contents: TikTokContentItem[]
|
|
131
|
-
currency: string
|
|
132
|
-
value: number
|
|
133
|
-
}): void {
|
|
134
|
-
const ttq = safeTtq()
|
|
135
|
-
if (!ttq) return
|
|
136
|
-
ttq.track("InitiateCheckout", {
|
|
137
|
-
contents: data.contents,
|
|
138
|
-
content_type: "product",
|
|
139
|
-
currency: data.currency,
|
|
140
|
-
value: data.value,
|
|
141
|
-
})
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export function trackTikTokPurchase(data: {
|
|
145
|
-
contents: TikTokContentItem[]
|
|
146
|
-
currency: string
|
|
147
|
-
value: number
|
|
148
|
-
/** The order's display id — the dedup key and the order_id TikTok shows. */
|
|
149
|
-
displayId: string | number
|
|
150
|
-
customerType?: "new" | "returning"
|
|
151
|
-
}): void {
|
|
152
|
-
const ttq = safeTtq()
|
|
153
|
-
if (!ttq) return
|
|
154
|
-
ttq.track(
|
|
155
|
-
"Purchase",
|
|
156
|
-
{
|
|
157
|
-
contents: data.contents,
|
|
158
|
-
content_type: "product",
|
|
159
|
-
currency: data.currency,
|
|
160
|
-
value: data.value,
|
|
161
|
-
order_id: String(data.displayId),
|
|
162
|
-
...(data.customerType ? { customer_type: data.customerType } : {}),
|
|
163
|
-
},
|
|
164
|
-
{ event_id: tiktokPurchaseEventId(data.displayId) }
|
|
165
|
-
)
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Push a live consent decision to the pixel.
|
|
170
|
-
*
|
|
171
|
-
* Normally you do NOT call this: `applyConsent()` in ./consent.ts already
|
|
172
|
-
* relays every decision to gtag, fbq and ttq together. It is exported for
|
|
173
|
-
* a host app driving the pixel from a consent layer of its own.
|
|
174
|
-
*/
|
|
175
|
-
export function applyTikTokConsent(adsGranted: boolean): void {
|
|
176
|
-
const ttq = safeTtq()
|
|
177
|
-
if (!ttq) return
|
|
178
|
-
if (adsGranted) ttq.grantConsent()
|
|
179
|
-
else ttq.revokeConsent()
|
|
180
|
-
}
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Typed wrappers around the global `ttq` from the TikTok Pixel — the
|
|
5
|
+
* TikTok twin of ./fbq.ts, and deliberately the same shape so a surface
|
|
6
|
+
* that fires one can fire the other on the line below.
|
|
7
|
+
*
|
|
8
|
+
* Every helper no-ops when `window.ttq` is absent (pixel not configured,
|
|
9
|
+
* server render, script blocked), so callers need no guards.
|
|
10
|
+
*
|
|
11
|
+
* Event names are case-sensitive and taken from TikTok's supported pixel
|
|
12
|
+
* events table:
|
|
13
|
+
* https://business-api.tiktok.com/portal/docs/supported-pixel-events/v1.3
|
|
14
|
+
* `Purchase` is the current name; `CompletePayment` and `PlaceAnOrder` do
|
|
15
|
+
* not appear in the current tables at all.
|
|
16
|
+
*
|
|
17
|
+
* `event_id` is the THIRD argument, a separate object after the
|
|
18
|
+
* properties, per TikTok's own implementation guide:
|
|
19
|
+
*
|
|
20
|
+
* ttq.track('Purchase', { ...properties }, { event_id: 'tt_purchase_1044' })
|
|
21
|
+
*
|
|
22
|
+
* TikTok deduplicates on event_source_id + event + event_id and discards
|
|
23
|
+
* duplicates for 48 hours from the first event, which is how the browser
|
|
24
|
+
* Purchase and the server-side Events API Purchase collapse into one.
|
|
25
|
+
*
|
|
26
|
+
* `content_id` must be the SAME identifier the product catalogue uses,
|
|
27
|
+
* which on Cartbase is the PRODUCT id — the value the feed emits as
|
|
28
|
+
* <g:id> and the value Meta already receives. A variant id here matches
|
|
29
|
+
* no catalogue entry at all.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/** One product line inside `contents`. */
|
|
33
|
+
export type TikTokContentItem = {
|
|
34
|
+
content_id: string
|
|
35
|
+
content_name?: string
|
|
36
|
+
content_category?: string
|
|
37
|
+
brand?: string
|
|
38
|
+
quantity?: number
|
|
39
|
+
price?: number
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type TikTokProperties = {
|
|
43
|
+
contents?: TikTokContentItem[]
|
|
44
|
+
content_id?: string
|
|
45
|
+
content_name?: string
|
|
46
|
+
content_type?: "product" | "product_group"
|
|
47
|
+
currency?: string
|
|
48
|
+
value?: number
|
|
49
|
+
quantity?: number
|
|
50
|
+
description?: string
|
|
51
|
+
order_id?: string
|
|
52
|
+
/** TikTok's new-versus-returning signal. Same computed fact the backend
|
|
53
|
+
* persists on the order and sends to Google Ads as `new_customer`. */
|
|
54
|
+
customer_type?: "new" | "returning"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
type TtqFn = {
|
|
58
|
+
track: (
|
|
59
|
+
event: string,
|
|
60
|
+
properties?: TikTokProperties,
|
|
61
|
+
options?: { event_id?: string }
|
|
62
|
+
) => void
|
|
63
|
+
page: () => void
|
|
64
|
+
identify: (data: Record<string, unknown>) => void
|
|
65
|
+
grantConsent: () => void
|
|
66
|
+
revokeConsent: () => void
|
|
67
|
+
holdConsent: () => void
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function safeTtq(): TtqFn | null {
|
|
71
|
+
if (typeof window === "undefined") return null
|
|
72
|
+
const ttq = (window as unknown as { ttq?: Partial<TtqFn> }).ttq
|
|
73
|
+
if (!ttq || typeof ttq.track !== "function") return null
|
|
74
|
+
return ttq as TtqFn
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* THE purchase dedup key — must equal what the backend forwarder sends
|
|
79
|
+
* (`tiktokPurchaseEventId` in src/lib/tracking/constants.ts). Built from
|
|
80
|
+
* display_id here so the format cannot drift between the two sides, the
|
|
81
|
+
* same guarantee `trackPurchase` gives for Meta.
|
|
82
|
+
*/
|
|
83
|
+
export function tiktokPurchaseEventId(displayId: string | number): string {
|
|
84
|
+
return `tt_purchase_${displayId}`
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function trackTikTokViewContent(data: {
|
|
88
|
+
contentId: string
|
|
89
|
+
contentName?: string
|
|
90
|
+
currency: string
|
|
91
|
+
value: number
|
|
92
|
+
}): void {
|
|
93
|
+
const ttq = safeTtq()
|
|
94
|
+
if (!ttq) return
|
|
95
|
+
ttq.track("ViewContent", {
|
|
96
|
+
content_id: data.contentId,
|
|
97
|
+
content_type: "product",
|
|
98
|
+
content_name: data.contentName,
|
|
99
|
+
currency: data.currency,
|
|
100
|
+
value: data.value,
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function trackTikTokAddToCart(data: {
|
|
105
|
+
contentId: string
|
|
106
|
+
contentName?: string
|
|
107
|
+
quantity: number
|
|
108
|
+
price: number
|
|
109
|
+
currency: string
|
|
110
|
+
value: number
|
|
111
|
+
}): void {
|
|
112
|
+
const ttq = safeTtq()
|
|
113
|
+
if (!ttq) return
|
|
114
|
+
ttq.track("AddToCart", {
|
|
115
|
+
contents: [
|
|
116
|
+
{
|
|
117
|
+
content_id: data.contentId,
|
|
118
|
+
content_name: data.contentName,
|
|
119
|
+
quantity: data.quantity,
|
|
120
|
+
price: data.price,
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
content_type: "product",
|
|
124
|
+
currency: data.currency,
|
|
125
|
+
value: data.value,
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function trackTikTokInitiateCheckout(data: {
|
|
130
|
+
contents: TikTokContentItem[]
|
|
131
|
+
currency: string
|
|
132
|
+
value: number
|
|
133
|
+
}): void {
|
|
134
|
+
const ttq = safeTtq()
|
|
135
|
+
if (!ttq) return
|
|
136
|
+
ttq.track("InitiateCheckout", {
|
|
137
|
+
contents: data.contents,
|
|
138
|
+
content_type: "product",
|
|
139
|
+
currency: data.currency,
|
|
140
|
+
value: data.value,
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function trackTikTokPurchase(data: {
|
|
145
|
+
contents: TikTokContentItem[]
|
|
146
|
+
currency: string
|
|
147
|
+
value: number
|
|
148
|
+
/** The order's display id — the dedup key and the order_id TikTok shows. */
|
|
149
|
+
displayId: string | number
|
|
150
|
+
customerType?: "new" | "returning"
|
|
151
|
+
}): void {
|
|
152
|
+
const ttq = safeTtq()
|
|
153
|
+
if (!ttq) return
|
|
154
|
+
ttq.track(
|
|
155
|
+
"Purchase",
|
|
156
|
+
{
|
|
157
|
+
contents: data.contents,
|
|
158
|
+
content_type: "product",
|
|
159
|
+
currency: data.currency,
|
|
160
|
+
value: data.value,
|
|
161
|
+
order_id: String(data.displayId),
|
|
162
|
+
...(data.customerType ? { customer_type: data.customerType } : {}),
|
|
163
|
+
},
|
|
164
|
+
{ event_id: tiktokPurchaseEventId(data.displayId) }
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Push a live consent decision to the pixel.
|
|
170
|
+
*
|
|
171
|
+
* Normally you do NOT call this: `applyConsent()` in ./consent.ts already
|
|
172
|
+
* relays every decision to gtag, fbq and ttq together. It is exported for
|
|
173
|
+
* a host app driving the pixel from a consent layer of its own.
|
|
174
|
+
*/
|
|
175
|
+
export function applyTikTokConsent(adsGranted: boolean): void {
|
|
176
|
+
const ttq = safeTtq()
|
|
177
|
+
if (!ttq) return
|
|
178
|
+
if (adsGranted) ttq.grantConsent()
|
|
179
|
+
else ttq.revokeConsent()
|
|
180
|
+
}
|
|
@@ -1,54 +1,54 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import { useEffect, useState } from "react"
|
|
4
|
-
|
|
5
|
-
import type { StorefrontClient } from "../api/http"
|
|
6
|
-
import { getTrackingConfig } from "./get-tracking-config"
|
|
7
|
-
import type { TrackingConfig } from "./types"
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* The store's public tag config, from a client component.
|
|
11
|
-
*
|
|
12
|
-
* `<StorefrontTags>` reads the same config on the SERVER, which is right
|
|
13
|
-
* for mounting tags in a layout. This hook exists for the one place that
|
|
14
|
-
* genuinely cannot: a client page that needs the config to decide what to
|
|
15
|
-
* send, the order confirmation being the case that matters, because
|
|
16
|
-
* Google Ads needs the account id and the purchase label to build a valid
|
|
17
|
-
* `send_to`.
|
|
18
|
-
*
|
|
19
|
-
* Cached per browser session in module scope: the config changes only
|
|
20
|
-
* when a merchant edits Settings → Integrations, and re-fetching it on
|
|
21
|
-
* every mount would put a request in front of the purchase event.
|
|
22
|
-
*
|
|
23
|
-
* Returns undefined until the fetch resolves, and `{}` if it fails, so a
|
|
24
|
-
* caller renders and fires the vendors that need no config either way.
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
let cached: TrackingConfig | undefined
|
|
28
|
-
let inFlight: Promise<TrackingConfig> | undefined
|
|
29
|
-
|
|
30
|
-
export function useTrackingConfig(
|
|
31
|
-
client: StorefrontClient
|
|
32
|
-
): TrackingConfig | undefined {
|
|
33
|
-
const [config, setConfig] = useState<TrackingConfig | undefined>(cached)
|
|
34
|
-
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
if (cached) return
|
|
37
|
-
let active = true
|
|
38
|
-
inFlight =
|
|
39
|
-
inFlight ??
|
|
40
|
-
getTrackingConfig(client).then((value) => {
|
|
41
|
-
cached = value
|
|
42
|
-
inFlight = undefined
|
|
43
|
-
return value
|
|
44
|
-
})
|
|
45
|
-
inFlight.then((value) => {
|
|
46
|
-
if (active) setConfig(value)
|
|
47
|
-
})
|
|
48
|
-
return () => {
|
|
49
|
-
active = false
|
|
50
|
-
}
|
|
51
|
-
}, [client])
|
|
52
|
-
|
|
53
|
-
return config
|
|
54
|
-
}
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react"
|
|
4
|
+
|
|
5
|
+
import type { StorefrontClient } from "../api/http"
|
|
6
|
+
import { getTrackingConfig } from "./get-tracking-config"
|
|
7
|
+
import type { TrackingConfig } from "./types"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The store's public tag config, from a client component.
|
|
11
|
+
*
|
|
12
|
+
* `<StorefrontTags>` reads the same config on the SERVER, which is right
|
|
13
|
+
* for mounting tags in a layout. This hook exists for the one place that
|
|
14
|
+
* genuinely cannot: a client page that needs the config to decide what to
|
|
15
|
+
* send, the order confirmation being the case that matters, because
|
|
16
|
+
* Google Ads needs the account id and the purchase label to build a valid
|
|
17
|
+
* `send_to`.
|
|
18
|
+
*
|
|
19
|
+
* Cached per browser session in module scope: the config changes only
|
|
20
|
+
* when a merchant edits Settings → Integrations, and re-fetching it on
|
|
21
|
+
* every mount would put a request in front of the purchase event.
|
|
22
|
+
*
|
|
23
|
+
* Returns undefined until the fetch resolves, and `{}` if it fails, so a
|
|
24
|
+
* caller renders and fires the vendors that need no config either way.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
let cached: TrackingConfig | undefined
|
|
28
|
+
let inFlight: Promise<TrackingConfig> | undefined
|
|
29
|
+
|
|
30
|
+
export function useTrackingConfig(
|
|
31
|
+
client: StorefrontClient
|
|
32
|
+
): TrackingConfig | undefined {
|
|
33
|
+
const [config, setConfig] = useState<TrackingConfig | undefined>(cached)
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (cached) return
|
|
37
|
+
let active = true
|
|
38
|
+
inFlight =
|
|
39
|
+
inFlight ??
|
|
40
|
+
getTrackingConfig(client).then((value) => {
|
|
41
|
+
cached = value
|
|
42
|
+
inFlight = undefined
|
|
43
|
+
return value
|
|
44
|
+
})
|
|
45
|
+
inFlight.then((value) => {
|
|
46
|
+
if (active) setConfig(value)
|
|
47
|
+
})
|
|
48
|
+
return () => {
|
|
49
|
+
active = false
|
|
50
|
+
}
|
|
51
|
+
}, [client])
|
|
52
|
+
|
|
53
|
+
return config
|
|
54
|
+
}
|
package/theme/index.css
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Cartbase storefront theme — the one line a storefront imports.
|
|
3
|
-
*
|
|
4
|
-
* @import "tailwindcss";
|
|
5
|
-
* @import "@cartbase/storefront/theme";
|
|
6
|
-
*
|
|
7
|
-
* That is the whole setup. There is no Tailwind config file, no preset to
|
|
8
|
-
* register, and no list of token names to copy: Tailwind 4 reads the theme
|
|
9
|
-
* from CSS, and this ships filled, so a fresh install renders as a designed
|
|
10
|
-
* store rather than grey boxes.
|
|
11
|
-
*
|
|
12
|
-
* To retheme, override the values from tokens.css in your own stylesheet
|
|
13
|
-
* AFTER this import. Never edit the vocabulary — components reference those
|
|
14
|
-
* names, and changing one moves every storefront at once.
|
|
15
|
-
*/
|
|
16
|
-
@import "./tokens.css";
|
|
17
|
-
@import "./theme.css";
|
|
18
|
-
|
|
19
|
-
/* The page's own ground. Without this the body inherits the browser default
|
|
20
|
-
and every surface token above renders against white regardless of theme. */
|
|
21
|
-
body {
|
|
22
|
-
background-color: var(--background);
|
|
23
|
-
color: var(--foreground);
|
|
24
|
-
font-family: var(--font-body-family);
|
|
25
|
-
}
|
|
1
|
+
/*
|
|
2
|
+
* Cartbase storefront theme — the one line a storefront imports.
|
|
3
|
+
*
|
|
4
|
+
* @import "tailwindcss";
|
|
5
|
+
* @import "@cartbase/storefront/theme";
|
|
6
|
+
*
|
|
7
|
+
* That is the whole setup. There is no Tailwind config file, no preset to
|
|
8
|
+
* register, and no list of token names to copy: Tailwind 4 reads the theme
|
|
9
|
+
* from CSS, and this ships filled, so a fresh install renders as a designed
|
|
10
|
+
* store rather than grey boxes.
|
|
11
|
+
*
|
|
12
|
+
* To retheme, override the values from tokens.css in your own stylesheet
|
|
13
|
+
* AFTER this import. Never edit the vocabulary — components reference those
|
|
14
|
+
* names, and changing one moves every storefront at once.
|
|
15
|
+
*/
|
|
16
|
+
@import "./tokens.css";
|
|
17
|
+
@import "./theme.css";
|
|
18
|
+
|
|
19
|
+
/* The page's own ground. Without this the body inherits the browser default
|
|
20
|
+
and every surface token above renders against white regardless of theme. */
|
|
21
|
+
body {
|
|
22
|
+
background-color: var(--background);
|
|
23
|
+
color: var(--foreground);
|
|
24
|
+
font-family: var(--font-body-family);
|
|
25
|
+
}
|