@cartbase/storefront 0.7.0 → 0.9.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/README.md +4 -5
- package/package.json +248 -243
- package/src/api/carts.ts +16 -1
- package/src/api/http.ts +17 -9
- package/src/api/index.ts +1 -0
- package/src/api/store.ts +35 -0
- package/src/cart-drawer/cross-sell-carousel.tsx +2 -3
- package/src/cart-drawer/gift-wrap.tsx +82 -83
- package/src/cart-drawer/item/index.tsx +4 -6
- package/src/cart-drawer/sticky-footer.tsx +73 -73
- package/src/checkout/geocode.ts +1 -1
- package/src/checkout/gift-card-section.tsx +2 -2
- package/src/checkout/line-item-card.tsx +3 -3
- package/src/checkout/order-summary.tsx +11 -12
- package/src/checkout/payment-button.tsx +2 -3
- package/src/checkout/shipping-method-list.tsx +2 -2
- package/src/lib/cookie-names.ts +45 -0
- package/src/lib/money.ts +1 -1
- package/src/lib/platform.ts +13 -0
- package/src/lib/price.tsx +39 -0
- package/src/lib/visitor.ts +74 -0
- package/src/order/order-delivery-card.tsx +2 -2
- package/src/order/order-item.tsx +3 -3
- package/src/order/order-totals.tsx +4 -4
- package/src/tracking/attribution.ts +134 -105
- package/src/tracking/chatgpt-pixel.tsx +92 -0
- package/src/tracking/consent.ts +12 -0
- package/src/tracking/events.ts +56 -2
- package/src/tracking/get-tracking-attribution.ts +16 -3
- package/src/tracking/index.ts +23 -0
- package/src/tracking/live-heartbeat.tsx +63 -0
- package/src/tracking/oaiq.ts +206 -0
- package/src/tracking/page-views.tsx +96 -0
- package/src/tracking/storefront-tags.tsx +77 -67
- package/src/tracking/types.ts +4 -0
- package/src/lib/dual-price.tsx +0 -73
|
@@ -1,67 +1,77 @@
|
|
|
1
|
-
import { GoogleTag } from "./ga4"
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
config
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
1
|
+
import { GoogleTag } from "./ga4"
|
|
2
|
+
import { LiveHeartbeat } from "./live-heartbeat"
|
|
3
|
+
import { PageViews } from "./page-views"
|
|
4
|
+
import { Gtm } from "./gtm"
|
|
5
|
+
import { MetaPixel } from "./meta-pixel"
|
|
6
|
+
import { TikTokPixel } from "./tiktok-pixel"
|
|
7
|
+
import { ChatGptPixel } from "./chatgpt-pixel"
|
|
8
|
+
import { getTrackingConfig } from "./get-tracking-config"
|
|
9
|
+
import type { StorefrontClient } from "../api/http"
|
|
10
|
+
import type { TrackingConfig } from "./types"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* StorefrontTags — every marketing tag the store has configured, mounted
|
|
14
|
+
* from the store's own config.
|
|
15
|
+
*
|
|
16
|
+
* THE POINT: a merchant who saves their pixel ids in the admin gets those
|
|
17
|
+
* tags on their storefront, with nothing else to do and no code to write.
|
|
18
|
+
* Before this existed the package EXPORTED tags and no storefront mounted
|
|
19
|
+
* them, so the admin could serve a Meta pixel id, a GA4 id and a Google
|
|
20
|
+
* Ads conversion id that reached the browser and did nothing. Adding a
|
|
21
|
+
* vendor is one line HERE now, and every store built on the package gains
|
|
22
|
+
* it at the next deploy.
|
|
23
|
+
*
|
|
24
|
+
* Consent: nothing extra to wire. <ConsentInit> sets the Consent Mode v2
|
|
25
|
+
* defaults synchronously ahead of these, the Meta and TikTok snippets read
|
|
26
|
+
* the shared consent cookie before they are allowed to write cookies, and
|
|
27
|
+
* `applyConsent()` relays a live decision to all three vendors. Mount this
|
|
28
|
+
* AFTER <ConsentInit> and the gate holds.
|
|
29
|
+
*
|
|
30
|
+
* Google is deliberately ONE tag with two destinations rather than two
|
|
31
|
+
* loaders — see the comment block in ./ga4.tsx. It is also why the Ads
|
|
32
|
+
* conversion inherits the consent gate for free.
|
|
33
|
+
*
|
|
34
|
+
* Server component: pass a `client` and it fetches the config (cached),
|
|
35
|
+
* or pass an already-fetched `config` when the layout has one in hand.
|
|
36
|
+
* Renders nothing for a vendor the store has not configured, so a store
|
|
37
|
+
* running only GA4 ships exactly one tag.
|
|
38
|
+
*/
|
|
39
|
+
export async function StorefrontTags({
|
|
40
|
+
client,
|
|
41
|
+
config,
|
|
42
|
+
}: {
|
|
43
|
+
/** Used to fetch the tracking config when `config` is not supplied. */
|
|
44
|
+
client?: StorefrontClient
|
|
45
|
+
/** Pre-fetched tracking block, e.g. from a layout that already has it. */
|
|
46
|
+
config?: TrackingConfig
|
|
47
|
+
}) {
|
|
48
|
+
let tracking: TrackingConfig | undefined = config
|
|
49
|
+
if (!tracking) {
|
|
50
|
+
if (!client) return null
|
|
51
|
+
try {
|
|
52
|
+
tracking = await getTrackingConfig(client)
|
|
53
|
+
} catch {
|
|
54
|
+
// A tracking-config failure must never take a storefront down.
|
|
55
|
+
return null
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<>
|
|
61
|
+
{/* Cartbase's own analytics, always on and never a merchant setting:
|
|
62
|
+
it is what feeds the merchant's own dashboard, so it does not
|
|
63
|
+
depend on any vendor id being configured. The vendor tags below
|
|
64
|
+
only appear when the merchant has entered their ids. */}
|
|
65
|
+
<PageViews />
|
|
66
|
+
<LiveHeartbeat />
|
|
67
|
+
<MetaPixel pixelId={tracking.facebookPixel?.pixelId} />
|
|
68
|
+
<TikTokPixel pixelId={tracking.tiktok?.pixelId} />
|
|
69
|
+
<ChatGptPixel pixelId={tracking.chatgptAds?.pixelId} />
|
|
70
|
+
<GoogleTag
|
|
71
|
+
measurementId={tracking.ga4?.measurementId}
|
|
72
|
+
adsConversionId={tracking.googleAds?.conversionId}
|
|
73
|
+
/>
|
|
74
|
+
<Gtm containerId={tracking.gtm?.containerId} />
|
|
75
|
+
</>
|
|
76
|
+
)
|
|
77
|
+
}
|
package/src/tracking/types.ts
CHANGED
|
@@ -44,6 +44,10 @@ export type TrackingConfig = {
|
|
|
44
44
|
* read by the order.placed forwarder, exactly like Meta's CAPI token,
|
|
45
45
|
* which has never traversed this surface. */
|
|
46
46
|
tiktok?: { pixelId: string }
|
|
47
|
+
/** ChatGPT Ads, pixel id ONLY — the Conversions API key is a bearer
|
|
48
|
+
* token and stays on the platform, read by the order.placed forwarder
|
|
49
|
+
* (chatgpt-ads-pixel card). */
|
|
50
|
+
chatgptAds?: { pixelId: string }
|
|
47
51
|
/** True when the store's consent CMP is enabled — client tags must
|
|
48
52
|
* mount through the consent gate (`_1c_consent` / Consent Mode v2). */
|
|
49
53
|
consent_required?: boolean
|
package/src/lib/dual-price.tsx
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
|
|
5
|
-
import { cn } from "./utils"
|
|
6
|
-
import { convertToLocale } from "./money"
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* DualPrice — render a EUR price with BGN next to it.
|
|
10
|
-
*
|
|
11
|
-
* Bulgarian law (Дв, effective through 2026-08) requires showing both EUR
|
|
12
|
-
* and BGN on every price displayed to consumers. Fixed statutory rate:
|
|
13
|
-
* 1 EUR = 1.95583 BGN
|
|
14
|
-
*
|
|
15
|
-
* For non-EUR currencies the BGN leg is skipped and only the primary locale
|
|
16
|
-
* currency is shown. The component is stateless and side-effect-free — safe
|
|
17
|
-
* to render in server components and client components alike.
|
|
18
|
-
*
|
|
19
|
-
* The EUR-to-BGN rate and the law's expiry date are baked in for now. When
|
|
20
|
-
* the law expires (or when a store outside Bulgaria consumes this library),
|
|
21
|
-
* we'll move both to a BrandingContext value so each store can override.
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* <DualPrice amount={19.99} currencyCode="eur" />
|
|
25
|
-
* // → "€19.99 39,10 лв."
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
const EUR_TO_BGN = 1.95583
|
|
29
|
-
|
|
30
|
-
function toBGN(eurAmount: number): string {
|
|
31
|
-
const bgn = eurAmount * EUR_TO_BGN
|
|
32
|
-
return bgn.toFixed(2).replace(".", ",") + " лв."
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export type DualPriceProps = {
|
|
36
|
-
/** Amount in major units of the primary currency (e.g. 19.99 for €19.99) */
|
|
37
|
-
amount: number
|
|
38
|
-
/** ISO currency code (case-insensitive). Only "eur" triggers the BGN leg. */
|
|
39
|
-
currencyCode: string
|
|
40
|
-
/** Optional class for the outer span */
|
|
41
|
-
className?: string
|
|
42
|
-
/** Optional class for the BGN leg (defaults to small muted) */
|
|
43
|
-
bgnClassName?: string
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function DualPrice({
|
|
47
|
-
amount,
|
|
48
|
-
currencyCode,
|
|
49
|
-
className,
|
|
50
|
-
bgnClassName,
|
|
51
|
-
}: DualPriceProps): React.ReactElement {
|
|
52
|
-
const isEur = currencyCode?.toLowerCase() === "eur"
|
|
53
|
-
|
|
54
|
-
if (!isEur) {
|
|
55
|
-
return (
|
|
56
|
-
<span className={className}>
|
|
57
|
-
{convertToLocale({ amount, currency_code: currencyCode })}
|
|
58
|
-
</span>
|
|
59
|
-
)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return (
|
|
63
|
-
<span className={className}>
|
|
64
|
-
{convertToLocale({ amount, currency_code: currencyCode })}
|
|
65
|
-
<span className={cn("ml-1.5 text-muted-foreground/70 font-normal", bgnClassName)}>
|
|
66
|
-
{toBGN(amount)}
|
|
67
|
-
</span>
|
|
68
|
-
</span>
|
|
69
|
-
)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** Exposed for advanced callers that need the rate directly. */
|
|
73
|
-
export const EUR_TO_BGN_RATE = EUR_TO_BGN
|