@cartbase/storefront 0.8.0 → 0.10.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 (50) hide show
  1. package/package.json +7 -8
  2. package/src/cart-drawer/cross-sell-carousel.tsx +2 -3
  3. package/src/cart-drawer/gift-wrap.tsx +82 -83
  4. package/src/cart-drawer/index.ts +0 -1
  5. package/src/cart-drawer/item/index.tsx +4 -6
  6. package/src/cart-drawer/sticky-footer.tsx +73 -73
  7. package/src/checkout/address-error-copy.ts +117 -119
  8. package/src/checkout/discount-section.tsx +218 -218
  9. package/src/checkout/error-copy-codes.ts +63 -0
  10. package/src/checkout/gift-card-section.tsx +4 -4
  11. package/src/checkout/index.ts +4 -5
  12. package/src/checkout/labels.ts +218 -0
  13. package/src/checkout/line-item-card.tsx +3 -3
  14. package/src/checkout/order-summary.tsx +11 -12
  15. package/src/checkout/payment-button.tsx +372 -373
  16. package/src/checkout/payment-error-copy.ts +154 -269
  17. package/src/checkout/promotion-error-copy.ts +91 -124
  18. package/src/checkout/shipping-method-list.tsx +2 -2
  19. package/src/checkout/use-checkout-orchestration.ts +10 -8
  20. package/src/lib/money.ts +1 -1
  21. package/src/lib/price.tsx +39 -0
  22. package/src/locales/bg.ts +468 -0
  23. package/src/locales/en.ts +26 -0
  24. package/src/locales/es.ts +467 -0
  25. package/src/locales/index.ts +19 -0
  26. package/src/locales/provider.tsx +63 -0
  27. package/src/locales/types.ts +49 -0
  28. package/src/order/index.ts +0 -1
  29. package/src/order/order-delivery-card.tsx +2 -2
  30. package/src/order/order-item.tsx +3 -3
  31. package/src/order/order-totals.tsx +4 -4
  32. package/src/products/index.ts +0 -1
  33. package/src/reviews-ui/index.ts +0 -1
  34. package/src/store/index.ts +0 -1
  35. package/src/tracking/attribution.ts +13 -0
  36. package/src/tracking/chatgpt-pixel.tsx +92 -0
  37. package/src/tracking/consent-banner.tsx +5 -1
  38. package/src/tracking/consent.ts +12 -0
  39. package/src/tracking/events.ts +56 -2
  40. package/src/tracking/index.ts +12 -0
  41. package/src/tracking/oaiq.ts +206 -0
  42. package/src/tracking/storefront-tags.tsx +2 -0
  43. package/src/tracking/types.ts +4 -0
  44. package/src/cart-drawer/labels-bg.ts +0 -72
  45. package/src/checkout/labels-bg.ts +0 -128
  46. package/src/lib/dual-price.tsx +0 -73
  47. package/src/order/labels-bg.ts +0 -39
  48. package/src/products/labels-bg.ts +0 -35
  49. package/src/reviews-ui/labels-bg.ts +0 -91
  50. package/src/store/labels-bg.ts +0 -22
