@cartbase/storefront 0.20.0 → 0.21.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 (109) hide show
  1. package/LICENSE +21 -21
  2. package/package.json +4 -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 +181 -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/redirects.ts +37 -37
  18. package/src/api/regions.ts +200 -200
  19. package/src/api/search.ts +163 -163
  20. package/src/api/store.ts +56 -35
  21. package/src/api/types.ts +91 -91
  22. package/src/cart-drawer/context.tsx +778 -778
  23. package/src/cart-drawer/cross-sell-carousel.tsx +211 -211
  24. package/src/cart-drawer/cross-sell-sidebar.tsx +158 -158
  25. package/src/cart-drawer/gift-wrap.tsx +82 -82
  26. package/src/cart-drawer/item/index.tsx +162 -162
  27. package/src/cart-drawer/item/upsell.tsx +110 -110
  28. package/src/cart-drawer/labels.ts +123 -123
  29. package/src/cart-drawer/mutation-queue.ts +78 -78
  30. package/src/cart-drawer/notes.tsx +131 -131
  31. package/src/cart-drawer/payment-badges.tsx +36 -96
  32. package/src/cart-drawer/sticky-footer.tsx +73 -73
  33. package/src/cart-drawer/summary-breakdown.tsx +197 -197
  34. package/src/checkout/address-error-copy.ts +117 -117
  35. package/src/checkout/boxnow-locker-selector.tsx +410 -410
  36. package/src/checkout/compare-addresses.ts +40 -40
  37. package/src/checkout/discount-section.tsx +218 -218
  38. package/src/checkout/error-copy-codes.ts +63 -63
  39. package/src/checkout/geocode.ts +154 -154
  40. package/src/checkout/gift-card-section.tsx +224 -224
  41. package/src/checkout/index.ts +72 -72
  42. package/src/checkout/labels.ts +495 -495
  43. package/src/checkout/payment-button.tsx +372 -372
  44. package/src/checkout/payment-error-copy.ts +154 -154
  45. package/src/checkout/promotion-error-copy.ts +91 -91
  46. package/src/common/country-flag.tsx +52 -52
  47. package/src/common/country-select.tsx +11 -11
  48. package/src/common/icons/cartbase-mark.ts +9 -0
  49. package/src/common/icons/payment-marks.ts +73 -0
  50. package/src/common/icons/social-marks.ts +61 -0
  51. package/src/common/index.ts +35 -20
  52. package/src/common/market-select.tsx +57 -57
  53. package/src/common/payment-icons.tsx +54 -0
  54. package/src/common/powered-by-cartbase.tsx +47 -0
  55. package/src/common/social-links.tsx +65 -0
  56. package/src/index.ts +12 -12
  57. package/src/lib/country-name.ts +59 -59
  58. package/src/lib/get-product-price.ts +133 -133
  59. package/src/lib/media-image.tsx +39 -39
  60. package/src/lib/payment-constants.ts +53 -53
  61. package/src/lib/platform.ts +13 -13
  62. package/src/lib/price.tsx +39 -39
  63. package/src/lib/store-api-error.ts +36 -36
  64. package/src/lib/variant-caption.ts +32 -32
  65. package/src/locales/bg.ts +3 -0
  66. package/src/locales/context.ts +37 -37
  67. package/src/locales/en.ts +26 -26
  68. package/src/locales/es.ts +3 -0
  69. package/src/locales/index.ts +19 -19
  70. package/src/locales/provider.tsx +59 -59
  71. package/src/locales/types.ts +77 -77
  72. package/src/order/index.ts +62 -62
  73. package/src/order/labels.ts +79 -79
  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/option-select.tsx +67 -67
  78. package/src/products/product-info.tsx +5 -5
  79. package/src/products/product-price.tsx +69 -69
  80. package/src/products/product-promises.tsx +61 -61
  81. package/src/products/product-specs.tsx +65 -65
  82. package/src/products/product-tabs.tsx +123 -123
  83. package/src/products/purchase-options.tsx +130 -130
  84. package/src/products/sets.ts +110 -110
  85. package/src/products/variant-matching.ts +71 -71
  86. package/src/products/variant-url.ts +74 -74
  87. package/src/reviews-ui/lightbox-state.ts +46 -46
  88. package/src/reviews-ui/review-header.tsx +166 -166
  89. package/src/reviews-ui/review-lightbox.tsx +271 -271
  90. package/src/reviews-ui/review-list.tsx +193 -193
  91. package/src/reviews-ui/review-widget.tsx +219 -218
  92. package/src/reviews-ui/widget-options.ts +55 -55
  93. package/src/store/category-template.tsx +136 -136
  94. package/src/store/index.ts +40 -40
  95. package/src/store/labels.ts +10 -0
  96. package/src/tracking/chatgpt-pixel.tsx +99 -99
  97. package/src/tracking/consent-init.tsx +62 -62
  98. package/src/tracking/events.ts +348 -348
  99. package/src/tracking/ga4.tsx +93 -93
  100. package/src/tracking/google-ads.ts +84 -84
  101. package/src/tracking/gtm.tsx +60 -60
  102. package/src/tracking/inline-script.ts +49 -49
  103. package/src/tracking/oaiq.ts +206 -206
  104. package/src/tracking/tiktok-pixel.tsx +91 -91
  105. package/src/tracking/track-init.tsx +56 -56
  106. package/src/tracking/track-order-purchase.tsx +122 -122
  107. package/src/tracking/ttq.ts +180 -180
  108. package/src/tracking/use-tracking-config.ts +54 -54
  109. package/theme/index.css +25 -25
