@cartbase/storefront 0.1.2 → 0.2.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/README.md +3 -3
- package/package.json +231 -226
- package/src/api/auth.ts +1 -1
- package/src/api/consent.ts +2 -2
- package/src/api/regions.ts +1 -1
- package/src/cart-drawer/context.tsx +4 -4
- package/src/cart-drawer/cross-sell-carousel.tsx +1 -1
- package/src/cart-drawer/cross-sell-sidebar.tsx +1 -1
- package/src/cart-drawer/header.tsx +1 -1
- package/src/cart-drawer/item/index.tsx +2 -2
- package/src/cart-drawer/item/quantity.tsx +2 -2
- package/src/cart-drawer/item/upsell.tsx +3 -3
- package/src/cart-drawer/item/variant.tsx +2 -2
- package/src/cart-drawer/labels-bg.ts +1 -1
- package/src/cart-drawer/labels.ts +2 -2
- package/src/cart-drawer/summary-breakdown.tsx +2 -2
- package/src/cart-drawer/template.tsx +2 -2
- package/src/cart-drawer/tiered-progress.tsx +1 -1
- package/src/checkout/address-error-copy.ts +3 -3
- package/src/checkout/address-form.tsx +2 -2
- package/src/checkout/address-select.tsx +1 -1
- package/src/checkout/boxnow-locker-selector.tsx +3 -3
- package/src/checkout/checkout-client.tsx +2 -2
- package/src/checkout/company-details.tsx +1 -1
- package/src/checkout/compare-addresses.ts +1 -1
- package/src/checkout/discount-section.tsx +5 -5
- package/src/checkout/econt-office-selector.tsx +2 -2
- package/src/checkout/gift-card-section.tsx +2 -2
- package/src/checkout/labels-bg.ts +1 -1
- package/src/checkout/labels.ts +4 -4
- package/src/checkout/line-item-card.tsx +2 -2
- package/src/checkout/order-summary.tsx +7 -7
- package/src/checkout/payment-button.tsx +4 -4
- package/src/checkout/payment-error-copy.ts +6 -6
- package/src/checkout/payment-method-list.tsx +2 -2
- package/src/checkout/payment-wrapper.tsx +8 -11
- package/src/checkout/promotion-error-copy.ts +2 -2
- package/src/checkout/shipping-method-list.tsx +2 -2
- package/src/checkout/stripe-wrapper.tsx +1 -1
- package/src/checkout/use-checkout-orchestration.ts +16 -16
- package/src/common/cart-button-client.tsx +1 -1
- package/src/common/cart-button.tsx +1 -1
- package/src/common/country-select.tsx +2 -2
- package/src/common/delete-button.tsx +1 -1
- package/src/common/index.ts +1 -1
- package/src/common/language-select.tsx +2 -2
- package/src/common/localized-link.tsx +1 -1
- package/src/lib/cart-helpers.ts +2 -2
- package/src/lib/cookie-names.ts +41 -0
- package/src/lib/get-product-price.ts +3 -3
- package/src/lib/payment-constants.ts +2 -2
- package/src/lib/store-api-error.ts +1 -1
- package/src/order/index.ts +1 -1
- package/src/order/labels.ts +3 -3
- package/src/order/order-address-card.tsx +1 -1
- package/src/order/order-completed-template.tsx +3 -3
- package/src/order/order-confirmation-header.tsx +1 -1
- package/src/order/order-delivery-card.tsx +3 -3
- package/src/order/order-item.tsx +2 -2
- package/src/order/order-items-list.tsx +3 -3
- package/src/order/order-payment-card.tsx +2 -2
- package/src/order/order-timeline.tsx +2 -2
- package/src/order/order-totals.tsx +5 -5
- package/src/platform/identity.ts +18 -0
- package/src/platform/index.ts +8 -0
- package/src/platform/metadata.ts +26 -0
- package/src/platform/platform-init.tsx +46 -0
- package/src/products/image-gallery.tsx +1 -1
- package/src/products/mobile-actions.tsx +1 -1
- package/src/products/option-select.tsx +1 -1
- package/src/products/preview-price.tsx +1 -1
- package/src/products/product-actions.tsx +6 -6
- package/src/products/product-info.tsx +1 -1
- package/src/products/product-preview.tsx +1 -1
- package/src/products/product-price.tsx +2 -2
- package/src/products/product-tabs.tsx +1 -1
- package/src/products/purchase-options.tsx +1 -1
- package/src/products/related-products.tsx +3 -3
- package/src/products/variant-matching.ts +4 -4
- package/src/reviews-ui/labels.ts +1 -1
- package/src/reviews-ui/photo-upload.tsx +1 -1
- package/src/reviews-ui/review-list.tsx +1 -1
- package/src/reviews-ui/review-widget.tsx +1 -1
- package/src/reviews-ui/review-wizard.tsx +1 -1
- package/src/reviews-ui/wizard-state.ts +1 -1
- package/src/store/category-template.tsx +2 -2
- package/src/store/collection-template.tsx +2 -2
- package/src/store/labels.ts +2 -2
- package/src/store/paginated-products.tsx +1 -1
- package/src/store/search-params.ts +1 -1
- package/src/store/search-template.tsx +1 -1
- package/src/tracking/consent-banner.tsx +1 -1
- package/src/tracking/consent-init.tsx +1 -1
- package/src/tracking/consent.ts +6 -6
- package/src/tracking/fbq.ts +2 -2
- package/src/tracking/get-tracking-attribution.ts +1 -1
- package/src/tracking/get-tracking-config.ts +2 -2
- package/src/tracking/index.ts +2 -2
- package/src/tracking/meta-pixel.tsx +1 -1
- package/src/tracking/rybbit.tsx +1 -1
- package/src/tracking/types.ts +6 -6
|
@@ -6,7 +6,7 @@ import { useCartDrawer } from "./context"
|
|
|
6
6
|
/**
|
|
7
7
|
* Cart drawer header — store title, item count badge, close button.
|
|
8
8
|
*
|
|
9
|
-
* Ported from `@1click/ui/src/cart-drawer/header.tsx` (v2.3.1).
|
|
9
|
+
* Ported from `@1click/ui/src/cart-drawer/header.tsx` (v2.3.1). Cartbase
|
|
10
10
|
* adjustment: the badge counts PRODUCT lines via the shared
|
|
11
11
|
* `productItemCount` predicate (the source summed all lines) so a
|
|
12
12
|
* backend-injected COD-fee line can never inflate the item count —
|
|
@@ -15,13 +15,13 @@ import { CartItemVariant } from "./variant"
|
|
|
15
15
|
* CartItem — a single cart line row: thumbnail, title, variant, quantity
|
|
16
16
|
* stepper, price (with dual-currency + optional strikethrough), remove button.
|
|
17
17
|
*
|
|
18
|
-
* Ported from `@1click/ui/src/cart-drawer/item/index.tsx` (v2.3.1).
|
|
18
|
+
* Ported from `@1click/ui/src/cart-drawer/item/index.tsx` (v2.3.1). Cartbase
|
|
19
19
|
* data seam: `item` is the SDK's decorated `CartLineItem`
|
|
20
20
|
* (`@cartbase/storefront/api/carts`) — per-line `total`/`original_total` are
|
|
21
21
|
* server-computed EUR major units. Remove goes through the provider's
|
|
22
22
|
* SDK-wired `removeItem` (DELETE line-items/:id, returns the full decorated
|
|
23
23
|
* cart) instead of the Medusa server action. The variant row renders the
|
|
24
|
-
* line's flat `variant_title` —
|
|
24
|
+
* line's flat `variant_title` — Cartbase cart lines carry the variant title
|
|
25
25
|
* string, not an embedded variant object.
|
|
26
26
|
*
|
|
27
27
|
* `children` is rendered below the main row — intended for per-item upsell
|
|
@@ -15,9 +15,9 @@ import { useCartDrawer } from "../context"
|
|
|
15
15
|
* automatically (confirmed snapshot unchanged).
|
|
16
16
|
*
|
|
17
17
|
* Ported from `@1click/ui/src/cart-drawer/item/quantity.tsx` (v2.3.1).
|
|
18
|
-
*
|
|
18
|
+
* Cartbase data seam: `updateQuantity` wraps
|
|
19
19
|
* `POST /api/store/carts/:id/line-items/:lineId` with body `{quantity}` —
|
|
20
|
-
* the
|
|
20
|
+
* the Cartbase contract accepts ONLY `quantity` (no metadata on update) and
|
|
21
21
|
* quantity 0 deletes the line (unreachable here: the stepper floor is 1;
|
|
22
22
|
* removal is the dedicated remove button).
|
|
23
23
|
*/
|
|
@@ -12,12 +12,12 @@ import { useCartDrawer } from "../context"
|
|
|
12
12
|
* onAdd handler.
|
|
13
13
|
*
|
|
14
14
|
* Ported from `@1click/ui/src/cart-drawer/item/upsell.tsx` (v2.3.1).
|
|
15
|
-
*
|
|
15
|
+
* Cartbase data seam: source stores fed this from Medusa product fetches —
|
|
16
16
|
* feed it from `@cartbase/storefront/api/search` `listRelatedProducts`
|
|
17
17
|
* (per-anchor complements: manual admin picks + deterministic fallback)
|
|
18
18
|
* mapped via `toCrossSellProduct` (`../cross-sell-sidebar`). `variantId`
|
|
19
19
|
* was added to `UpsellProduct` and to the `onAdd` signature so the handler
|
|
20
|
-
* can call the provider's `addItem(variantId)` directly —
|
|
20
|
+
* can call the provider's `addItem(variantId)` directly — Cartbase carts add
|
|
21
21
|
* by variant id, not product id. Prices are EUR major units.
|
|
22
22
|
*/
|
|
23
23
|
|
|
@@ -28,7 +28,7 @@ export type UpsellProduct = {
|
|
|
28
28
|
thumbnail: string | null
|
|
29
29
|
price: number
|
|
30
30
|
currencyCode: string
|
|
31
|
-
/** Variant to add (
|
|
31
|
+
/** Variant to add (Cartbase add-to-cart is by variant id). */
|
|
32
32
|
variantId?: string
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* under the product title in a cart line.
|
|
6
6
|
*
|
|
7
7
|
* Ported from `@1click/ui/src/cart-drawer/item/variant.tsx` (v2.3.1).
|
|
8
|
-
*
|
|
9
|
-
* embedded Medusa variant object;
|
|
8
|
+
* Cartbase data seam: the source rendered `variant.options[].value` from an
|
|
9
|
+
* embedded Medusa variant object; Cartbase cart lines carry the flat
|
|
10
10
|
* `variant_title` string (SDK `CartLineItem.variant_title`) instead, so
|
|
11
11
|
* that is the primary prop. Callers that fetched the full variant (PDP
|
|
12
12
|
* context) can still pass structural `options` and get the original
|
|
@@ -6,7 +6,7 @@ import type { CartDrawerLabels } from "./labels"
|
|
|
6
6
|
* Ships alongside the English defaults so Bulgarian stores get the
|
|
7
7
|
* translated copy with a single import instead of hand-writing every
|
|
8
8
|
* string. Strings match the originals from mindpages-storefront, plus the
|
|
9
|
-
*
|
|
9
|
+
* Cartbase summary-breakdown row additions.
|
|
10
10
|
*
|
|
11
11
|
* Usage in a store's layout:
|
|
12
12
|
*
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* `CartDrawerProvider`. English is the baseline; stores wanting Bulgarian,
|
|
7
7
|
* German, etc. pass their own translations.
|
|
8
8
|
*
|
|
9
|
-
* Ported from `@1click/ui/src/cart-drawer/labels.ts` (v2.3.1).
|
|
9
|
+
* Ported from `@1click/ui/src/cart-drawer/labels.ts` (v2.3.1). Cartbase
|
|
10
10
|
* additions: the summary-breakdown row labels (`discount`, `tax`, `codFee`,
|
|
11
11
|
* `giftCard`, `remainingToPay`) — the decorated cart carries
|
|
12
12
|
* discount/tax/COD-fee/gift-card tender fields that the breakdown renders,
|
|
@@ -55,7 +55,7 @@ export type CartDrawerLabels = {
|
|
|
55
55
|
secureCheckout: string
|
|
56
56
|
shippingAndTaxCalculatedAtCheckout: string
|
|
57
57
|
|
|
58
|
-
// Summary breakdown rows (
|
|
58
|
+
// Summary breakdown rows (Cartbase decorated-cart fields)
|
|
59
59
|
discount: string
|
|
60
60
|
tax: string
|
|
61
61
|
/** Fallback only — the server's `cod_fee_label` wins when present. */
|
|
@@ -12,8 +12,8 @@ import { useCartDrawer } from "./context"
|
|
|
12
12
|
* pre-checkout context).
|
|
13
13
|
*
|
|
14
14
|
* Ported from `@1click/ui/src/cart-drawer/summary-breakdown.tsx` (v2.3.1)
|
|
15
|
-
* and EXPANDED for
|
|
16
|
-
* subtotal only;
|
|
15
|
+
* and EXPANDED for Cartbase: the source rendered a client-summed product
|
|
16
|
+
* subtotal only; Cartbase's decorated cart carries server-computed totals on
|
|
17
17
|
* every read (`subtotal`, `discount_total`, `shipping_total`, `tax_total`,
|
|
18
18
|
* `cod_fee_total`/`cod_fee_label`, `gift_cards[]`, `gift_card_total`,
|
|
19
19
|
* `gift_card_remainder`, `total` — all EUR major units), so this component
|
|
@@ -29,7 +29,7 @@ import { isProductLine } from "../lib/cart-helpers"
|
|
|
29
29
|
* body inside a `<CartDrawer>` shell. That way the store owns its
|
|
30
30
|
* composition and still gets library updates to every primitive.
|
|
31
31
|
*
|
|
32
|
-
* Ported from `@1click/ui/src/cart-drawer/template.tsx` (v2.3.1).
|
|
32
|
+
* Ported from `@1click/ui/src/cart-drawer/template.tsx` (v2.3.1). Cartbase
|
|
33
33
|
* adaptations:
|
|
34
34
|
* - Amounts are EUR major units — `rewards.pointsPerCurrency` is points
|
|
35
35
|
* per 1 EUR (the source's `/100` minor-unit division is gone), and
|
|
@@ -130,7 +130,7 @@ export function CartDrawerTemplate({
|
|
|
130
130
|
.finally(() => setCrossSellLoaded(true))
|
|
131
131
|
}, [hasItems, crossSellLoaded, config.crossSell?.loader])
|
|
132
132
|
|
|
133
|
-
// Cross-sell add → the provider's SDK addItem (
|
|
133
|
+
// Cross-sell add → the provider's SDK addItem (Cartbase adds by variant
|
|
134
134
|
// id). Cards built without a variantId no-op.
|
|
135
135
|
const handleCrossSellAdd = useCallback(
|
|
136
136
|
(_productId: string, variantId?: string) => {
|
|
@@ -11,7 +11,7 @@ import { useCartDrawer } from "./context"
|
|
|
11
11
|
* originally extracted from mindpages-storefront
|
|
12
12
|
* src/modules/cart-drawer/cart-tiered-progress.tsx.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
14
|
+
* Cartbase money note: `cart.total` and tier `threshold`s are EUR decimal
|
|
15
15
|
* major units (e.g. `{ threshold: 50 }` = €50) — configure tiers in major
|
|
16
16
|
* units, not cents.
|
|
17
17
|
*/
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* checkout address surface.
|
|
4
4
|
*
|
|
5
5
|
* Ported from `@1click/ui/src/checkout/address-error-copy.ts` (v2.3.1) and
|
|
6
|
-
* adapted to the
|
|
6
|
+
* adapted to the Cartbase error envelope: `StoreApiError` carries a structured
|
|
7
7
|
* `code` (POST /api/store/carts/:id — docs/storefront/carts.md), so
|
|
8
8
|
* translation is code-first; the substring patterns stay as the fallback for
|
|
9
9
|
* network failures and any legacy wire.
|
|
@@ -20,7 +20,7 @@ const NETWORK_FALLBACK =
|
|
|
20
20
|
"Няма връзка със сървъра. Моля, проверете интернет връзката и опитайте отново."
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
23
|
+
* Cartbase cart-update error codes → BG copy (POST /api/store/carts/:id,
|
|
24
24
|
* docs/storefront/carts.md). Code-first, matched before any pattern.
|
|
25
25
|
*/
|
|
26
26
|
export const ADDRESS_ERROR_CODE_COPY: Record<string, string> = {
|
|
@@ -76,7 +76,7 @@ const PATTERNS: Array<{ match: RegExp; copy: string }> = [
|
|
|
76
76
|
copy: NETWORK_FALLBACK,
|
|
77
77
|
},
|
|
78
78
|
// The Next.js canned Server Components production message — surface a
|
|
79
|
-
// clean fallback instead of the English placeholder. (The
|
|
79
|
+
// clean fallback instead of the English placeholder. (The Cartbase SDK
|
|
80
80
|
// throws from the browser, so this only fires for stores that proxy the
|
|
81
81
|
// save through a server action.)
|
|
82
82
|
{
|
|
@@ -17,9 +17,9 @@ import { ErrorMessage } from "./error-message"
|
|
|
17
17
|
* (`CheckoutClient` / `useCheckoutOrchestration`).
|
|
18
18
|
*
|
|
19
19
|
* Ported from `@1click/ui/src/checkout/address-form.tsx` (v2.3.1) with the
|
|
20
|
-
*
|
|
20
|
+
* Cartbase type seam only: `@medusajs/types` → the SDK's `CustomerAddress` /
|
|
21
21
|
* `CartAddress` partials. The `countries` list comes from the orchestration
|
|
22
|
-
* hook's `regionCountries` —
|
|
22
|
+
* hook's `regionCountries` — Cartbase regions carry NO embedded countries
|
|
23
23
|
* array (api/regions.ts divergence), so the host supplies the list via the
|
|
24
24
|
* hook's `countries` option (single-entry `countryCode` fallback).
|
|
25
25
|
*/
|
|
@@ -15,7 +15,7 @@ import { useCheckoutLabels } from "./context"
|
|
|
15
15
|
* replace this one file locally.
|
|
16
16
|
*
|
|
17
17
|
* Ported from `@1click/ui/src/checkout/address-select.tsx` (v2.3.1) with
|
|
18
|
-
* the
|
|
18
|
+
* the Cartbase type seam: `@medusajs/types` address DTOs → the SDK's
|
|
19
19
|
* `CustomerAddress` (GET /api/store/customers/me — addresses embedded,
|
|
20
20
|
* docs/storefront/customers.md) and `CartAddress` partials. Field names are
|
|
21
21
|
* identical across both wires, so the compare/format logic is unchanged.
|
|
@@ -17,11 +17,11 @@ import { useCheckoutLabels } from "./context"
|
|
|
17
17
|
* BoxNowLockerSelector — full BoxNow locker picker for Bulgarian shipping.
|
|
18
18
|
*
|
|
19
19
|
* Ported from `@1click/ui/src/checkout/boxnow-locker-selector.tsx` (v2.3.1)
|
|
20
|
-
* with the
|
|
20
|
+
* with the Cartbase data seam: the locker directory comes from
|
|
21
21
|
* `listBoxNowLockers(client)` (`@cartbase/storefront/api/integrations`,
|
|
22
22
|
* GET /api/store/integrations/boxnow/lockers — backend owns BoxNow API auth
|
|
23
23
|
* + the 10-min cache; docs/storefront/integrations.md). The @1click data
|
|
24
|
-
* helper returned `{ok, lockers}`; the
|
|
24
|
+
* helper returned `{ok, lockers}`; the Cartbase SDK THROWS on 503 (not
|
|
25
25
|
* configured) / 502 (carrier failure), so the fetch wrapper catches and
|
|
26
26
|
* maps both to the same "temporarily unavailable" state. Availability
|
|
27
27
|
* should be discovered via `carriers.boxnow.lockers_url` on the
|
|
@@ -38,7 +38,7 @@ import { useCheckoutLabels } from "./context"
|
|
|
38
38
|
* - title (not name)
|
|
39
39
|
* - addressLine1 / addressLine2 (not address.street/num)
|
|
40
40
|
* - postalCode (not address.city.name)
|
|
41
|
-
* - lat / lng on the root (numbers or null — the
|
|
41
|
+
* - lat / lng on the root (numbers or null — the Cartbase route coerces
|
|
42
42
|
* malformed carrier coordinates to null, never NaN)
|
|
43
43
|
* - No isAPS / code / phones
|
|
44
44
|
*
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
* automatically.
|
|
28
28
|
*
|
|
29
29
|
* Ported from `@1click/ui/src/checkout/checkout-client.tsx` (v2.3.1) with
|
|
30
|
-
* the
|
|
30
|
+
* the Cartbase data seam:
|
|
31
31
|
*
|
|
32
32
|
* - Takes the SDK `client` — every server call (address autosave,
|
|
33
33
|
* prepare-checkout, sync, complete, promo/gift-card apply, quantity
|
|
@@ -55,7 +55,7 @@ type CheckoutClientProps = {
|
|
|
55
55
|
availableShippingMethods: StoreShippingOption[] | null
|
|
56
56
|
availablePaymentMethods: PaymentProviderLike[] | null
|
|
57
57
|
countryCode: string
|
|
58
|
-
/** Countries for the address form (
|
|
58
|
+
/** Countries for the address form (Cartbase regions embed none). */
|
|
59
59
|
countries?: Array<{ iso_2: string; display_name: string }>
|
|
60
60
|
paymentMethodFilter?: (
|
|
61
61
|
methods: PaymentProviderLike[] | null,
|
|
@@ -13,7 +13,7 @@ import { useCheckoutLabels } from "./context"
|
|
|
13
13
|
* Ported from `@1click/ui/src/checkout/company-details.tsx` (v2.3.1)
|
|
14
14
|
* unchanged — pure form UI; the values ride `formData` and the
|
|
15
15
|
* orchestration hook persists them into `cart.metadata`
|
|
16
|
-
* (company_name/company_vat/company_mol/company_address).
|
|
16
|
+
* (company_name/company_vat/company_mol/company_address). Cartbase upgrade
|
|
17
17
|
* over the @1click KNOWN_ISSUES state: the hook ALSO syncs
|
|
18
18
|
* `company_name`/`company_eik` onto the signed-in customer profile
|
|
19
19
|
* (PATCH /api/store/customers/me — first-class columns,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* shipping-address fields. Returns true if every field matches.
|
|
4
4
|
*
|
|
5
5
|
* Ported from `@1click/ui/src/data/util/compare-addresses.ts` (v2.3.1)
|
|
6
|
-
* unchanged. Lives in the checkout family because the
|
|
6
|
+
* unchanged. Lives in the checkout family because the Cartbase SDK has no
|
|
7
7
|
* data/util layer (the @1click data layer became `@cartbase/storefront/api`).
|
|
8
8
|
* Extra file beyond the pre-declared export list → exported from the
|
|
9
9
|
* family barrel only.
|
|
@@ -15,16 +15,16 @@ import { translatePromotionError } from "./promotion-error-copy"
|
|
|
15
15
|
* amount.
|
|
16
16
|
*
|
|
17
17
|
* Ported from `@1click/ui/src/checkout/discount-section.tsx` (v2.3.1) with
|
|
18
|
-
* the
|
|
18
|
+
* the Cartbase data seam:
|
|
19
19
|
*
|
|
20
20
|
* - `applyPromotions(codes)` (Medusa js-sdk server action, full-list
|
|
21
21
|
* replace) → `POST /api/store/carts/:id/promotions {promo_codes}` — the
|
|
22
|
-
*
|
|
22
|
+
* Cartbase route is ADDITIVE (upserts each code's link, then re-applies),
|
|
23
23
|
* so only the NEW code is sent; already-applied codes stay put. The SDK
|
|
24
24
|
* carts module ships no wrapper for this route yet, so the component
|
|
25
25
|
* calls it through the client transport with the documented DTO
|
|
26
26
|
* (verified against src/app/api/store/carts/[id]/promotions/route.ts).
|
|
27
|
-
* - `cart.promotions` on the
|
|
27
|
+
* - `cart.promotions` on the Cartbase wire is the raw pivot embed
|
|
28
28
|
* (`[{promotion: {..., application_method}}]`) — unwrapped here, with a
|
|
29
29
|
* flat-row fallback should the decoration ever flatten it.
|
|
30
30
|
* - Errors are code-first (`promotion_not_found` / `promotion_inactive` /
|
|
@@ -46,7 +46,7 @@ type AppliedPromotion = {
|
|
|
46
46
|
} | null
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
/** Unwrap the
|
|
49
|
+
/** Unwrap the Cartbase pivot embed; tolerate an already-flat row. */
|
|
50
50
|
function extractPromotions(cart: Cart): AppliedPromotion[] {
|
|
51
51
|
const raw = (cart.promotions ?? []) as Array<Record<string, unknown>>
|
|
52
52
|
return raw
|
|
@@ -84,7 +84,7 @@ export function DiscountSection({
|
|
|
84
84
|
setLoading(true)
|
|
85
85
|
setError("")
|
|
86
86
|
try {
|
|
87
|
-
// Additive apply — the
|
|
87
|
+
// Additive apply — the Cartbase route upserts the new code's link and
|
|
88
88
|
// re-applies every cart promotion; already-applied codes stay.
|
|
89
89
|
const { cart: updated } = await applyPromotions(client, cart.id, [code.trim()])
|
|
90
90
|
onCartChange?.(updated)
|
|
@@ -10,13 +10,13 @@ import { useCheckoutLabels } from "./context"
|
|
|
10
10
|
* EcontOfficeSelector — full Econt office picker for Bulgarian shipping.
|
|
11
11
|
*
|
|
12
12
|
* Ported from `@1click/ui/src/checkout/econt-office-selector.tsx` (v2.3.1)
|
|
13
|
-
* verbatim — this component has NO
|
|
13
|
+
* verbatim — this component has NO Cartbase data seam: the office directory
|
|
14
14
|
* comes straight from Econt's public Nomenclatures endpoint (client-side,
|
|
15
15
|
* cached globally per page load), and the CHOSEN office is handed to the
|
|
16
16
|
* orchestration hook, which writes it into `carrier_metadata` exactly once
|
|
17
17
|
* at Buy click (POST /api/store/carts/:id/prepare-checkout —
|
|
18
18
|
* docs/storefront/checkout.md). The geocode helpers moved into the checkout
|
|
19
|
-
* family (`./geocode`) because the
|
|
19
|
+
* family (`./geocode`) because the Cartbase lib family doesn't ship one.
|
|
20
20
|
*
|
|
21
21
|
* Behavior:
|
|
22
22
|
* - On mount, fetches the full Econt offices list from the public Econt
|
|
@@ -15,8 +15,8 @@ import { translateGiftCardError } from "./payment-error-copy"
|
|
|
15
15
|
* summary card, plus the applied-cards chip list (masked `last4`, per-card
|
|
16
16
|
* coverage, remove).
|
|
17
17
|
*
|
|
18
|
-
* NEW in the
|
|
19
|
-
*
|
|
18
|
+
* NEW in the Cartbase port (no @1click equivalent — gift-card tender is a
|
|
19
|
+
* Cartbase feature, docs/storefront/gift-cards.md). Built in the
|
|
20
20
|
* DiscountSection idiom so the two summary widgets read as one family.
|
|
21
21
|
*
|
|
22
22
|
* Contract discipline baked in:
|
|
@@ -3,7 +3,7 @@ import type { CheckoutLabels } from "./labels"
|
|
|
3
3
|
/**
|
|
4
4
|
* Bulgarian translations for the checkout.
|
|
5
5
|
*
|
|
6
|
-
* Ported from `@1click/ui/src/checkout/labels-bg.ts` (v2.3.1) + the
|
|
6
|
+
* Ported from `@1click/ui/src/checkout/labels-bg.ts` (v2.3.1) + the Cartbase
|
|
7
7
|
* gift-card keys. Strings match the originals from mindpages-storefront's
|
|
8
8
|
* checkout-client monolith.
|
|
9
9
|
*
|
package/src/checkout/labels.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Checkout labels — default English copy.
|
|
3
3
|
*
|
|
4
4
|
* Ported from `@1click/ui/src/checkout/labels.ts` (v2.3.1) with the
|
|
5
|
-
*
|
|
6
|
-
* GiftCardSection (gift cards are a
|
|
5
|
+
* Cartbase additions: gift-card tender labels (`giftCard*`) for the new
|
|
6
|
+
* GiftCardSection (gift cards are a Cartbase feature with no @1click
|
|
7
7
|
* equivalent — docs/storefront/gift-cards.md).
|
|
8
8
|
*
|
|
9
9
|
* Every user-facing string in the checkout module comes from a
|
|
@@ -87,7 +87,7 @@ export type CheckoutLabels = {
|
|
|
87
87
|
discountCode: string
|
|
88
88
|
discountSub: string
|
|
89
89
|
/** Default label for the cash-on-delivery fee row. Used as the third-
|
|
90
|
-
* level fallback when neither the cart's `cod_fee_label` (
|
|
90
|
+
* level fallback when neither the cart's `cod_fee_label` (Cartbase
|
|
91
91
|
* server truth) nor a per-store override is available. */
|
|
92
92
|
codFee: string
|
|
93
93
|
recommended: string
|
|
@@ -96,7 +96,7 @@ export type CheckoutLabels = {
|
|
|
96
96
|
remove: string
|
|
97
97
|
secureCheckout: string
|
|
98
98
|
|
|
99
|
-
// Gift cards (
|
|
99
|
+
// Gift cards (Cartbase gift-card tender — docs/storefront/gift-cards.md)
|
|
100
100
|
giftCard: string
|
|
101
101
|
giftCardSub: string
|
|
102
102
|
giftCardPlaceholder: string
|
|
@@ -14,13 +14,13 @@ import { useCheckoutLabels } from "./context"
|
|
|
14
14
|
* and dual-currency price.
|
|
15
15
|
*
|
|
16
16
|
* Ported from `@1click/ui/src/checkout/line-item-card.tsx` (v2.3.1) with
|
|
17
|
-
* the
|
|
17
|
+
* the Cartbase data seam: the cookie-bound `updateLineItem` server action →
|
|
18
18
|
* `carts.updateLineItem(client, cartId, lineId, {quantity})`
|
|
19
19
|
* (POST /api/store/carts/:id/line-items/:lineId — docs/storefront/
|
|
20
20
|
* carts.md). The route returns the freshly decorated cart; pass
|
|
21
21
|
* `onCartChange` so the host can update its cart state (or
|
|
22
22
|
* `router.refresh()` in an RSC app). `variant.title` → the flat
|
|
23
|
-
* `variant_title` column of the
|
|
23
|
+
* `variant_title` column of the Cartbase line DTO.
|
|
24
24
|
*
|
|
25
25
|
* Checkout is master-level: customers cannot remove items here.
|
|
26
26
|
* Removal lives in the cart drawer only.
|
|
@@ -17,15 +17,15 @@ import { GiftCardSection } from "./gift-card-section"
|
|
|
17
17
|
* tax / total, optional optimistic overrides used during method selection.
|
|
18
18
|
*
|
|
19
19
|
* Ported from `@1click/ui/src/checkout/order-summary.tsx` (v2.3.1) with
|
|
20
|
-
* the
|
|
20
|
+
* the Cartbase data seam:
|
|
21
21
|
*
|
|
22
|
-
* - COD fee is CART-LEVEL decoration on the
|
|
22
|
+
* - COD fee is CART-LEVEL decoration on the Cartbase wire —
|
|
23
23
|
* `cart.cod_fee_total` / `cart.cod_fee_label`, folded into `cart.total`
|
|
24
24
|
* while a live pp_cod session exists (docs/storefront/checkout.md) —
|
|
25
25
|
* NOT a metadata-flagged fee line item. The fee row + optimistic math
|
|
26
26
|
* read those fields; `isProductLine` is kept as a defensive filter for
|
|
27
27
|
* any legacy fee-line carts.
|
|
28
|
-
* - Gift-card tender rows (
|
|
28
|
+
* - Gift-card tender rows (Cartbase feature): `gift_card_total` renders as
|
|
29
29
|
* a negative tender row and `gift_card_remainder` as "Remaining to pay"
|
|
30
30
|
* UNDER the (unchanged) total — tender never moves totals
|
|
31
31
|
* (docs/storefront/gift-cards.md). `GiftCardSection` mounts next to
|
|
@@ -254,7 +254,7 @@ export function OrderSummary({
|
|
|
254
254
|
}: OrderSummaryProps) {
|
|
255
255
|
const labels = useCheckoutLabels()
|
|
256
256
|
|
|
257
|
-
// Product rows only.
|
|
257
|
+
// Product rows only. Cartbase's COD fee is cart-level decoration (never a
|
|
258
258
|
// line item), so this filter is defensive — it also hides any
|
|
259
259
|
// metadata-flagged fee line a legacy/ported cart might still carry.
|
|
260
260
|
const allItems = cart.items ?? []
|
|
@@ -269,7 +269,7 @@ export function OrderSummary({
|
|
|
269
269
|
const shippingKnown = shippingCost !== null && shippingCost !== undefined
|
|
270
270
|
|
|
271
271
|
// Effective COD fee: optimistic prediction wins until the cart catches
|
|
272
|
-
// up. Three states (null / 0 / positive).
|
|
272
|
+
// up. Three states (null / 0 / positive). Cartbase server truth is
|
|
273
273
|
// `cart.cod_fee_total` (folded into cart.total while a pp_cod session
|
|
274
274
|
// lives — docs/storefront/checkout.md).
|
|
275
275
|
const realCodFeeAmount = cart.cod_fee_total ?? 0
|
|
@@ -296,7 +296,7 @@ export function OrderSummary({
|
|
|
296
296
|
realCodFeeAmount,
|
|
297
297
|
])
|
|
298
298
|
|
|
299
|
-
// Subtotal:
|
|
299
|
+
// Subtotal: Cartbase's `item_total` is products-only (the COD fee is not a
|
|
300
300
|
// line item), so no fee subtraction is needed — direct server truth.
|
|
301
301
|
const productSubtotal = cart.item_total ?? 0
|
|
302
302
|
|
|
@@ -318,7 +318,7 @@ export function OrderSummary({
|
|
|
318
318
|
}, [cart.shipping_total, optimisticShippingCost, onOptimisticShippingClear])
|
|
319
319
|
|
|
320
320
|
// Clear optimistic COD fee once the cart's decoration matches the
|
|
321
|
-
// prediction (
|
|
321
|
+
// prediction (Cartbase: cod_fee_total, not a fee line item).
|
|
322
322
|
useEffect(() => {
|
|
323
323
|
if (optimisticCodFee === null || optimisticCodFee === undefined) return
|
|
324
324
|
const synced = realCodFeeAmount === optimisticCodFee
|
|
@@ -18,10 +18,10 @@ const PROCESSING_MESSAGE_INTERVAL_MS = 1800
|
|
|
18
18
|
* PaymentButton — top-level "place order" button.
|
|
19
19
|
*
|
|
20
20
|
* Ported from `@1click/ui/src/checkout/payment-button.tsx` (v2.3.1) with
|
|
21
|
-
* the
|
|
22
|
-
* `currency_code` are read — any decorated
|
|
21
|
+
* the Cartbase data seam: the `cart` prop is structural (only `id`, `total`,
|
|
22
|
+
* `currency_code` are read — any decorated Cartbase cart satisfies it), and
|
|
23
23
|
* the `logCheckoutError` Supabase writer becomes the optional `logError`
|
|
24
|
-
* callback (
|
|
24
|
+
* callback (Cartbase has no store-side log endpoint; every production log
|
|
25
25
|
* point is preserved through the callback).
|
|
26
26
|
*
|
|
27
27
|
* The button delegates entirely to the orchestration hook's
|
|
@@ -270,7 +270,7 @@ export function PaymentButton({
|
|
|
270
270
|
setSubmitting(false)
|
|
271
271
|
} catch (err: unknown) {
|
|
272
272
|
// Next.js's `redirect()` throws a NEXT_REDIRECT error — that is
|
|
273
|
-
// success, not failure. The
|
|
273
|
+
// success, not failure. The Cartbase default path navigates via
|
|
274
274
|
// window.location.assign (no throw), but a store routing
|
|
275
275
|
// `onOrderPlaced` through a server action still hits this; don't
|
|
276
276
|
// translate it — let it propagate so the navigation happens.
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
* Bulgarian copy for the checkout payment surface.
|
|
4
4
|
*
|
|
5
5
|
* Ported from `@1click/ui/src/checkout/payment-error-copy.ts` (v2.3.1) and
|
|
6
|
-
* adapted to the
|
|
6
|
+
* adapted to the Cartbase error envelope: the store API throws `StoreApiError`
|
|
7
7
|
* with a STRUCTURED `code` (docs/storefront/checkout.md lists every code per
|
|
8
8
|
* endpoint), so translation is **code-first** — the substring patterns from
|
|
9
9
|
* the Medusa era are kept as the fallback layer for Stripe.js browser errors
|
|
10
|
-
* (which have no
|
|
10
|
+
* (which have no Cartbase code) and any legacy wire.
|
|
11
11
|
*
|
|
12
12
|
* Why this exists: Stripe and API errors are technical English strings
|
|
13
13
|
* ("Your card's security code is incorrect.", "fetch failed"). Showing them
|
|
@@ -26,7 +26,7 @@ const GENERIC_COD =
|
|
|
26
26
|
"Поръчката не може да бъде финализирана в момента. Моля, опитайте отново след малко."
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
29
|
+
* Cartbase store-API error codes → BG copy. Covers EVERY code documented for
|
|
30
30
|
* POST /api/store/carts/:id/complete, prepare-checkout,
|
|
31
31
|
* sync-payment-amount and refresh-payment-if-terminal
|
|
32
32
|
* (docs/storefront/checkout.md + carts.completeCart JSDoc). Code-first:
|
|
@@ -110,7 +110,7 @@ const GIFT_CARD_GENERIC =
|
|
|
110
110
|
* Map of Stripe error codes / decline codes / common substrings to BG copy.
|
|
111
111
|
* Substring match is case-insensitive. First match wins, so order from
|
|
112
112
|
* most-specific to most-generic. Kept verbatim from the production port —
|
|
113
|
-
* Stripe.js browser errors carry no
|
|
113
|
+
* Stripe.js browser errors carry no Cartbase `code`, so this layer still does
|
|
114
114
|
* the heavy lifting on the card path.
|
|
115
115
|
*
|
|
116
116
|
* Stripe error code reference:
|
|
@@ -203,7 +203,7 @@ const API_PATTERNS: Array<{ match: RegExp; copy: string }> = [
|
|
|
203
203
|
},
|
|
204
204
|
]
|
|
205
205
|
|
|
206
|
-
/** Pull the structured
|
|
206
|
+
/** Pull the structured Cartbase error code out of any thrown value. */
|
|
207
207
|
function extractCode(err: unknown): string | null {
|
|
208
208
|
if (err && typeof err === "object") {
|
|
209
209
|
const code = (err as { code?: unknown }).code
|
|
@@ -214,7 +214,7 @@ function extractCode(err: unknown): string | null {
|
|
|
214
214
|
|
|
215
215
|
/**
|
|
216
216
|
* Translate any thrown / returned error into customer-facing Bulgarian.
|
|
217
|
-
* Code-first (
|
|
217
|
+
* Code-first (Cartbase `StoreApiError.code`), then Stripe substring patterns,
|
|
218
218
|
* then legacy API substrings, then the per-context generic.
|
|
219
219
|
*
|
|
220
220
|
* @param err - the error from a try/catch or a Stripe error response
|
|
@@ -19,9 +19,9 @@ import type { CheckoutLogError } from "./use-checkout-orchestration"
|
|
|
19
19
|
* per-method code here.
|
|
20
20
|
*
|
|
21
21
|
* Ported from `@1click/ui/src/checkout/payment-method-list.tsx` (v2.3.1)
|
|
22
|
-
* with the
|
|
22
|
+
* with the Cartbase data seam: the `cart` prop is structural, and the
|
|
23
23
|
* `logCheckoutError` Supabase writer becomes the optional `logError`
|
|
24
|
-
* callback. Provider semantics on the
|
|
24
|
+
* callback. Provider semantics on the Cartbase wire: the "card" tab is
|
|
25
25
|
* `pp_stripe`, the "cod" tab is `pp_cod` (native fee via the integrations
|
|
26
26
|
* `cod` block) or `pp_manual` (offline, no fee) — the orchestration hook
|
|
27
27
|
* resolves the ids (`hasCard`/`hasCod`/`cardId`/`codId`); both listings
|
|
@@ -10,11 +10,11 @@ import { StripeScopeProvider } from "./stripe-wrapper"
|
|
|
10
10
|
* PaymentWrapper — provides Stripe Elements scope context to descendants.
|
|
11
11
|
*
|
|
12
12
|
* Ported from `@1click/ui/src/checkout/payment-wrapper.tsx` (v2.3.1).
|
|
13
|
-
*
|
|
13
|
+
* Cartbase adaptations: the `cart` prop is the Cartbase `Cart` DTO (only
|
|
14
14
|
* `currency_code` is read — typed structurally so any decorated cart
|
|
15
|
-
* satisfies it);
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* satisfies it); `NEXT_PUBLIC_STRIPE_KEY` is the canonical env name
|
|
16
|
+
* (the Medusa-era fallback was dropped 2026-08-04, before any external
|
|
17
|
+
* consumer existed).
|
|
18
18
|
*
|
|
19
19
|
* As of the deferred-checkout architecture, this wrapper supplies an
|
|
20
20
|
* optimistic `amount` + `currency` (computed by the orchestration hook)
|
|
@@ -30,16 +30,13 @@ import { StripeScopeProvider } from "./stripe-wrapper"
|
|
|
30
30
|
* </PaymentWrapper>
|
|
31
31
|
*
|
|
32
32
|
* Environment variables (read at module load time):
|
|
33
|
-
* - NEXT_PUBLIC_STRIPE_KEY
|
|
34
|
-
*
|
|
35
|
-
* - NEXT_PUBLIC_MEDUSA_PAYMENTS_ACCOUNT_ID (optional, connect accounts)
|
|
33
|
+
* - NEXT_PUBLIC_STRIPE_KEY
|
|
34
|
+
* - NEXT_PUBLIC_STRIPE_ACCOUNT_ID (optional, connect accounts)
|
|
36
35
|
*/
|
|
37
36
|
|
|
38
|
-
const stripeKey =
|
|
39
|
-
process.env.NEXT_PUBLIC_STRIPE_KEY ||
|
|
40
|
-
process.env.NEXT_PUBLIC_MEDUSA_PAYMENTS_PUBLISHABLE_KEY
|
|
37
|
+
const stripeKey = process.env.NEXT_PUBLIC_STRIPE_KEY
|
|
41
38
|
|
|
42
|
-
const stripeAccountId = process.env.
|
|
39
|
+
const stripeAccountId = process.env.NEXT_PUBLIC_STRIPE_ACCOUNT_ID
|
|
43
40
|
|
|
44
41
|
const stripePromise = stripeKey
|
|
45
42
|
? loadStripe(
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* checkout discount-code surface.
|
|
4
4
|
*
|
|
5
5
|
* Ported from `@1click/ui/src/checkout/promotion-error-copy.ts` (v2.3.1)
|
|
6
|
-
* and adapted to the
|
|
6
|
+
* and adapted to the Cartbase error envelope: the cart-promotions route
|
|
7
7
|
* (POST /api/store/carts/:id/promotions) throws structured codes —
|
|
8
8
|
* `promotion_not_found`, `promotion_inactive`, `promotion_misconfigured`,
|
|
9
9
|
* `promotion_unsupported_type`, plus the cart-level `cart_not_found` /
|
|
@@ -34,7 +34,7 @@ const EXHAUSTED =
|
|
|
34
34
|
"Промоцията е изчерпана и вече не може да бъде използвана."
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
|
-
*
|
|
37
|
+
* Cartbase promotion error codes → BG copy (POST/DELETE
|
|
38
38
|
* /api/store/carts/:id/promotions + src/lib/promotions/apply.ts).
|
|
39
39
|
*/
|
|
40
40
|
export const PROMOTION_ERROR_CODE_COPY: Record<string, string> = {
|
|
@@ -19,7 +19,7 @@ import { ErrorMessage } from "./error-message"
|
|
|
19
19
|
* matching picker expands inline inside that row.
|
|
20
20
|
*
|
|
21
21
|
* Ported from `@1click/ui/src/checkout/shipping-method-list.tsx` (v2.3.1)
|
|
22
|
-
* with the
|
|
22
|
+
* with the Cartbase data seam: options are `StoreShippingOption` rows from
|
|
23
23
|
* `checkout.listShippingOptions(client, {cart_id})` — rule-filtered and
|
|
24
24
|
* display-ordered SERVER-SIDE (checkout rules + `checkout_method_order`,
|
|
25
25
|
* docs/storefront/checkout.md); this component only renders what arrives.
|
|
@@ -183,7 +183,7 @@ export function CheckoutShippingMethodList({
|
|
|
183
183
|
// mid-courier-switch — the in-flight switch would otherwise
|
|
184
184
|
// leave a brief window where prices are calculated=true but
|
|
185
185
|
// the new option's price hasn't landed in the map yet,
|
|
186
|
-
// making it look broken. (
|
|
186
|
+
// making it look broken. (Cartbase serves flat prices today;
|
|
187
187
|
// the calculated branch is forward-compat —
|
|
188
188
|
// docs/storefront/checkout.md.)
|
|
189
189
|
const cantCalc =
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
*
|
|
19
19
|
* Ported from `@1click/ui/src/checkout/stripe-wrapper.tsx` (v2.3.1)
|
|
20
20
|
* unchanged — no data seam; the PaymentIntent seam moved server-side into
|
|
21
|
-
*
|
|
21
|
+
* Cartbase's POST /api/store/carts/:id/prepare-checkout.
|
|
22
22
|
*
|
|
23
23
|
* `<Elements>` is mounted with `mode: 'payment'`, `amount`, `currency` — NO
|
|
24
24
|
* PaymentIntent exists on the backend yet. The PI is minted at Buy click by
|