@@ -152,6 +152,110 @@ export type CheckoutLabels = {
152
152
  boxnowChange: string
153
153
  boxnowUnavailable: string
154
154
  boxnowNoLockersInCity: string
155
+ /**
156
+ * What a shopper reads when saving the address fails. It used to be
157
+ * hardcoded BULGARIAN inside `address-error-copy.ts` with no way to
158
+ * override it, so a merchant anywhere on earth showed Bulgarian on the
159
+ * one screen where a shopper is about to pay (found 2026-09-13). It is
160
+ * copy, so it belongs in the label pack like all other copy.
161
+ *
162
+ * The PATTERNS that recognize each failure stay in code: they match the
163
+ * API's own English error text, which is not translated and must not be.
164
+ */
165
+ addressErrors: AddressErrorCopy
166
+ /**
167
+ * The rest of the checkout's failure copy, lifted out of
168
+ * `payment-error-copy.ts` and `promotion-error-copy.ts` on 2026-09-13
169
+ * for the same reason as `addressErrors`: they held 54 hardcoded
170
+ * Bulgarian sentences that every merchant shipped and none could
171
+ * override. The recognition stays in those modules; only the words
172
+ * live here.
173
+ */
174
+ paymentErrors: PaymentErrorCopy
175
+ giftCardErrors: GiftCardErrorCopy
176
+ promotionErrors: PromotionErrorCopy
177
+ }
178
+
179
+ /** The eleven things that can go wrong saving a checkout address. */
180
+ export type AddressErrorCopy = {
181
+ /** Anything unrecognized. Never leaks the technical message. */
182
+ generic: string
183
+ network: string
184
+ validationFailed: string
185
+ invalidRegion: string
186
+ regionRequired: string
187
+ cartCompleted: string
188
+ cartNotFound: string
189
+ invalidEmail: string
190
+ invalidPostalCode: string
191
+ invalidPhone: string
192
+ invalidCountry: string
193
+ }
194
+
195
+ export type PaymentErrorCopy = {
196
+ genericCard: string
197
+ genericCod: string
198
+ cartEmailRequired: string
199
+ cartEmpty: string
200
+ shippingAddressRequired: string
201
+ shippingMethodRequired: string
202
+ paymentCollectionRequired: string
203
+ paymentSessionRequired: string
204
+ insufficientInventory: string
205
+ checkoutMethodHidden: string
206
+ requiresAction: string
207
+ paymentNotAuthorized: string
208
+ paymentNotInitiated: string
209
+ paymentIncomplete: string
210
+ giftCardInsufficientBalance: string
211
+ giftCardNotRedeemable: string
212
+ accountRequired: string
213
+ cartLocked: string
214
+ cartCompleted: string
215
+ cartNotFound: string
216
+ shippingOptionNotFound: string
217
+ invalidProvider: string
218
+ shippingPriceMissing: string
219
+ stripeNotConfigured: string
220
+ validationFailed: string
221
+ cardDeclined: string
222
+ insufficientFunds: string
223
+ expiredCard: string
224
+ incorrectCvc: string
225
+ incorrectCardNumber: string
226
+ invalidExpiry: string
227
+ authenticationRequired: string
228
+ processingError: string
229
+ rateLimited: string
230
+ paymentExpired: string
231
+ amountMismatch: string
232
+ networkError: string
233
+ cartAlreadyCompleted: string
234
+ outOfStock: string
235
+ invalidRegion: string
236
+ shippingMethodMissing: string
237
+ }
238
+
239
+ export type GiftCardErrorCopy = {
240
+ invalidGiftCard: string
241
+ rateLimited: string
242
+ cartCompleted: string
243
+ cartNotFound: string
244
+ validationFailed: string
245
+ generic: string
246
+ }
247
+
248
+ export type PromotionErrorCopy = {
249
+ generic: string
250
+ needsEmail: string
251
+ exhausted: string
252
+ promotionNotFound: string
253
+ promotionInactive: string
254
+ promotionMisconfigured: string
255
+ promotionUnsupportedType: string
256
+ cartCompleted: string
257
+ cartNotFound: string
258
+ validationFailed: string
155
259
  }
156
260
 