@@ -1,158 +1,158 @@
1
- "use client"
2
-
3
- import { StoreImage as Image } from "../lib/media-image"
4
- import Link from "next/link"
5
-
6
- import type { StoreProduct } from "../api/products"
7
- import { convertToLocale } from "../lib/money"
8
- import { useCartDrawer } from "./context"
9
-
10
- /**
11
- * CartCrossSellSidebar — vertical product-card list for desktop cart
12
- * drawer's left sidebar.
13
- *
14
- * Ported from `@1click/ui/src/cart-drawer/cross-sell-sidebar.tsx` (v2.3.1).
15
- * Cartbase data seam: source stores fed this from Medusa product fetches —
16
- * feed it from `@cartbase/storefront/api/products` `listProducts` (curated
17
- * collection/tag) or `@cartbase/storefront/api/search` `listRelatedProducts`
18
- * (anchor-product complements), mapped through `toCrossSellProduct` below.
19
- * Wire `onAdd` to the provider's `addItem(variantId)`. Prices are EUR
20
- * major units and come server-computed via `calculated_price` — never
21
- * recompute client-side.
22
- */
23
-
24
- export type CrossSellSidebarProduct = {
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
- /**
35
- * Map a store-API product (products/search/related responses all reuse the
36
- * canonical `StoreProduct` shape) to a cross-sell card. Picks the first
37
- * variant carrying a server-computed `calculated_price` for the current
38
- * pricing context; returns null when no variant is priced (pass
39
- * `currency_code`/`region_id` on the listing call, and skip null results).
40
- * Pure — exported for unit tests and for building `crossSell.loader`s.
41
- */
42
- export function toCrossSellProduct(
43
- product: StoreProduct
44
- ): CrossSellSidebarProduct | null {
45
- const variant = (product.variants ?? []).find((v) => v.calculated_price)
46
- const price = variant?.calculated_price
47
- if (!variant || !price) return null
48
- return {
49
- id: product.id,
50
- title: product.title,
51
- handle: product.handle,
52
- thumbnail: product.thumbnail,
53
- price: price.calculated_amount,
54
- currencyCode: price.currency_code,
55
- variantId: variant.id,
56
- }
57
- }
58
-
59
- type CartCrossSellSidebarProps = {
60
- label?: string
61
- products: CrossSellSidebarProduct[]
62
- onAdd?: (productId: string, variantId?: string) => void
63
- }
64
-
65
- export function CartCrossSellSidebar({
66
- label,
67
- products,
68
- onAdd,
69
- }: CartCrossSellSidebarProps) {
70
- const { labels, hrefs } = useCartDrawer()
71
-
72
- if (!products.length) return null
73
-
74
- return (
75
- <div className="h-full flex flex-col">
76
- <div className="px-5 pt-6 pb-4">
77
- <h3 className="text-xs font-bold text-foreground uppercase tracking-widest">
78
- {label ?? labels.youMightAlsoLike}
79
- </h3>
80
- </div>
81
- <div className="flex-1 overflow-y-auto no-scrollbar px-4 pb-6 space-y-3">
82
- {products.map((product) => (
83
- <div
84
- key={product.id}
85
- className="bg-card rounded-[2px] p-3.5 transition-shadow hover:shadow-md"
86
- style={{
87
- boxShadow: "0 1px 4px rgba(0,0,0,0.06), 0 0 1px rgba(0,0,0,0.08)",
88
- }}
89
- >
90
- <Link href={`${hrefs.productPrefix}/${product.handle}`}>
91
- <div className="w-full aspect-square rounded-xl overflow-hidden bg-muted relative mb-3">
92
- {product.thumbnail ? (
93
- <Image
94
- src={product.thumbnail}
95
- alt={product.title}
96
- fill
97
- className="object-cover"
98
- sizes="200px"
99
- />
100
- ) : (
101
- <div className="w-full h-full flex items-center justify-center">
102
- <svg
103
- width="32"
104
- height="32"
105
- viewBox="0 0 24 24"
106
- fill="none"
107
- stroke="currentColor"
108
- className="text-muted-foreground"
109
- strokeWidth="1.2"
110
- >
111
- <rect x="3" y="3" width="18" height="18" rx="3" />
112
- <circle cx="8.5" cy="8.5" r="1.5" />
113
- <path
114
- d="M21 15l-5-5L5 21"
115
- strokeLinecap="round"
116
- strokeLinejoin="round"
117
- />
118
- </svg>
119
- </div>
120
- )}
121
- </div>
122
- </Link>
123
- <Link
124
- href={`${hrefs.productPrefix}/${product.handle}`}
125
- className="text-[13px] font-semibold text-foreground leading-tight line-clamp-2 hover:text-muted-foreground transition-colors block"
126
- >
127
- {product.title}
128
- </Link>
129
- <p className="text-sm font-semibold text-foreground mt-1.5">
130
- {convertToLocale({
131
- amount: product.price,
132
- currency_code: product.currencyCode,
133
- })}
134
- </p>
135
- <button
136
- type="button"
137
- onClick={() => onAdd?.(product.id, product.variantId)}
138
- className="mt-3 w-full h-10 text-xs font-semibold text-foreground bg-card border border-border rounded-xl hover:bg-foreground hover:text-card hover:border-text-base transition-all flex items-center justify-center gap-1.5 active:scale-[0.97]"
139
- >
140
- {labels.addToCart}
141
- <svg
142
- width="12"
143
- height="12"
144
- viewBox="0 0 12 12"
145
- fill="none"
146
- stroke="currentColor"
147
- strokeWidth="1.5"
148
- strokeLinecap="round"
149
- >
150
- <path d="M2.5 6h7M7 3.5L9.5 6 7 8.5" />
151
- </svg>
152
- </button>
153
- </div>
154
- ))}
155
- </div>
156
- </div>
157
- )
158
- }
1
+ "use client"
2
+
3
+ import { StoreImage as Image } from "../lib/media-image"
4
+ import Link from "next/link"
5
+
6
+ import type { StoreProduct } from "../api/products"
7
+ import { convertToLocale } from "../lib/money"
8
+ import { useCartDrawer } from "./context"
9
+
10
+ /**
11
+ * CartCrossSellSidebar — vertical product-card list for desktop cart
12
+ * drawer's left sidebar.
13
+ *
14
+ * Ported from `@1click/ui/src/cart-drawer/cross-sell-sidebar.tsx` (v2.3.1).
15
+ * Cartbase data seam: source stores fed this from Medusa product fetches —
16
+ * feed it from `@cartbase/storefront/api/products` `listProducts` (curated
17
+ * collection/tag) or `@cartbase/storefront/api/search` `listRelatedProducts`
18
+ * (anchor-product complements), mapped through `toCrossSellProduct` below.
19
+ * Wire `onAdd` to the provider's `addItem(variantId)`. Prices are EUR
20
+ * major units and come server-computed via `calculated_price` — never
21
+ * recompute client-side.
22
+ */
23
+
24
+ export type CrossSellSidebarProduct = {
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
+ /**
35
+ * Map a store-API product (products/search/related responses all reuse the
36
+ * canonical `StoreProduct` shape) to a cross-sell card. Picks the first
37
+ * variant carrying a server-computed `calculated_price` for the current
38
+ * pricing context; returns null when no variant is priced (pass
39
+ * `currency_code`/`region_id` on the listing call, and skip null results).
40
+ * Pure — exported for unit tests and for building `crossSell.loader`s.
41
+ */
42
+ export function toCrossSellProduct(
43
+ product: StoreProduct
44
+ ): CrossSellSidebarProduct | null {
45
+ const variant = (product.variants ?? []).find((v) => v.calculated_price)
46
+ const price = variant?.calculated_price
47
+ if (!variant || !price) return null
48
+ return {
49
+ id: product.id,
50
+ title: product.title,
51
+ handle: product.handle,
52
+ thumbnail: product.thumbnail,
53
+ price: price.calculated_amount,
54
+ currencyCode: price.currency_code,
55
+ variantId: variant.id,
56
+ }
57
+ }
58
+
59
+ type CartCrossSellSidebarProps = {
60
+ label?: string
61
+ products: CrossSellSidebarProduct[]
62
+ onAdd?: (productId: string, variantId?: string) => void
63
+ }
64
+
65
+ export function CartCrossSellSidebar({
66
+ label,
67
+ products,
68
+ onAdd,
69
+ }: CartCrossSellSidebarProps) {
70
+ const { labels, hrefs } = useCartDrawer()
71
+
72
+ if (!products.length) return null
73
+
74
+ return (
75
+ <div className="h-full flex flex-col">
76
+ <div className="px-5 pt-6 pb-4">
77
+ <h3 className="text-xs font-bold text-foreground uppercase tracking-widest">
78
+ {label ?? labels.youMightAlsoLike}
79
+ </h3>
80
+ </div>
81
+ <div className="flex-1 overflow-y-auto no-scrollbar px-4 pb-6 space-y-3">
82
+ {products.map((product) => (
83
+ <div
84
+ key={product.id}
85
+ className="bg-card rounded-[2px] p-3.5 transition-shadow hover:shadow-md"
86
+ style={{
87
+ boxShadow: "0 1px 4px rgba(0,0,0,0.06), 0 0 1px rgba(0,0,0,0.08)",
88
+ }}
89
+ >
90
+ <Link href={`${hrefs.productPrefix}/${product.handle}`}>
91
+ <div className="w-full aspect-square rounded-xl overflow-hidden bg-muted relative mb-3">
92
+ {product.thumbnail ? (
93
+ <Image
94
+ src={product.thumbnail}
95
+ alt={product.title}
96
+ fill
97
+ className="object-cover"
98
+ sizes="200px"
99
+ />
100
+ ) : (
101
+ <div className="w-full h-full flex items-center justify-center">
102
+ <svg
103
+ width="32"
104
+ height="32"
105
+ viewBox="0 0 24 24"
106
+ fill="none"
107
+ stroke="currentColor"
108
+ className="text-muted-foreground"
109
+ strokeWidth="1.2"
110
+ >
111
+ <rect x="3" y="3" width="18" height="18" rx="3" />
112
+ <circle cx="8.5" cy="8.5" r="1.5" />
113
+ <path
114
+ d="M21 15l-5-5L5 21"
115
+ strokeLinecap="round"
116
+ strokeLinejoin="round"
117
+ />
118
+ </svg>
119
+ </div>
120
+ )}
121
+ </div>
122
+ </Link>
123
+ <Link
124
+ href={`${hrefs.productPrefix}/${product.handle}`}
125
+ className="text-[13px] font-semibold text-foreground leading-tight line-clamp-2 hover:text-muted-foreground transition-colors block"
126
+ >
127
+ {product.title}
128
+ </Link>
129
+ <p className="text-sm font-semibold text-foreground mt-1.5">
130
+ {convertToLocale({
131
+ amount: product.price,
132
+ currency_code: product.currencyCode,
133
+ })}
134
+ </p>
135
+ <button
136
+ type="button"
137
+ onClick={() => onAdd?.(product.id, product.variantId)}
138
+ className="mt-3 w-full h-10 text-xs font-semibold text-foreground bg-card border border-border rounded-xl hover:bg-foreground hover:text-card hover:border-text-base transition-all flex items-center justify-center gap-1.5 active:scale-[0.97]"
139
+ >
140
+ {labels.addToCart}
141
+ <svg
142
+ width="12"
143
+ height="12"
144
+ viewBox="0 0 12 12"
145
+ fill="none"
146
+ stroke="currentColor"
147
+ strokeWidth="1.5"
148
+ strokeLinecap="round"
149
+ >
150
+ <path d="M2.5 6h7M7 3.5L9.5 6 7 8.5" />
151
+ </svg>
152
+ </button>
153
+ </div>
154
+ ))}
155
+ </div>
156
+ </div>
157
+ )
158
+ }
@@ -1,82 +1,82 @@
1
- "use client"
2
-
3
- import { useState } from "react"
4
-
5
- import { Price } from "../lib/price"
6
- import { cn } from "../lib/utils"
7
- import { useCartDrawer } from "./context"
8
-
9
- /**
10
- * CartGiftWrap — inline toggle row for adding gift wrapping to the cart.
11
- *
12
- * Ported from `@1click/ui/src/cart-drawer/gift-wrap.tsx` (v2.3.1);
13
- * originally extracted from mindpages-storefront
14
- * src/modules/cart-drawer/cart-gift-wrap.tsx. `price` is EUR major units.
15
- * The store owns the effect: wire `onToggle` to add/remove the store's
16
- * gift-wrap variant via the provider's `addItem` / `removeItem`.
17
- */
18
-
19
- type CartGiftWrapProps = {
20
- price: number
21
- currencyCode: string
22
- enabled?: boolean
23
- onToggle?: (enabled: boolean) => void
24
- }
25
-
26
- export function CartGiftWrap({
27
- price,
28
- currencyCode,
29
- enabled = false,
30
- onToggle,
31
- }: CartGiftWrapProps) {
32
- const { labels } = useCartDrawer()
33
- const [checked, setChecked] = useState(enabled)
34
-
35
- const handleToggle = () => {
36
- const next = !checked
37
- setChecked(next)
38
- onToggle?.(next)
39
- }
40
-
41
- return (
42
- <div className="px-5 sm:px-6 py-2">
43
- <button
44
- type="button"
45
- onClick={handleToggle}
46
- className="flex items-center gap-3 w-full text-left py-2 min-h-[44px] group"
47
- >
48
- <div
49
- className={cn(
50
- "w-5 h-5 rounded border-[1.5px] flex items-center justify-center transition-all flex-shrink-0",
51
- checked
52
- ? "bg-foreground border-text-base"
53
- : "border-border group-hover:border-text-muted group-active:border-text-muted"
54
- )}
55
- >
56
- {checked && (
57
- <svg
58
- width="10"
59
- height="10"
60
- viewBox="0 0 10 10"
61
- fill="none"
62
- stroke="white"
63
- strokeWidth="2"
64
- strokeLinecap="round"
65
- strokeLinejoin="round"
66
- >
67
- <path d="M2 5l2 2 4-4" />
68
- </svg>
69
- )}
70
- </div>
71
- <span className="text-[13px] text-foreground flex-1">
72
- {labels.addGiftWrap}
73
- </span>
74
- <Price
75
- amount={price}
76
- currencyCode={currencyCode}
77
- className="text-[13px] text-muted-foreground font-medium"
78
- />
79
- </button>
80
- </div>
81
- )
82
- }
1
+ "use client"
2
+
3
+ import { useState } from "react"
4
+
5
+ import { Price } from "../lib/price"
6
+ import { cn } from "../lib/utils"
7
+ import { useCartDrawer } from "./context"
8
+
9
+ /**
10
+ * CartGiftWrap — inline toggle row for adding gift wrapping to the cart.
11
+ *
12
+ * Ported from `@1click/ui/src/cart-drawer/gift-wrap.tsx` (v2.3.1);
13
+ * originally extracted from mindpages-storefront
14
+ * src/modules/cart-drawer/cart-gift-wrap.tsx. `price` is EUR major units.
15
+ * The store owns the effect: wire `onToggle` to add/remove the store's
16
+ * gift-wrap variant via the provider's `addItem` / `removeItem`.
17
+ */
18
+
19
+ type CartGiftWrapProps = {
20
+ price: number
21
+ currencyCode: string
22
+ enabled?: boolean
23
+ onToggle?: (enabled: boolean) => void
24
+ }
25
+
26
+ export function CartGiftWrap({
27
+ price,
28
+ currencyCode,
29
+ enabled = false,
30
+ onToggle,
31
+ }: CartGiftWrapProps) {
32
+ const { labels } = useCartDrawer()
33
+ const [checked, setChecked] = useState(enabled)
34
+
35
+ const handleToggle = () => {
36
+ const next = !checked
37
+ setChecked(next)
38
+ onToggle?.(next)
39
+ }
40
+
41
+ return (
42
+ <div className="px-5 sm:px-6 py-2">
43
+ <button
44
+ type="button"
45
+ onClick={handleToggle}
46
+ className="flex items-center gap-3 w-full text-left py-2 min-h-[44px] group"
47
+ >
48
+ <div
49
+ className={cn(
50
+ "w-5 h-5 rounded border-[1.5px] flex items-center justify-center transition-all flex-shrink-0",
51
+ checked
52
+ ? "bg-foreground border-text-base"
53
+ : "border-border group-hover:border-text-muted group-active:border-text-muted"
54
+ )}
55
+ >
56
+ {checked && (
57
+ <svg
58
+ width="10"
59
+ height="10"
60
+ viewBox="0 0 10 10"
61
+ fill="none"
62
+ stroke="white"
63
+ strokeWidth="2"
64
+ strokeLinecap="round"
65
+ strokeLinejoin="round"
66
+ >
67
+ <path d="M2 5l2 2 4-4" />
68
+ </svg>
69
+ )}
70
+ </div>
71
+ <span className="text-[13px] text-foreground flex-1">
72
+ {labels.addGiftWrap}
73
+ </span>
74
+ <Price
75
+ amount={price}
76
+ currencyCode={currencyCode}
77
+ className="text-[13px] text-muted-foreground font-medium"
78
+ />
79
+ </button>
80
+ </div>
81
+ )
82
+ }