@cartbase/storefront 0.19.0 → 0.20.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 (105) hide show
  1. package/LICENSE +21 -21
  2. package/package.json +258 -258
  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/redirects.ts +37 -37
  18. package/src/api/regions.ts +200 -200
  19. package/src/api/reviews.ts +22 -1
  20. package/src/api/search.ts +163 -163
  21. package/src/api/store.ts +35 -35
  22. package/src/api/types.ts +91 -91
  23. package/src/cart-drawer/context.tsx +778 -778
  24. package/src/cart-drawer/cross-sell-carousel.tsx +211 -211
  25. package/src/cart-drawer/cross-sell-sidebar.tsx +158 -158
  26. package/src/cart-drawer/gift-wrap.tsx +82 -82
  27. package/src/cart-drawer/item/index.tsx +162 -162
  28. package/src/cart-drawer/item/upsell.tsx +110 -110
  29. package/src/cart-drawer/labels.ts +123 -123
  30. package/src/cart-drawer/mutation-queue.ts +78 -78
  31. package/src/cart-drawer/notes.tsx +131 -131
  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/index.ts +20 -20
  49. package/src/common/market-select.tsx +57 -57
  50. package/src/index.ts +12 -12
  51. package/src/lib/country-name.ts +59 -59
  52. package/src/lib/get-product-price.ts +133 -133
  53. package/src/lib/media-image.tsx +39 -39
  54. package/src/lib/payment-constants.ts +53 -53
  55. package/src/lib/platform.ts +13 -13
  56. package/src/lib/price.tsx +39 -39
  57. package/src/lib/store-api-error.ts +36 -36
  58. package/src/lib/variant-caption.ts +32 -32
  59. package/src/locales/bg.ts +3 -2
  60. package/src/locales/context.ts +37 -37
  61. package/src/locales/en.ts +26 -26
  62. package/src/locales/es.ts +3 -2
  63. package/src/locales/index.ts +19 -19
  64. package/src/locales/provider.tsx +59 -59
  65. package/src/locales/types.ts +77 -77
  66. package/src/order/index.ts +62 -62
  67. package/src/order/labels.ts +79 -79
  68. package/src/order/order-totals.tsx +250 -250
  69. package/src/primitives/select-field.tsx +93 -93
  70. package/src/products/image-gallery.tsx +43 -43
  71. package/src/products/option-select.tsx +67 -67
  72. package/src/products/product-info.tsx +5 -5
  73. package/src/products/product-price.tsx +69 -69
  74. package/src/products/product-promises.tsx +61 -61
  75. package/src/products/product-specs.tsx +65 -65
  76. package/src/products/product-tabs.tsx +123 -123
  77. package/src/products/purchase-options.tsx +130 -130
  78. package/src/products/sets.ts +110 -110
  79. package/src/products/variant-matching.ts +71 -71
  80. package/src/products/variant-url.ts +74 -74
  81. package/src/reviews-ui/index.ts +12 -0
  82. package/src/reviews-ui/labels.ts +5 -2
  83. package/src/reviews-ui/lightbox-state.ts +46 -46
  84. package/src/reviews-ui/review-header.tsx +166 -0
  85. package/src/reviews-ui/review-lightbox.tsx +271 -271
  86. package/src/reviews-ui/review-list.tsx +193 -191
  87. package/src/reviews-ui/review-widget.tsx +63 -71
  88. package/src/reviews-ui/star-badge.tsx +6 -4
  89. package/src/reviews-ui/widget-options.ts +55 -0
  90. package/src/store/category-template.tsx +136 -136
  91. package/src/store/index.ts +40 -40
  92. package/src/tracking/chatgpt-pixel.tsx +99 -99
  93. package/src/tracking/consent-init.tsx +62 -62
  94. package/src/tracking/events.ts +348 -348
  95. package/src/tracking/ga4.tsx +93 -93
  96. package/src/tracking/google-ads.ts +84 -84
  97. package/src/tracking/gtm.tsx +60 -60
  98. package/src/tracking/inline-script.ts +49 -49
  99. package/src/tracking/oaiq.ts +206 -206
  100. package/src/tracking/tiktok-pixel.tsx +91 -91
  101. package/src/tracking/track-init.tsx +56 -56
  102. package/src/tracking/track-order-purchase.tsx +122 -122
  103. package/src/tracking/ttq.ts +180 -180
  104. package/src/tracking/use-tracking-config.ts +54 -54
  105. 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
+ }