@cartbase/storefront 0.16.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 (110) hide show
  1. package/LICENSE +21 -21
  2. package/package.json +258 -255
  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 -130
  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/free-gift.tsx +1 -1
  28. package/src/cart-drawer/gift-wrap.tsx +82 -82
  29. package/src/cart-drawer/index.ts +71 -68
  30. package/src/cart-drawer/item/index.tsx +162 -162
  31. package/src/cart-drawer/item/quantity.tsx +8 -14
  32. package/src/cart-drawer/item/upsell.tsx +110 -110
  33. package/src/cart-drawer/labels.ts +123 -123
  34. package/src/cart-drawer/mutation-queue.ts +78 -0
  35. package/src/cart-drawer/notes.tsx +131 -131
  36. package/src/cart-drawer/sticky-footer.tsx +73 -73
  37. package/src/cart-drawer/summary-breakdown.tsx +197 -197
  38. package/src/checkout/address-error-copy.ts +117 -117
  39. package/src/checkout/boxnow-locker-selector.tsx +410 -410
  40. package/src/checkout/compare-addresses.ts +40 -40
  41. package/src/checkout/discount-section.tsx +218 -218
  42. package/src/checkout/error-copy-codes.ts +63 -63
  43. package/src/checkout/geocode.ts +154 -154
  44. package/src/checkout/gift-card-section.tsx +224 -224
  45. package/src/checkout/index.ts +72 -72
  46. package/src/checkout/labels.ts +495 -495
  47. package/src/checkout/payment-button.tsx +372 -372
  48. package/src/checkout/payment-error-copy.ts +154 -154
  49. package/src/checkout/promotion-error-copy.ts +91 -91
  50. package/src/checkout/use-checkout-orchestration.ts +1592 -1592
  51. package/src/common/country-flag.tsx +52 -52
  52. package/src/common/country-select.tsx +11 -11
  53. package/src/common/index.ts +20 -20
  54. package/src/common/market-select.tsx +57 -57
  55. package/src/index.ts +12 -12
  56. package/src/lib/country-name.ts +59 -59
  57. package/src/lib/get-product-price.ts +133 -133
  58. package/src/lib/media-image.tsx +39 -0
  59. package/src/lib/payment-constants.ts +53 -53
  60. package/src/lib/platform.ts +13 -13
  61. package/src/lib/price.tsx +39 -39
  62. package/src/lib/store-api-error.ts +36 -36
  63. package/src/lib/variant-caption.ts +32 -32
  64. package/src/locales/bg.ts +467 -467
  65. package/src/locales/context.ts +37 -37
  66. package/src/locales/en.ts +26 -26
  67. package/src/locales/es.ts +466 -466
  68. package/src/locales/index.ts +19 -19
  69. package/src/locales/provider.tsx +59 -59
  70. package/src/locales/types.ts +77 -77
  71. package/src/order/index.ts +62 -62
  72. package/src/order/labels.ts +79 -79
  73. package/src/order/order-item.tsx +1 -1
  74. package/src/order/order-totals.tsx +250 -250
  75. package/src/primitives/select-field.tsx +93 -93
  76. package/src/products/image-gallery.tsx +43 -43
  77. package/src/products/index.ts +68 -59
  78. package/src/products/option-select.tsx +67 -67
  79. package/src/products/product-actions-wrapper.tsx +63 -62
  80. package/src/products/product-actions.tsx +151 -150
  81. package/src/products/product-price.tsx +69 -69
  82. package/src/products/product-promises.tsx +61 -61
  83. package/src/products/product-specs.tsx +65 -65
  84. package/src/products/product-tabs.tsx +123 -123
  85. package/src/products/product-template.tsx +163 -162
  86. package/src/products/purchase-options.tsx +130 -130
  87. package/src/products/sets.ts +110 -0
  88. package/src/products/thumbnail.tsx +1 -1
  89. package/src/products/use-product-actions.ts +199 -164
  90. package/src/products/variant-matching.ts +71 -71
  91. package/src/products/variant-url.ts +74 -74
  92. package/src/reviews-ui/index.ts +73 -73
  93. package/src/store/category-template.tsx +136 -136
  94. package/src/store/index.ts +40 -40
  95. package/src/tracking/chatgpt-pixel.tsx +99 -99
  96. package/src/tracking/consent-init.tsx +62 -62
  97. package/src/tracking/events.ts +348 -348
  98. package/src/tracking/ga4.tsx +93 -93
  99. package/src/tracking/google-ads.ts +84 -84
  100. package/src/tracking/gtm.tsx +60 -60
  101. package/src/tracking/inline-script.ts +49 -49
  102. package/src/tracking/oaiq.ts +206 -206
  103. package/src/tracking/tiktok-pixel.tsx +91 -91
  104. package/src/tracking/track-init.tsx +56 -56
  105. package/src/tracking/track-order-purchase.tsx +122 -122
  106. package/src/tracking/ttq.ts +180 -180
  107. package/src/tracking/use-tracking-config.ts +54 -54
  108. package/theme/index.css +25 -25
  109. package/theme/theme.css +150 -150
  110. package/theme/tokens.css +106 -106
