@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.
- package/package.json +7 -8
- package/src/cart-drawer/cross-sell-carousel.tsx +2 -3
- package/src/cart-drawer/gift-wrap.tsx +82 -83
- package/src/cart-drawer/index.ts +0 -1
- package/src/cart-drawer/item/index.tsx +4 -6
- package/src/cart-drawer/sticky-footer.tsx +73 -73
- package/src/checkout/address-error-copy.ts +117 -119
- package/src/checkout/discount-section.tsx +218 -218
- package/src/checkout/error-copy-codes.ts +63 -0
- package/src/checkout/gift-card-section.tsx +4 -4
- package/src/checkout/index.ts +4 -5
- package/src/checkout/labels.ts +218 -0
- package/src/checkout/line-item-card.tsx +3 -3
- package/src/checkout/order-summary.tsx +11 -12
- package/src/checkout/payment-button.tsx +372 -373
- package/src/checkout/payment-error-copy.ts +154 -269
- package/src/checkout/promotion-error-copy.ts +91 -124
- package/src/checkout/shipping-method-list.tsx +2 -2
- package/src/checkout/use-checkout-orchestration.ts +10 -8
- package/src/lib/money.ts +1 -1
- package/src/lib/price.tsx +39 -0
- package/src/locales/bg.ts +468 -0
- package/src/locales/en.ts +26 -0
- package/src/locales/es.ts +467 -0
- package/src/locales/index.ts +19 -0
- package/src/locales/provider.tsx +63 -0
- package/src/locales/types.ts +49 -0
- package/src/order/index.ts +0 -1
- package/src/order/order-delivery-card.tsx +2 -2
- package/src/order/order-item.tsx +3 -3
- package/src/order/order-totals.tsx +4 -4
- package/src/products/index.ts +0 -1
- package/src/reviews-ui/index.ts +0 -1
- package/src/store/index.ts +0 -1
- package/src/tracking/attribution.ts +13 -0
- package/src/tracking/chatgpt-pixel.tsx +92 -0
- package/src/tracking/consent-banner.tsx +5 -1
- package/src/tracking/consent.ts +12 -0
- package/src/tracking/events.ts +56 -2
- package/src/tracking/index.ts +12 -0
- package/src/tracking/oaiq.ts +206 -0
- package/src/tracking/storefront-tags.tsx +2 -0
- package/src/tracking/types.ts +4 -0
- package/src/cart-drawer/labels-bg.ts +0 -72
- package/src/checkout/labels-bg.ts +0 -128
- package/src/lib/dual-price.tsx +0 -73
- package/src/order/labels-bg.ts +0 -39
- package/src/products/labels-bg.ts +0 -35
- package/src/reviews-ui/labels-bg.ts +0 -91
- package/src/store/labels-bg.ts +0 -22
package/src/checkout/labels.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
-
<
|
|
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 {
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
494
|
+
<Price
|
|
496
495
|
amount={giftCardRemainder}
|
|
497
496
|
currencyCode={cart.currency_code}
|
|
498
497
|
className="font-semibold text-foreground"
|