@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,154 +1,154 @@
1
- /**
2
- * Geo + Bulgarian place-name helpers for the carrier pickers (Econt office,
3
- * BoxNow locker).
4
- *
5
- * Ported from `@1click/ui/src/lib/geocode.ts` (v2.3.1) verbatim — the lib
6
- * family of `@cartbase/storefront` does not ship a geocode module, so the
7
- * checkout family (its only consumer) carries it. Extra file beyond the
8
- * pre-declared export list → exported from the family barrel only.
9
- */
10
-
11
- /**
12
- * transliterateBgToLatin — Bulgarian Cyrillic → Latin transliteration
13
- * using the streamlined system (official BG government standard, used
14
- * on passports and road signs).
15
- *
16
- * Purpose: when a Bulgarian user types "Sofia" but the data source has
17
- * "София", substring matching fails. Normalize both sides through this
18
- * function before comparing.
19
- *
20
- * Idempotent — Latin input passes through unchanged (just lowercased).
21
- */
22
- const BG_CYRILLIC_TO_LATIN: Record<string, string> = {
23
- а: "a", б: "b", в: "v", г: "g", д: "d", е: "e", ж: "zh",
24
- з: "z", и: "i", й: "y", к: "k", л: "l", м: "m", н: "n",
25
- о: "o", п: "p", р: "r", с: "s", т: "t", у: "u", ф: "f",
26
- х: "h", ц: "ts", ч: "ch", ш: "sh", щ: "sht",
27
- ъ: "a", ь: "y", ю: "yu", я: "ya",
28
- }
29
-
30
- export function transliterateBgToLatin(s: string): string {
31
- return s
32
- .toLowerCase()
33
- .split("")
34
- .map((c) => BG_CYRILLIC_TO_LATIN[c] ?? c)
35
- .join("")
36
- }
37
-
38
- /**
39
- * normalizeForMatch — produce a canonical key for fuzzy place-name matching.
40
- *
41
- * Built on `transliterateBgToLatin` but additionally collapses common
42
- * Bulgarian / South Slavic Latinization variants:
43
- *
44
- * - "София" → "sofiya" → "sofia" (strict transliteration → common)
45
- * - "Sofia" → "sofia" → "sofia"
46
- * - "Sofiya" → "sofiya" → "sofia"
47
- * - "Sofija" → "sofija" → "sofia"
48
- * - "Sofiia" → "sofiia" → "sofia"
49
- *
50
- * Rule: any "iy" / "ij" / "ii" before a vowel collapses to "i". This is
51
- * the ONE pattern that breaks substring matching for Bulgarian cities.
52
- * Strict transliteration emits "ya" for "я" (correct on passports), but
53
- * BoxNow / Econt / OpenStreetMap commonly use the more colloquial "ia"
54
- * spelling — so users typing "Sofia" must match data containing "София".
55
- *
56
- * Use this — not `transliterateBgToLatin` directly — wherever you compare
57
- * a user-entered place name against an external data source's name.
58
- * Reserve `transliterateBgToLatin` for outbound contexts (API queries,
59
- * passport-shaped output) where the strict form is required.
60
- */
61
- export function normalizeForMatch(s: string): string {
62
- return transliterateBgToLatin(s)
63
- .replace(/iy(?=[aeiouy])/g, "i")
64
- .replace(/ij(?=[aeiouy])/g, "i")
65
- .replace(/ii(?=[aeiouy])/g, "i")
66
- }
67
-
68
- /**
69
- * Shared geo helpers used by Bulgarian fulfillment selectors (Econt, BoxNow).
70
- *
71
- * Nominatim usage is rate-limited (1 req/s/IP per their ToS); for a
72
- * typical checkout we only call once per address change, well under the
73
- * limit.
74
- */
75
-
76
- export function distanceMeters(
77
- lat1: number,
78
- lon1: number,
79
- lat2: number,
80
- lon2: number
81
- ): number {
82
- const R = 6371000
83
- const dLat = ((lat2 - lat1) * Math.PI) / 180
84
- const dLon = ((lon2 - lon1) * Math.PI) / 180
85
- const a =
86
- Math.sin(dLat / 2) ** 2 +
87
- Math.cos((lat1 * Math.PI) / 180) *
88
- Math.cos((lat2 * Math.PI) / 180) *
89
- Math.sin(dLon / 2) ** 2
90
- return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
91
- }
92
-
93
- export function formatDistance(meters: number): string {
94
- if (meters < 1000) return `${Math.round(meters)} m`
95
- return `${(meters / 1000).toFixed(1)} km`
96
- }
97
-
98
- /**
99
- * cleanAddress — strip Bulgarian address prefixes/suffixes (ул., бул.,
100
- * вх., ет., ап., etc.) to give Nominatim a better chance at a hit.
101
- */
102
- export function cleanAddress(raw: string): string {
103
- return raw
104
- .replace(/^(ул|ul)\.?\s*/i, "")
105
- .replace(/^(бул|bul)\.?\s*/i, "")
106
- .replace(/^(пл|pl)\.?\s*/i, "")
107
- .replace(/^(жк|zhk|jk)\.?\s*/i, "")
108
- .replace(/^(ж\.к|zh\.k)\.?\s*/i, "")
109
- .replace(/^(кв|kv)\.?\s*/i, "")
110
- .replace(/^(с|s)\.?\s+/i, "")
111
- .replace(/^(гр|gr)\.?\s*/i, "")
112
- .replace(/[„""()]/g, "")
113
- .replace(
114
- /\s*[,;]?\s*(вх|vh|ет|et|fl|ап|ap|apt|офис|office|стая|room|секция|корпус|каб|entr|entrance)\.?\s*.*/i,
115
- ""
116
- )
117
- .replace(/\s*(бл|bl|block)\.?\s*(\d+)\s*.*/i, " $2")
118
- .replace(/(\d+)\s*[А-Яа-яA-Za-z]$/, "$1")
119
- .replace(/[,.\s]+$/, "")
120
- .trim()
121
- }
122
-
123
- /**
124
- * geocodeAddress — hit OpenStreetMap Nominatim for a Bulgarian address.
125
- * Returns { lat, lng } or null when no hit / network error.
126
- */
127
- export async function geocodeAddress(
128
- city: string,
129
- address: string
130
- ): Promise<{ lat: number; lng: number } | null> {
131
- try {
132
- const cleaned = cleanAddress(address)
133
- const numMatch = cleaned.match(/^(.+?)\s+(\d+\S*)$/)
134
- const street = numMatch ? `${numMatch[2]} ${numMatch[1]}` : cleaned
135
-
136
- const url = new URL("https://nominatim.openstreetmap.org/search")
137
- url.searchParams.set("city", city)
138
- url.searchParams.set("street", street)
139
- url.searchParams.set("country", "Bulgaria")
140
- url.searchParams.set("format", "json")
141
- url.searchParams.set("limit", "1")
142
-
143
- const res = await fetch(url.toString(), {
144
- headers: { "User-Agent": "cartbase-storefront/1.0" },
145
- })
146
- const data = await res.json()
147
- if (data?.[0]) {
148
- return { lat: parseFloat(data[0].lat), lng: parseFloat(data[0].lon) }
149
- }
150
- return null
151
- } catch {
152
- return null
153
- }
154
- }
1
+ /**
2
+ * Geo + Bulgarian place-name helpers for the carrier pickers (Econt office,
3
+ * BoxNow locker).
4
+ *
5
+ * Ported from `@1click/ui/src/lib/geocode.ts` (v2.3.1) verbatim — the lib
6
+ * family of `@cartbase/storefront` does not ship a geocode module, so the
7
+ * checkout family (its only consumer) carries it. Extra file beyond the
8
+ * pre-declared export list → exported from the family barrel only.
9
+ */
10
+
11
+ /**
12
+ * transliterateBgToLatin — Bulgarian Cyrillic → Latin transliteration
13
+ * using the streamlined system (official BG government standard, used
14
+ * on passports and road signs).
15
+ *
16
+ * Purpose: when a Bulgarian user types "Sofia" but the data source has
17
+ * "София", substring matching fails. Normalize both sides through this
18
+ * function before comparing.
19
+ *
20
+ * Idempotent — Latin input passes through unchanged (just lowercased).
21
+ */
22
+ const BG_CYRILLIC_TO_LATIN: Record<string, string> = {
23
+ а: "a", б: "b", в: "v", г: "g", д: "d", е: "e", ж: "zh",
24
+ з: "z", и: "i", й: "y", к: "k", л: "l", м: "m", н: "n",
25
+ о: "o", п: "p", р: "r", с: "s", т: "t", у: "u", ф: "f",
26
+ х: "h", ц: "ts", ч: "ch", ш: "sh", щ: "sht",
27
+ ъ: "a", ь: "y", ю: "yu", я: "ya",
28
+ }
29
+
30
+ export function transliterateBgToLatin(s: string): string {
31
+ return s
32
+ .toLowerCase()
33
+ .split("")
34
+ .map((c) => BG_CYRILLIC_TO_LATIN[c] ?? c)
35
+ .join("")
36
+ }
37
+
38
+ /**
39
+ * normalizeForMatch — produce a canonical key for fuzzy place-name matching.
40
+ *
41
+ * Built on `transliterateBgToLatin` but additionally collapses common
42
+ * Bulgarian / South Slavic Latinization variants:
43
+ *
44
+ * - "София" → "sofiya" → "sofia" (strict transliteration → common)
45
+ * - "Sofia" → "sofia" → "sofia"
46
+ * - "Sofiya" → "sofiya" → "sofia"
47
+ * - "Sofija" → "sofija" → "sofia"
48
+ * - "Sofiia" → "sofiia" → "sofia"
49
+ *
50
+ * Rule: any "iy" / "ij" / "ii" before a vowel collapses to "i". This is
51
+ * the ONE pattern that breaks substring matching for Bulgarian cities.
52
+ * Strict transliteration emits "ya" for "я" (correct on passports), but
53
+ * BoxNow / Econt / OpenStreetMap commonly use the more colloquial "ia"
54
+ * spelling — so users typing "Sofia" must match data containing "София".
55
+ *
56
+ * Use this — not `transliterateBgToLatin` directly — wherever you compare
57
+ * a user-entered place name against an external data source's name.
58
+ * Reserve `transliterateBgToLatin` for outbound contexts (API queries,
59
+ * passport-shaped output) where the strict form is required.
60
+ */
61
+ export function normalizeForMatch(s: string): string {
62
+ return transliterateBgToLatin(s)
63
+ .replace(/iy(?=[aeiouy])/g, "i")
64
+ .replace(/ij(?=[aeiouy])/g, "i")
65
+ .replace(/ii(?=[aeiouy])/g, "i")
66
+ }
67
+
68
+ /**
69
+ * Shared geo helpers used by Bulgarian fulfillment selectors (Econt, BoxNow).
70
+ *
71
+ * Nominatim usage is rate-limited (1 req/s/IP per their ToS); for a
72
+ * typical checkout we only call once per address change, well under the
73
+ * limit.
74
+ */
75
+
76
+ export function distanceMeters(
77
+ lat1: number,
78
+ lon1: number,
79
+ lat2: number,
80
+ lon2: number
81
+ ): number {
82
+ const R = 6371000
83
+ const dLat = ((lat2 - lat1) * Math.PI) / 180
84
+ const dLon = ((lon2 - lon1) * Math.PI) / 180
85
+ const a =
86
+ Math.sin(dLat / 2) ** 2 +
87
+ Math.cos((lat1 * Math.PI) / 180) *
88
+ Math.cos((lat2 * Math.PI) / 180) *
89
+ Math.sin(dLon / 2) ** 2
90
+ return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
91
+ }
92
+
93
+ export function formatDistance(meters: number): string {
94
+ if (meters < 1000) return `${Math.round(meters)} m`
95
+ return `${(meters / 1000).toFixed(1)} km`
96
+ }
97
+
98
+ /**
99
+ * cleanAddress — strip Bulgarian address prefixes/suffixes (ул., бул.,
100
+ * вх., ет., ап., etc.) to give Nominatim a better chance at a hit.
101
+ */
102
+ export function cleanAddress(raw: string): string {
103
+ return raw
104
+ .replace(/^(ул|ul)\.?\s*/i, "")
105
+ .replace(/^(бул|bul)\.?\s*/i, "")
106
+ .replace(/^(пл|pl)\.?\s*/i, "")
107
+ .replace(/^(жк|zhk|jk)\.?\s*/i, "")
108
+ .replace(/^(ж\.к|zh\.k)\.?\s*/i, "")
109
+ .replace(/^(кв|kv)\.?\s*/i, "")
110
+ .replace(/^(с|s)\.?\s+/i, "")
111
+ .replace(/^(гр|gr)\.?\s*/i, "")
112
+ .replace(/[„""()]/g, "")
113
+ .replace(
114
+ /\s*[,;]?\s*(вх|vh|ет|et|fl|ап|ap|apt|офис|office|стая|room|секция|корпус|каб|entr|entrance)\.?\s*.*/i,
115
+ ""
116
+ )
117
+ .replace(/\s*(бл|bl|block)\.?\s*(\d+)\s*.*/i, " $2")
118
+ .replace(/(\d+)\s*[А-Яа-яA-Za-z]$/, "$1")
119
+ .replace(/[,.\s]+$/, "")
120
+ .trim()
121
+ }
122
+
123
+ /**
124
+ * geocodeAddress — hit OpenStreetMap Nominatim for a Bulgarian address.
125
+ * Returns { lat, lng } or null when no hit / network error.
126
+ */
127
+ export async function geocodeAddress(
128
+ city: string,
129
+ address: string
130
+ ): Promise<{ lat: number; lng: number } | null> {
131
+ try {
132
+ const cleaned = cleanAddress(address)
133
+ const numMatch = cleaned.match(/^(.+?)\s+(\d+\S*)$/)
134
+ const street = numMatch ? `${numMatch[2]} ${numMatch[1]}` : cleaned
135
+
136
+ const url = new URL("https://nominatim.openstreetmap.org/search")
137
+ url.searchParams.set("city", city)
138
+ url.searchParams.set("street", street)
139
+ url.searchParams.set("country", "Bulgaria")
140
+ url.searchParams.set("format", "json")
141
+ url.searchParams.set("limit", "1")
142
+
143
+ const res = await fetch(url.toString(), {
144
+ headers: { "User-Agent": "cartbase-storefront/1.0" },
145
+ })
146
+ const data = await res.json()
147
+ if (data?.[0]) {
148
+ return { lat: parseFloat(data[0].lat), lng: parseFloat(data[0].lon) }
149
+ }
150
+ return null
151
+ } catch {
152
+ return null
153
+ }
154
+ }