@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.
Files changed (108) hide show
  1. package/LICENSE +21 -21
  2. package/package.json +3 -1
  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 -180
  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 +367 -168
  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/gift-wrap.tsx +82 -82
  28. package/src/cart-drawer/index.ts +73 -68
  29. package/src/cart-drawer/item/index.tsx +162 -162
  30. package/src/cart-drawer/item/quantity.tsx +8 -14
  31. package/src/cart-drawer/item/upsell.tsx +110 -110
  32. package/src/cart-drawer/labels.ts +123 -123
  33. package/src/cart-drawer/mutation-queue.ts +78 -0
  34. package/src/cart-drawer/notes.tsx +131 -131
  35. package/src/cart-drawer/sticky-footer.tsx +73 -73
  36. package/src/cart-drawer/summary-breakdown.tsx +197 -197
  37. package/src/checkout/address-error-copy.ts +117 -117
  38. package/src/checkout/boxnow-locker-selector.tsx +410 -410
  39. package/src/checkout/compare-addresses.ts +40 -40
  40. package/src/checkout/discount-section.tsx +218 -218
  41. package/src/checkout/error-copy-codes.ts +63 -63
  42. package/src/checkout/geocode.ts +154 -154
  43. package/src/checkout/gift-card-section.tsx +224 -224
  44. package/src/checkout/index.ts +72 -72
  45. package/src/checkout/labels.ts +495 -495
  46. package/src/checkout/payment-button.tsx +372 -372
  47. package/src/checkout/payment-error-copy.ts +154 -154
  48. package/src/checkout/promotion-error-copy.ts +91 -91
  49. package/src/checkout/use-checkout-orchestration.ts +1604 -1592
  50. package/src/common/country-flag.tsx +52 -52
  51. package/src/common/country-select.tsx +11 -11
  52. package/src/common/index.ts +20 -20
  53. package/src/common/market-select.tsx +57 -57
  54. package/src/index.ts +12 -12
  55. package/src/lib/cookie-names.ts +14 -0
  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 -39
  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-totals.tsx +250 -250
  74. package/src/primitives/select-field.tsx +93 -93
  75. package/src/products/image-gallery.tsx +43 -43
  76. package/src/products/index.ts +68 -59
  77. package/src/products/option-select.tsx +67 -67
  78. package/src/products/product-actions-wrapper.tsx +63 -62
  79. package/src/products/product-actions.tsx +151 -150
  80. package/src/products/product-price.tsx +69 -69
  81. package/src/products/product-promises.tsx +61 -61
  82. package/src/products/product-specs.tsx +65 -65
  83. package/src/products/product-tabs.tsx +123 -123
  84. package/src/products/product-template.tsx +163 -162
  85. package/src/products/purchase-options.tsx +130 -130
  86. package/src/products/sets.ts +110 -0
  87. package/src/products/use-product-actions.ts +199 -164
  88. package/src/products/variant-matching.ts +71 -71
  89. package/src/products/variant-url.ts +74 -74
  90. package/src/reviews-ui/index.ts +73 -73
  91. package/src/store/category-template.tsx +136 -136
  92. package/src/store/index.ts +40 -40
  93. package/src/tracking/chatgpt-pixel.tsx +99 -99
  94. package/src/tracking/consent-init.tsx +62 -62
  95. package/src/tracking/events.ts +348 -348
  96. package/src/tracking/ga4.tsx +93 -93
  97. package/src/tracking/google-ads.ts +84 -84
  98. package/src/tracking/gtm.tsx +60 -60
  99. package/src/tracking/inline-script.ts +49 -49
  100. package/src/tracking/oaiq.ts +206 -206
  101. package/src/tracking/tiktok-pixel.tsx +91 -91
  102. package/src/tracking/track-init.tsx +56 -56
  103. package/src/tracking/track-order-purchase.tsx +122 -122
  104. package/src/tracking/ttq.ts +180 -180
  105. package/src/tracking/use-tracking-config.ts +54 -54
  106. package/theme/index.css +25 -25
  107. package/theme/theme.css +150 -150
  108. 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
+ }