157
261
  export const defaultCheckoutLabels: CheckoutLabels = {
@@ -260,4 +364,118 @@ export const defaultCheckoutLabels: CheckoutLabels = {
260
364
  "BoxNow is temporarily unavailable, please choose a different shipping method.",
261
365
  boxnowNoLockersInCity:
262
366
  "No BoxNow lockers found in your city. Please choose a different shipping method.",
367
+ addressErrors: {
368
+ generic:
369
+ "We could not save your address. Please check the details and try again.",
370
+ network:
371
+ "We cannot reach the server. Please check your connection and try again.",
372
+ validationFailed:
373
+ "One of the fields is not valid. Please check the details and try again.",
374
+ invalidRegion: "That country is not valid. Please reload the page.",
375
+ regionRequired: "The delivery region is not valid. Please reload the page.",
376
+ cartCompleted:
377
+ "This order is already placed. Please check your email for the confirmation.",
378
+ cartNotFound: "Your cart session expired. Please reload the page.",
379
+ invalidEmail: "That email address is not valid. Please check the spelling.",
380
+ // NOT "four digits": the postcode length is the country's business, and
381
+ // the Bulgarian original said four, which is wrong everywhere else.
382
+ invalidPostalCode: "That postal code is not valid. Please check it.",
383
+ invalidPhone: "That phone number is not valid. Please check the digits.",
384
+ invalidCountry: "That country is not valid. Please reload the page.",
385
+ },
386
+ paymentErrors: {
387
+ genericCard:
388
+ "The payment could not be processed right now. Try again or choose another payment method.",
389
+ genericCod: "The order could not be placed right now. Try again in a moment.",
390
+ cartEmailRequired:
391
+ "An email address is needed to place the order. Add one to continue.",
392
+ cartEmpty: "Your cart is empty. Add a product to continue.",
393
+ shippingAddressRequired:
394
+ "A delivery address is needed to place the order. Add one to continue.",
395
+ shippingMethodRequired: "Choose a delivery method to continue.",
396
+ paymentCollectionRequired: "The payment is not ready yet. Try again.",
397
+ paymentSessionRequired: "The payment is not ready yet. Try again.",
398
+ insufficientInventory:
399
+ "One of the products is no longer available in the quantity you chose. Update your cart to continue.",
400
+ checkoutMethodHidden:
401
+ "The method you chose is no longer available for this order. Choose another one and try again.",
402
+ requiresAction:
403
+ "Your bank needs to confirm this payment. Follow the steps it shows to continue.",
404
+ paymentNotAuthorized:
405
+ "The payment was not approved. Try again or choose another payment method.",
406
+ paymentNotInitiated:
407
+ "The payment expired before it went through. Reload the page and try again.",
408
+ paymentIncomplete:
409
+ "Your gift cards no longer cover the full amount. Choose a payment method for the rest.",
410
+ giftCardInsufficientBalance:
411
+ "The balance on that gift card no longer covers the amount. Remove it or choose another payment method.",
412
+ giftCardNotRedeemable:
413
+ "That gift card can no longer be used. Remove it and try again.",
414
+ accountRequired: "This order needs you to be signed in. Sign in to continue.",
415
+ cartLocked:
416
+ "This order is being processed right now. Wait a few seconds before trying again.",
417
+ cartCompleted:
418
+ "This order has already gone through. Check your email for the confirmation.",
419
+ cartNotFound: "Your cart is no longer available. Reload the page to start again.",
420
+ shippingOptionNotFound:
421
+ "The delivery method you chose is no longer available. Choose another one.",
422
+ invalidProvider: "The payment method you chose cannot be used. Choose another one.",
423
+ shippingPriceMissing:
424
+ "The delivery cost could not be worked out. Choose another delivery method.",
425
+ stripeNotConfigured:
426
+ "Card payments are unavailable at the moment. Choose another payment method.",
427
+ validationFailed: "Some order details are missing. Check the form and try again.",
428
+ cardDeclined:
429
+ "That card was declined by the bank. Try another card or contact your bank.",
430
+ insufficientFunds: "That card does not have enough funds. Try another card.",
431
+ expiredCard: "That card has expired. Try another card.",
432
+ incorrectCvc:
433
+ "The security code does not match. Check the security code on your card and try again.",
434
+ incorrectCardNumber:
435
+ "That card number does not look right. Check the digits and try again.",
436
+ invalidExpiry: "The expiry date does not look right. Check the month and the year.",
437
+ authenticationRequired:
438
+ "Your bank needs to confirm this payment. Follow the steps it shows to continue.",
439
+ processingError: "The card could not be processed. Try again in a moment.",
440
+ rateLimited:
441
+ "There have been too many payment attempts. Wait a few minutes and try again.",
442
+ paymentExpired:
443
+ "The payment expired before it went through. Reload the page and try again.",
444
+ amountMismatch: "The order total changed. Reload the page and try again.",
445
+ networkError:
446
+ "The connection was lost. Check your internet connection and try again.",
447
+ cartAlreadyCompleted:
448
+ "This order has already gone through. Check your email for the confirmation.",
449
+ outOfStock:
450
+ "One of the products is no longer available in the quantity you chose. Update your cart to continue.",
451
+ invalidRegion:
452
+ "This delivery destination is not available. Reload the page and try again.",
453
+ shippingMethodMissing: "Choose a delivery method to continue.",
454
+ },
455
+ giftCardErrors: {
456
+ invalidGiftCard: "That gift card code is not valid. Check the code and try again.",
457
+ rateLimited: "Too many attempts. Wait a few minutes and try again.",
458
+ cartCompleted:
459
+ "This order is already placed. Check your email for the confirmation.",
460
+ cartNotFound: "Your cart timed out. Reload the page and try again.",
461
+ validationFailed: "That code is not valid. Check it and try again.",
462
+ generic: "The gift card could not be applied right now. Try again in a moment.",
463
+ },
464
+ promotionErrors: {
465
+ generic:
466
+ "This discount code can't be applied right now. Check the code and try again.",
467
+ needsEmail:
468
+ "This discount code needs an email address. Add yours above and try again.",
469
+ exhausted: "This promotion has been fully used and is no longer available.",
470
+ promotionNotFound: "This discount code isn't valid. Check it and try again.",
471
+ promotionInactive: "This discount code is no longer active.",
472
+ promotionMisconfigured:
473
+ "This discount code can't be applied right now. Check the code and try again.",
474
+ promotionUnsupportedType:
475
+ "This discount code can't be applied right now. Check the code and try again.",
476
+ cartCompleted:
477
+ "This order has already been placed. Check your email for the confirmation.",
478
+ cartNotFound: "We couldn't find your cart. Reload the page and try again.",
479
+ validationFailed: "This discount code isn't valid. Check it and try again.",
480
+ },
263
481
  }
@@ -4,14 +4,14 @@ import { useState } from "react"
4
4
 
5
5
  import type { StorefrontClient } from "../api/http"
6
6
  import { updateLineItem, type Cart, type CartLineItem } from "../api/carts"
7
- import { DualPrice } from "../lib/dual-price"
7
+ import { Price } from "../lib/price"
8
8
  import { cn } from "../lib/utils"
9
9
  import { useCheckoutLabels } from "./context"
10
10
 
11
11
  /**
12
12
  * LineItemCard — a single cart line shown inside the checkout order
13
13
  * summary card. Includes thumbnail, title, variant, quantity select,
14
- * and dual-currency price.
14
+ * and price.
15
15
  *
16
16
  * Ported from `@1click/ui/src/checkout/line-item-card.tsx` (v2.3.1) with
17
17
  * the Cartbase data seam: the cookie-bound `updateLineItem` server action →
@@ -141,7 +141,7 @@ export function LineItemCard({
141
141
  </div>
142
142
 
143
143
  <div className="flex flex-col items-end justify-center flex-shrink-0">
144
- <DualPrice
144
+ <Price
145
145
  amount={total}
146
146
  currencyCode={currencyCode}
147
147
  className="text-sm font-bold text-foreground"
@@ -4,7 +4,7 @@ import { useEffect, useMemo, useState } from "react"
4
4
 
5
5
  import type { StorefrontClient } from "../api/http"
6
6
  import { updateLineItem, type Cart, type CartLineItem } from "../api/carts"
7
- import { DualPrice } from "../lib/dual-price"
7
+ import { Price } from "../lib/price"
8
8
  import { isProductLine } from "../lib/cart-helpers"
9
9
  import { cn } from "../lib/utils"
10
10
  import { useCheckoutLabels } from "./context"
@@ -170,7 +170,7 @@ export function CheckoutLineItem({
170
170
  <div className="hidden sm:flex flex-1 min-w-0 flex-col gap-2.5">
171
171
  <div className="flex items-baseline justify-between gap-3">
172
172
  {titleBlock}
173
- <DualPrice
173
+ <Price
174
174
  amount={item.total ?? 0}
175
175
  currencyCode={currencyCode}
176
176
  className="text-sm font-bold text-foreground text-right flex-shrink-0"
@@ -185,7 +185,7 @@ export function CheckoutLineItem({
185
185
  {titleBlock}
186
186
  <div className="flex items-center justify-between gap-3">
187
187
  {qtyPill}
188
- <DualPrice
188
+ <Price
189
189
  amount={item.total ?? 0}
190
190
  currencyCode={currencyCode}
191
191
  className="text-sm font-bold text-foreground text-right"
@@ -376,7 +376,7 @@ export function OrderSummary({
376
376
  <div className="px-5 sm:px-6 space-y-3">
377
377
  <div className="flex justify-between text-sm">
378
378
  <span className="text-muted-foreground">{labels.subtotal}</span>
379
- <DualPrice
379
+ <Price
380
380
  amount={productSubtotal}
381
381
  currencyCode={cart.currency_code}
382
382
  className="font-medium text-foreground"
@@ -391,7 +391,7 @@ export function OrderSummary({
391
391
  {labels.shippingFree}
392
392
  </span>
393
393
  ) : (
394
- <DualPrice
394
+ <Price
395
395
  amount={shippingCost ?? 0}
396
396
  currencyCode={cart.currency_code}
397
397
  className="font-medium text-foreground"
@@ -409,7 +409,7 @@ export function OrderSummary({
409
409
  <span className="text-muted-foreground">
410
410
  {methodFeeLabel ?? cart.payment_method_fee_label ?? labels.paymentMethodFee}
411
411
  </span>
412
- <DualPrice
412
+ <Price
413
413
  amount={methodFeeAmount}
414
414
  currencyCode={cart.currency_code}
415
415
  className="font-medium text-foreground"
@@ -422,7 +422,7 @@ export function OrderSummary({
422
422
  <span>{labels.discount}</span>
423
423
  <span className="font-medium">
424
424
  -
425
- <DualPrice
425
+ <Price
426
426
  amount={cart.discount_total}
427
427
  currencyCode={cart.currency_code}
428
428
  />
@@ -450,7 +450,7 @@ export function OrderSummary({
450
450
  {labels.taxTooltip}
451
451
  </span>
452
452
  </span>
453
- <DualPrice
453
+ <Price
454
454
  amount={cart.tax_total ?? 0}
455
455
  currencyCode={cart.currency_code}
456
456
  className="font-medium text-foreground"
@@ -464,11 +464,10 @@ export function OrderSummary({
464
464
  {labels.total}
465
465
  </span>
466
466
  <div className="text-right">
467
- <DualPrice
467
+ <Price
468
468
  amount={displayTotal ?? 0}
469
469
  currencyCode={cart.currency_code}
470
470
  className="text-2xl font-bold text-foreground tracking-tight"
471
- bgnClassName="ml-2 text-sm text-muted-foreground/70 font-normal"
472
471
  />
473
472
  </div>
474
473
  </div>
@@ -482,7 +481,7 @@ export function OrderSummary({
482
481
  <span>{labels.giftCardApplied}</span>
483
482
  <span className="font-medium">
484
483
  -
485
- <DualPrice
484
+ <Price
486
485
  amount={giftCardTotal}
487
486
  currencyCode={cart.currency_code}
488
487
  />
@@ -492,7 +491,7 @@ export function OrderSummary({
492
491
  <span className="text-muted-foreground">
493
492
  {labels.giftCardRemaining}
494
493
  </span>
495
- <DualPrice
494
+ <Price
496
495
  amount={giftCardRemainder}
497
496
  currencyCode={cart.currency_code}
498
497
  className="font-semibold text-foreground"