@@ -1,99 +1,99 @@
1
- "use client"
2
-
3
- import Script from "next/script"
4
-
5
- import { adsConsentSnippet } from "./consent"
6
- import { jsStringLiteral } from "./inline-script"
7
-
8
- /**
9
- * ChatGptPixel — the OpenAI measurement pixel, the ChatGPT Ads twin of
10
- * <MetaPixel> and <TikTokPixel>.
11
- *
12
- * The loader is OpenAI's own, verbatim from
13
- * https://developers.openai.com/ads/measurement-pixel: a command queue on
14
- * `window.oaiq` that buffers calls until the SDK arrives from
15
- * bzrcdn.openai.com, injected with `<Script strategy="afterInteractive">`.
16
- *
17
- * CONSENT IS CLOSED BEFORE INIT, and that order is the whole point. Their
18
- * consent default is TRUE, so an unconsented visitor is measured unless we
19
- * say otherwise first:
20
- *
21
- * oaiq("consent", false) // queued BEFORE init — no pings
22
- * oaiq("init", { pixelId })
23
- * oaiq("consent", true) // only once the visitor accepted
24
- *
25
- * The script is LOADED either way, like the Meta and TikTok gates beside
26
- * it, so a later grant costs no round trip. But the page view is fired
27
- * only AFTER a grant, and that is deliberate rather than cautious: their
28
- * docs state that with consent false the pixel sends no measurement
29
- * pings, and they do NOT document whether a call made while consent was
30
- * false is released on a later grant. TikTok documents exactly that
31
- * (`holdConsent` queues, `grantConsent` flushes) and our TikTok gate
32
- * relies on it. Nothing here relies on behaviour OpenAI has not written
33
- * down: an unconsented page view is not sent, and not counted on later.
34
- *
35
- * The decision is the store's default overridden by the visitor's stored
36
- * choice (`adsConsentSnippet`), resolved inside the snippet before anything
37
- * can be sent: a store that collects consent starts closed, a store with
38
- * no consent gate starts open, the cookie winning either way. Live changes
39
- * arrive through `applyConsent()` in ./consent.ts — there is no cookie
40
- * watcher. `consentRequired` is `tracking.consent_required`, passed by
41
- * <StorefrontTags>, and required for the reason <ConsentInit> gives.
42
- *
43
- * The page view is ours to fire at all: their pixel sends nothing at init,
44
- * unlike Meta's and TikTok's base code.
45
- *
46
- * Their SDK also captures `oppref` off the landing URL into a first-party
47
- * `__oppref` cookie by itself. Our attribution module captures the same
48
- * parameter into `_1c_oppref` independently, so the server-side
49
- * Conversions API event can carry the click even on a visit where this
50
- * script never loaded at all.
51
- *
52
- * Renders nothing when `pixelId` is falsy, so a layout can mount it
53
- * unconditionally.
54
- */
55
- export function ChatGptPixel({
56
- pixelId,
57
- consentRequired,
58
- }: {
59
- pixelId?: string
60
- /** `tracking.consent_required`: does this store collect consent before it tracks? */
61
- consentRequired: boolean
62
- }) {
63
- if (!pixelId) return null
64
-
65
- const id = jsStringLiteral(pixelId)
66
-
67
- const initSnippet = `
68
- (function (w, d, s, u) {
69
- if (w.oaiq) return;
70
- var q = function () { q.q.push(arguments); };
71
- q.q = [];
72
- w.oaiq = q;
73
- var js = d.createElement(s);
74
- js.async = true;
75
- js.src = u;
76
- var f = d.getElementsByTagName(s)[0];
77
- f.parentNode.insertBefore(js, f);
78
- })(window, document, "script", "https://bzrcdn.openai.com/sdk/oaiq.min.js");
79
-
80
- oaiq("consent", false);
81
- oaiq("init", { pixelId: ${id} });
82
-
83
- (function (d) {
84
- ${adsConsentSnippet(consentRequired, "d")}
85
- if (ads) {
86
- oaiq("consent", true);
87
- oaiq("measure", "page_viewed", { type: "contents" });
88
- }
89
- })(document);
90
- `.trim()
91
-
92
- return (
93
- <Script
94
- id="chatgpt-pixel-init"
95
- strategy="afterInteractive"
96
- dangerouslySetInnerHTML={{ __html: initSnippet }}
97
- />
98
- )
99
- }
1
+ "use client"
2
+
3
+ import Script from "next/script"
4
+
5
+ import { adsConsentSnippet } from "./consent"
6
+ import { jsStringLiteral } from "./inline-script"
7
+
8
+ /**
9
+ * ChatGptPixel — the OpenAI measurement pixel, the ChatGPT Ads twin of
10
+ * <MetaPixel> and <TikTokPixel>.
11
+ *
12
+ * The loader is OpenAI's own, verbatim from
13
+ * https://developers.openai.com/ads/measurement-pixel: a command queue on
14
+ * `window.oaiq` that buffers calls until the SDK arrives from
15
+ * bzrcdn.openai.com, injected with `<Script strategy="afterInteractive">`.
16
+ *
17
+ * CONSENT IS CLOSED BEFORE INIT, and that order is the whole point. Their
18
+ * consent default is TRUE, so an unconsented visitor is measured unless we
19
+ * say otherwise first:
20
+ *
21
+ * oaiq("consent", false) // queued BEFORE init — no pings
22
+ * oaiq("init", { pixelId })
23
+ * oaiq("consent", true) // only once the visitor accepted
24
+ *
25
+ * The script is LOADED either way, like the Meta and TikTok gates beside
26
+ * it, so a later grant costs no round trip. But the page view is fired
27
+ * only AFTER a grant, and that is deliberate rather than cautious: their
28
+ * docs state that with consent false the pixel sends no measurement
29
+ * pings, and they do NOT document whether a call made while consent was
30
+ * false is released on a later grant. TikTok documents exactly that
31
+ * (`holdConsent` queues, `grantConsent` flushes) and our TikTok gate
32
+ * relies on it. Nothing here relies on behaviour OpenAI has not written
33
+ * down: an unconsented page view is not sent, and not counted on later.
34
+ *
35
+ * The decision is the store's default overridden by the visitor's stored
36
+ * choice (`adsConsentSnippet`), resolved inside the snippet before anything
37
+ * can be sent: a store that collects consent starts closed, a store with
38
+ * no consent gate starts open, the cookie winning either way. Live changes
39
+ * arrive through `applyConsent()` in ./consent.ts — there is no cookie
40
+ * watcher. `consentRequired` is `tracking.consent_required`, passed by
41
+ * <StorefrontTags>, and required for the reason <ConsentInit> gives.
42
+ *
43
+ * The page view is ours to fire at all: their pixel sends nothing at init,
44
+ * unlike Meta's and TikTok's base code.
45
+ *
46
+ * Their SDK also captures `oppref` off the landing URL into a first-party
47
+ * `__oppref` cookie by itself. Our attribution module captures the same
48
+ * parameter into `_1c_oppref` independently, so the server-side
49
+ * Conversions API event can carry the click even on a visit where this
50
+ * script never loaded at all.
51
+ *
52
+ * Renders nothing when `pixelId` is falsy, so a layout can mount it
53
+ * unconditionally.
54
+ */
55
+ export function ChatGptPixel({
56
+ pixelId,
57
+ consentRequired,
58
+ }: {
59
+ pixelId?: string
60
+ /** `tracking.consent_required`: does this store collect consent before it tracks? */
61
+ consentRequired: boolean
62
+ }) {
63
+ if (!pixelId) return null
64
+
65
+ const id = jsStringLiteral(pixelId)
66
+
67
+ const initSnippet = `
68
+ (function (w, d, s, u) {
69
+ if (w.oaiq) return;
70
+ var q = function () { q.q.push(arguments); };
71
+ q.q = [];
72
+ w.oaiq = q;
73
+ var js = d.createElement(s);
74
+ js.async = true;
75
+ js.src = u;
76
+ var f = d.getElementsByTagName(s)[0];
77
+ f.parentNode.insertBefore(js, f);
78
+ })(window, document, "script", "https://bzrcdn.openai.com/sdk/oaiq.min.js");
79
+
80
+ oaiq("consent", false);
81
+ oaiq("init", { pixelId: ${id} });
82
+
83
+ (function (d) {
84
+ ${adsConsentSnippet(consentRequired, "d")}
85
+ if (ads) {
86
+ oaiq("consent", true);
87
+ oaiq("measure", "page_viewed", { type: "contents" });
88
+ }
89
+ })(document);
90
+ `.trim()
91
+
92
+ return (
93
+ <Script
94
+ id="chatgpt-pixel-init"
95
+ strategy="afterInteractive"
96
+ dangerouslySetInnerHTML={{ __html: initSnippet }}
97
+ />
98
+ )
99
+ }
@@ -1,62 +1,62 @@
1
- import { consentInitSnippet } from "./consent"
2
-
3
- /**
4
- * ConsentInit — the synchronous Consent Mode v2 "default" snippet.
5
- *
6
- * Ported from the Cartbase reference implementation
7
- * `src/components/storefront/consent/consent-init.tsx` (itself from
8
- * `@1click/ui/src/tracking/consent-init.tsx` v2.3.1); the snippet lives in
9
- * ./consent.ts (`consentInitSnippet`) so its ordering contract is
10
- * unit-testable.
11
- *
12
- * Render it as the FIRST child of <body> (App Router layouts don't
13
- * expose <head> for inline scripts). A plain inline <script>
14
- * (deliberately NOT next/script) executes at HTML-parse time — long
15
- * before the afterInteractive <GA4>/<MetaPixel> loaders, which run
16
- * post-hydration. Google's consent-mode docs require the default to be
17
- * set synchronously ahead of the tag; an async default races the
18
- * loader and the first hit of a returning consented visitor would
19
- * ship denied.
20
- *
21
- * `required` is the store's consent switch, `consent.enabled` from
22
- * `GET /api/store/consent`, which the layout already fetches for the
23
- * banner. It is REQUIRED on purpose: with it absent there is no safe
24
- * default to fall back on. Defaulting to true would keep the pixels of a
25
- * store that switched its banner off silent forever (the defect fixed on
26
- * 2026-09-14); defaulting to false would fire them on an EU store whose
27
- * layout forgot the prop. So the layout states it, and a layout that does
28
- * not fails to compile.
29
- *
30
- * Behavior:
31
- * - `required` and no stored choice (first visit) → default ALL DENIED.
32
- * Tags still load (advanced consent mode): gtag sends cookieless
33
- * pings, the Pixel queues client-side. The <ConsentBanner> then
34
- * collects the choice and applies the live 'update'.
35
- * - not `required` and no stored choice → default ALL GRANTED: the store
36
- * collects no consent, so there is nothing to wait for.
37
- * - Stored choice → default mirrors it, whatever the switch, so
38
- * returning visitors are correct from the very first hit with no
39
- * banner and no race, and a visitor who declined keeps that decision.
40
- *
41
- * `ads_data_redaction` strips ad-click identifiers (gclid et al.) from
42
- * the cookieless pings while ad_storage is denied, per Google's docs.
43
- *
44
- * The script interpolates one boolean literal and the shared cookie name,
45
- * nothing typed by a person — safe as a server-component inline script
46
- * with a stable id. CARD TRAP: it must NEVER wait on the
47
- * /api/store/consent fetch inside the browser; the layout resolves the
48
- * setting on the server and inlines it here.
49
- */
50
- export function ConsentInit({
51
- required,
52
- }: {
53
- /** The store's consent switch: `consent.enabled` from `GET /api/store/consent`. */
54
- required: boolean
55
- }) {
56
- return (
57
- <script
58
- id="1click-consent-init"
59
- dangerouslySetInnerHTML={{ __html: consentInitSnippet(required) }}
60
- />
61
- )
62
- }
1
+ import { consentInitSnippet } from "./consent"
2
+
3
+ /**
4
+ * ConsentInit — the synchronous Consent Mode v2 "default" snippet.
5
+ *
6
+ * Ported from the Cartbase reference implementation
7
+ * `src/components/storefront/consent/consent-init.tsx` (itself from
8
+ * `@1click/ui/src/tracking/consent-init.tsx` v2.3.1); the snippet lives in
9
+ * ./consent.ts (`consentInitSnippet`) so its ordering contract is
10
+ * unit-testable.
11
+ *
12
+ * Render it as the FIRST child of <body> (App Router layouts don't
13
+ * expose <head> for inline scripts). A plain inline <script>
14
+ * (deliberately NOT next/script) executes at HTML-parse time — long
15
+ * before the afterInteractive <GA4>/<MetaPixel> loaders, which run
16
+ * post-hydration. Google's consent-mode docs require the default to be
17
+ * set synchronously ahead of the tag; an async default races the
18
+ * loader and the first hit of a returning consented visitor would
19
+ * ship denied.
20
+ *
21
+ * `required` is the store's consent switch, `consent.enabled` from
22
+ * `GET /api/store/consent`, which the layout already fetches for the
23
+ * banner. It is REQUIRED on purpose: with it absent there is no safe
24
+ * default to fall back on. Defaulting to true would keep the pixels of a
25
+ * store that switched its banner off silent forever (the defect fixed on
26
+ * 2026-09-14); defaulting to false would fire them on an EU store whose
27
+ * layout forgot the prop. So the layout states it, and a layout that does
28
+ * not fails to compile.
29
+ *
30
+ * Behavior:
31
+ * - `required` and no stored choice (first visit) → default ALL DENIED.
32
+ * Tags still load (advanced consent mode): gtag sends cookieless
33
+ * pings, the Pixel queues client-side. The <ConsentBanner> then
34
+ * collects the choice and applies the live 'update'.
35
+ * - not `required` and no stored choice → default ALL GRANTED: the store
36
+ * collects no consent, so there is nothing to wait for.
37
+ * - Stored choice → default mirrors it, whatever the switch, so
38
+ * returning visitors are correct from the very first hit with no
39
+ * banner and no race, and a visitor who declined keeps that decision.
40
+ *
41
+ * `ads_data_redaction` strips ad-click identifiers (gclid et al.) from
42
+ * the cookieless pings while ad_storage is denied, per Google's docs.
43
+ *
44
+ * The script interpolates one boolean literal and the shared cookie name,
45
+ * nothing typed by a person — safe as a server-component inline script
46
+ * with a stable id. CARD TRAP: it must NEVER wait on the
47
+ * /api/store/consent fetch inside the browser; the layout resolves the
48
+ * setting on the server and inlines it here.
49
+ */
50
+ export function ConsentInit({
51
+ required,
52
+ }: {
53
+ /** The store's consent switch: `consent.enabled` from `GET /api/store/consent`. */
54
+ required: boolean
55
+ }) {
56
+ return (
57
+ <script
58
+ id="1click-consent-init"
59
+ dangerouslySetInnerHTML={{ __html: consentInitSnippet(required) }}
60
+ />
61
+ )
62
+ }