@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,211 +1,211 @@
1
- "use client"
2
-
3
- import Image from "next/image"
4
- import Link from "next/link"
5
- import { useRef, useState } from "react"
6
-
7
- import { Price } from "../lib/price"
8
- import { cn } from "../lib/utils"
9
- import { useCartDrawer } from "./context"
10
-
11
- /**
12
- * CartCrossSellCarousel — horizontal scrollable product strip with left/right
13
- * arrow controls. Used as mobile alternative to the sidebar or as a second
14
- * cross-sell zone below the line items on desktop.
15
- *
16
- * Ported from `@1click/ui/src/cart-drawer/cross-sell-carousel.tsx` (v2.3.1).
17
- * Cartbase data seam: same as the sidebar — feed from
18
- * `@cartbase/storefront/api/products` `listProducts` or `api/search`
19
- * `listRelatedProducts` via `toCrossSellProduct`
20
- * (`./cross-sell-sidebar`; the card shapes are identical), wire `onAdd`
21
- * to the provider's `addItem(variantId)`. Prices EUR major units.
22
- */
23
-
24
- export type CrossSellCarouselProduct = {
25
- id: string
26
- title: string
27
- handle: string
28
- thumbnail: string | null
29
- price: number
30
- currencyCode: string
31
- variantId?: string
32
- }
33
-
34
- type CartCrossSellCarouselProps = {
35
- label?: string
36
- products: CrossSellCarouselProduct[]
37
- onAdd?: (productId: string, variantId?: string) => void
38
- }
39
-
40
- export function CartCrossSellCarousel({
41
- label,
42
- products,
43
- onAdd,
44
- }: CartCrossSellCarouselProps) {
45
- const { labels, hrefs } = useCartDrawer()
46
- const scrollRef = useRef<HTMLDivElement>(null)
47
- const [canScrollLeft, setCanScrollLeft] = useState(false)
48
- const [canScrollRight, setCanScrollRight] = useState(true)
49
-
50
- const checkScroll = () => {
51
- if (!scrollRef.current) return
52
- const { scrollLeft, scrollWidth, clientWidth } = scrollRef.current
53
- setCanScrollLeft(scrollLeft > 4)
54
- setCanScrollRight(scrollLeft < scrollWidth - clientWidth - 4)
55
- }
56
-
57
- const scroll = (dir: "left" | "right") => {
58
- if (!scrollRef.current) return
59
- scrollRef.current.scrollBy({
60
- left: dir === "left" ? -260 : 260,
61
- behavior: "smooth",
62
- })
63
- setTimeout(checkScroll, 350)
64
- }
65
-
66
- if (!products.length) return null
67
-
68
- return (
69
- <div className="border-t border-border bg-muted/50 py-4">
70
- <div className="flex items-center justify-between px-5 sm:px-6 mb-3">
71
- <h3 className="text-sm font-semibold text-foreground">
72
- {label ?? labels.youllLoveThis}
73
- </h3>
74
- <div className="hidden sm:flex items-center gap-1">
75
- <button
76
- type="button"
77
- onClick={() => scroll("left")}
78
- disabled={!canScrollLeft}
79
- className="w-8 h-8 flex items-center justify-center rounded-full border border-border text-muted-foreground hover:border-text-muted hover:text-foreground disabled:opacity-30 disabled:cursor-not-allowed transition-all"
80
- aria-label="Scroll left"
81
- >
82
- <svg
83
- width="14"
84
- height="14"
85
- viewBox="0 0 14 14"
86
- fill="none"
87
- stroke="currentColor"
88
- strokeWidth="1.5"
89
- strokeLinecap="round"
90
- strokeLinejoin="round"
91
- >
92
- <path d="M8.5 3L4.5 7l4 4" />
93
- </svg>
94
- </button>
95
- <button
96
- type="button"
97
- onClick={() => scroll("right")}
98
- disabled={!canScrollRight}
99
- className="w-8 h-8 flex items-center justify-center rounded-full border border-border text-muted-foreground hover:border-text-muted hover:text-foreground disabled:opacity-30 disabled:cursor-not-allowed transition-all"
100
- aria-label="Scroll right"
101
- >
102
- <svg
103
- width="14"
104
- height="14"
105
- viewBox="0 0 14 14"
106
- fill="none"
107
- stroke="currentColor"
108
- strokeWidth="1.5"
109
- strokeLinecap="round"
110
- strokeLinejoin="round"
111
- >
112
- <path d="M5.5 3l4 4-4 4" />
113
- </svg>
114
- </button>
115
- </div>
116
- </div>
117
-
118
- <div
119
- ref={scrollRef}
120
- onScroll={checkScroll}
121
- className="flex gap-2.5 overflow-x-auto no-scrollbar"
122
- style={{
123
- WebkitOverflowScrolling: "touch",
124
- paddingLeft: "1.25rem",
125
- paddingRight: "1.25rem",
126
- }}
127
- >
128
- {products.map((product) => (
129
- <div
130
- key={product.id}
131
- className={cn(
132
- "flex-shrink-0 w-[260px] bg-card rounded-xl border border-border p-2.5",
133
- "flex gap-3 items-center snap-start"
134
- )}
135
- style={{ boxShadow: "0 1px 3px rgba(0,0,0,0.04)" }}
136
- >
137
- <Link
138
- href={`${hrefs.productPrefix}/${product.handle}`}
139
- className="flex-shrink-0"
140
- >
141
- <div className="w-14 h-14 rounded-lg overflow-hidden bg-muted relative">
142
- {product.thumbnail ? (
143
- <Image
144
- src={product.thumbnail}
145
- alt={product.title}
146
- fill
147
- className="object-cover"
148
- sizes="56px"
149
- />
150
- ) : (
151
- <div className="w-full h-full flex items-center justify-center">
152
- <svg
153
- width="16"
154
- height="16"
155
- viewBox="0 0 24 24"
156
- fill="none"
157
- stroke="currentColor"
158
- className="text-muted-foreground"
159
- strokeWidth="1.2"
160
- >
161
- <rect x="3" y="3" width="18" height="18" rx="3" />
162
- <circle cx="8.5" cy="8.5" r="1.5" />
163
- <path
164
- d="M21 15l-5-5L5 21"
165
- strokeLinecap="round"
166
- strokeLinejoin="round"
167
- />
168
- </svg>
169
- </div>
170
- )}
171
- </div>
172
- </Link>
173
-
174
- <div className="flex-1 min-w-0">
175
- <Link
176
- href={`${hrefs.productPrefix}/${product.handle}`}
177
- className="text-[12px] font-semibold text-foreground line-clamp-1 leading-snug hover:text-muted-foreground transition-colors block"
178
- >
179
- {product.title}
180
- </Link>
181
- <Price
182
- amount={product.price}
183
- currencyCode={product.currencyCode}
184
- className="text-[13px] font-bold text-foreground block mt-0.5"
185
- />
186
- </div>
187
-
188
- <button
189
- type="button"
190
- onClick={() => onAdd?.(product.id, product.variantId)}
191
- className="w-8 h-8 flex-shrink-0 flex items-center justify-center rounded-full border border-border text-muted-foreground hover:border-text-base hover:text-foreground active:bg-muted transition-all"
192
- aria-label={`${labels.addToCart} ${product.title}`}
193
- >
194
- <svg
195
- width="14"
196
- height="14"
197
- viewBox="0 0 12 12"
198
- fill="none"
199
- stroke="currentColor"
200
- strokeWidth="1.5"
201
- strokeLinecap="round"
202
- >
203
- <path d="M6 2.5v7M2.5 6h7" />
204
- </svg>
205
- </button>
206
- </div>
207
- ))}
208
- </div>
209
- </div>
210
- )
211
- }
1
+ "use client"
2
+
3
+ import { StoreImage as Image } from "../lib/media-image"
4
+ import Link from "next/link"
5
+ import { useRef, useState } from "react"
6
+
7
+ import { Price } from "../lib/price"
8
+ import { cn } from "../lib/utils"
9
+ import { useCartDrawer } from "./context"
10
+
11
+ /**
12
+ * CartCrossSellCarousel — horizontal scrollable product strip with left/right
13
+ * arrow controls. Used as mobile alternative to the sidebar or as a second
14
+ * cross-sell zone below the line items on desktop.
15
+ *
16
+ * Ported from `@1click/ui/src/cart-drawer/cross-sell-carousel.tsx` (v2.3.1).
17
+ * Cartbase data seam: same as the sidebar — feed from
18
+ * `@cartbase/storefront/api/products` `listProducts` or `api/search`
19
+ * `listRelatedProducts` via `toCrossSellProduct`
20
+ * (`./cross-sell-sidebar`; the card shapes are identical), wire `onAdd`
21
+ * to the provider's `addItem(variantId)`. Prices EUR major units.
22
+ */
23
+
24
+ export type CrossSellCarouselProduct = {
25
+ id: string
26
+ title: string
27
+ handle: string
28
+ thumbnail: string | null
29
+ price: number
30
+ currencyCode: string
31
+ variantId?: string
32
+ }
33
+
34
+ type CartCrossSellCarouselProps = {
35
+ label?: string
36
+ products: CrossSellCarouselProduct[]
37
+ onAdd?: (productId: string, variantId?: string) => void
38
+ }
39
+
40
+ export function CartCrossSellCarousel({
41
+ label,
42
+ products,
43
+ onAdd,
44
+ }: CartCrossSellCarouselProps) {
45
+ const { labels, hrefs } = useCartDrawer()
46
+ const scrollRef = useRef<HTMLDivElement>(null)
47
+ const [canScrollLeft, setCanScrollLeft] = useState(false)
48
+ const [canScrollRight, setCanScrollRight] = useState(true)
49
+
50
+ const checkScroll = () => {
51
+ if (!scrollRef.current) return
52
+ const { scrollLeft, scrollWidth, clientWidth } = scrollRef.current
53
+ setCanScrollLeft(scrollLeft > 4)
54
+ setCanScrollRight(scrollLeft < scrollWidth - clientWidth - 4)
55
+ }
56
+
57
+ const scroll = (dir: "left" | "right") => {
58
+ if (!scrollRef.current) return
59
+ scrollRef.current.scrollBy({
60
+ left: dir === "left" ? -260 : 260,
61
+ behavior: "smooth",
62
+ })
63
+ setTimeout(checkScroll, 350)
64
+ }
65
+
66
+ if (!products.length) return null
67
+
68
+ return (
69
+ <div className="border-t border-border bg-muted/50 py-4">
70
+ <div className="flex items-center justify-between px-5 sm:px-6 mb-3">
71
+ <h3 className="text-sm font-semibold text-foreground">
72
+ {label ?? labels.youllLoveThis}
73
+ </h3>
74
+ <div className="hidden sm:flex items-center gap-1">
75
+ <button
76
+ type="button"
77
+ onClick={() => scroll("left")}
78
+ disabled={!canScrollLeft}
79
+ className="w-8 h-8 flex items-center justify-center rounded-full border border-border text-muted-foreground hover:border-text-muted hover:text-foreground disabled:opacity-30 disabled:cursor-not-allowed transition-all"
80
+ aria-label="Scroll left"
81
+ >
82
+ <svg
83
+ width="14"
84
+ height="14"
85
+ viewBox="0 0 14 14"
86
+ fill="none"
87
+ stroke="currentColor"
88
+ strokeWidth="1.5"
89
+ strokeLinecap="round"
90
+ strokeLinejoin="round"
91
+ >
92
+ <path d="M8.5 3L4.5 7l4 4" />
93
+ </svg>
94
+ </button>
95
+ <button
96
+ type="button"
97
+ onClick={() => scroll("right")}
98
+ disabled={!canScrollRight}
99
+ className="w-8 h-8 flex items-center justify-center rounded-full border border-border text-muted-foreground hover:border-text-muted hover:text-foreground disabled:opacity-30 disabled:cursor-not-allowed transition-all"
100
+ aria-label="Scroll right"
101
+ >
102
+ <svg
103
+ width="14"
104
+ height="14"
105
+ viewBox="0 0 14 14"
106
+ fill="none"
107
+ stroke="currentColor"
108
+ strokeWidth="1.5"
109
+ strokeLinecap="round"
110
+ strokeLinejoin="round"
111
+ >
112
+ <path d="M5.5 3l4 4-4 4" />
113
+ </svg>
114
+ </button>
115
+ </div>
116
+ </div>
117
+
118
+ <div
119
+ ref={scrollRef}
120
+ onScroll={checkScroll}
121
+ className="flex gap-2.5 overflow-x-auto no-scrollbar"
122
+ style={{
123
+ WebkitOverflowScrolling: "touch",
124
+ paddingLeft: "1.25rem",
125
+ paddingRight: "1.25rem",
126
+ }}
127
+ >
128
+ {products.map((product) => (
129
+ <div
130
+ key={product.id}
131
+ className={cn(
132
+ "flex-shrink-0 w-[260px] bg-card rounded-xl border border-border p-2.5",
133
+ "flex gap-3 items-center snap-start"
134
+ )}
135
+ style={{ boxShadow: "0 1px 3px rgba(0,0,0,0.04)" }}
136
+ >
137
+ <Link
138
+ href={`${hrefs.productPrefix}/${product.handle}`}
139
+ className="flex-shrink-0"
140
+ >
141
+ <div className="w-14 h-14 rounded-lg overflow-hidden bg-muted relative">
142
+ {product.thumbnail ? (
143
+ <Image
144
+ src={product.thumbnail}
145
+ alt={product.title}
146
+ fill
147
+ className="object-cover"
148
+ sizes="56px"
149
+ />
150
+ ) : (
151
+ <div className="w-full h-full flex items-center justify-center">
152
+ <svg
153
+ width="16"
154
+ height="16"
155
+ viewBox="0 0 24 24"
156
+ fill="none"
157
+ stroke="currentColor"
158
+ className="text-muted-foreground"
159
+ strokeWidth="1.2"
160
+ >
161
+ <rect x="3" y="3" width="18" height="18" rx="3" />
162
+ <circle cx="8.5" cy="8.5" r="1.5" />
163
+ <path
164
+ d="M21 15l-5-5L5 21"
165
+ strokeLinecap="round"
166
+ strokeLinejoin="round"
167
+ />
168
+ </svg>
169
+ </div>
170
+ )}
171
+ </div>
172
+ </Link>
173
+
174
+ <div className="flex-1 min-w-0">
175
+ <Link
176
+ href={`${hrefs.productPrefix}/${product.handle}`}
177
+ className="text-[12px] font-semibold text-foreground line-clamp-1 leading-snug hover:text-muted-foreground transition-colors block"
178
+ >
179
+ {product.title}
180
+ </Link>
181
+ <Price
182
+ amount={product.price}
183
+ currencyCode={product.currencyCode}
184
+ className="text-[13px] font-bold text-foreground block mt-0.5"
185
+ />
186
+ </div>
187
+
188
+ <button
189
+ type="button"
190
+ onClick={() => onAdd?.(product.id, product.variantId)}
191
+ className="w-8 h-8 flex-shrink-0 flex items-center justify-center rounded-full border border-border text-muted-foreground hover:border-text-base hover:text-foreground active:bg-muted transition-all"
192
+ aria-label={`${labels.addToCart} ${product.title}`}
193
+ >
194
+ <svg
195
+ width="14"
196
+ height="14"
197
+ viewBox="0 0 12 12"
198
+ fill="none"
199
+ stroke="currentColor"
200
+ strokeWidth="1.5"
201
+ strokeLinecap="round"
202
+ >
203
+ <path d="M6 2.5v7M2.5 6h7" />
204
+ </svg>
205
+ </button>
206
+ </div>
207
+ ))}
208
+ </div>
209
+ </div>
210
+ )
211
+ }