@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
|
@@ -54,7 +54,7 @@ import type { BoxNowLocker } from "../api/integrations"
|
|
|
54
54
|
* persistence, custom summary).
|
|
55
55
|
*
|
|
56
56
|
* Ported from `@1click/ui/src/checkout/use-checkout-orchestration.ts`
|
|
57
|
-
* (v2.3.1, the deferred-intent architecture) with the
|
|
57
|
+
* (v2.3.1, the deferred-intent architecture) with the Cartbase data seam:
|
|
58
58
|
*
|
|
59
59
|
* - Server actions → `@cartbase/storefront/api` calls on a caller-supplied
|
|
60
60
|
* `StorefrontClient` (`updateCart`, `prepareCheckout`,
|
|
@@ -63,14 +63,14 @@ import type { BoxNowLocker } from "../api/integrations"
|
|
|
63
63
|
* - `placeOrder` (server action with baked-in redirect) →
|
|
64
64
|
* `completeCart()` + template navigation (`orderConfirmedPath` with
|
|
65
65
|
* `{id}`/`{country}` substitution) or the `onOrderPlaced` callback.
|
|
66
|
-
* - **Zero-remainder gift path** (
|
|
66
|
+
* - **Zero-remainder gift path** (Cartbase gift-card tender,
|
|
67
67
|
* docs/storefront/checkout.md): when applied gift cards cover the whole
|
|
68
68
|
* total, `prepareCheckout` returns `client_secret: null` +
|
|
69
69
|
* `provider_id: null` and the card path SKIPS `stripe.confirmPayment`
|
|
70
70
|
* entirely — the cart completes on the gift session alone. The @1click
|
|
71
|
-
* original threw on a missing client_secret;
|
|
71
|
+
* original threw on a missing client_secret; Cartbase treats
|
|
72
72
|
* null-secret + null-provider as the documented gift path.
|
|
73
|
-
* - COD fee:
|
|
73
|
+
* - COD fee: Cartbase's fee is CART-LEVEL decoration (`cod_fee_total` /
|
|
74
74
|
* `cod_fee_label`, folded into `cart.total` while a live pp_cod session
|
|
75
75
|
* exists) — not a metadata-flagged line item. The optimistic-fee math
|
|
76
76
|
* reads `cart.cod_fee_total`; the prediction comes from the
|
|
@@ -78,9 +78,9 @@ import type { BoxNowLocker } from "../api/integrations"
|
|
|
78
78
|
* - Provider ids: `pp_stripe` / `pp_cod` / `pp_manual` exactly
|
|
79
79
|
* (lib/payment-constants + the pp_cod resolution below).
|
|
80
80
|
* - `logCheckoutError`/`logEvent` (Supabase-side sinks in @1click) have no
|
|
81
|
-
*
|
|
81
|
+
* Cartbase store endpoint — the hook takes an optional `logError` callback
|
|
82
82
|
* so stores wire their own sink; all production log points are kept.
|
|
83
|
-
* -
|
|
83
|
+
* - Cartbase regions carry NO embedded countries array (api/regions.ts
|
|
84
84
|
* divergence note) — the country list is a `countries` option with a
|
|
85
85
|
* `countryCode` single-entry fallback.
|
|
86
86
|
*
|
|
@@ -115,7 +115,7 @@ export type UseCheckoutOrchestrationOptions = {
|
|
|
115
115
|
/** Default country code when the cart has no shipping address yet. */
|
|
116
116
|
countryCode?: string
|
|
117
117
|
/**
|
|
118
|
-
* Countries offered in the address form.
|
|
118
|
+
* Countries offered in the address form. Cartbase regions do NOT embed a
|
|
119
119
|
* countries array (store-API divergence), so the host app supplies the
|
|
120
120
|
* list; omitted → a single entry derived from `countryCode`.
|
|
121
121
|
*/
|
|
@@ -159,7 +159,7 @@ export type UseCheckoutOrchestrationOptions = {
|
|
|
159
159
|
resolveTrackingMetadata?: () => Record<string, unknown> | undefined
|
|
160
160
|
/**
|
|
161
161
|
* Operational-visibility sink (successor of @1click's logCheckoutError /
|
|
162
|
-
* logEvent Supabase writers —
|
|
162
|
+
* logEvent Supabase writers — Cartbase has no store-side log endpoint).
|
|
163
163
|
* Called with (errorType, message, context). Optional; defaults to no-op.
|
|
164
164
|
*/
|
|
165
165
|
logError?: CheckoutLogError
|
|
@@ -171,7 +171,7 @@ export type UseCheckoutOrchestrationOptions = {
|
|
|
171
171
|
*
|
|
172
172
|
* `shipping_address.phone` is required — Bulgarian carriers (Econt,
|
|
173
173
|
* BoxNow) need it to contact the customer; it's the courier's primary
|
|
174
|
-
* recovery channel when the address is ambiguous. (
|
|
174
|
+
* recovery channel when the address is ambiguous. (Cartbase's
|
|
175
175
|
* prepare-checkout schema requires it too — docs/storefront/checkout.md.)
|
|
176
176
|
*/
|
|
177
177
|
const REQUIRED_ADDRESS_FIELDS = [
|
|
@@ -194,7 +194,7 @@ const REQUIRED_ADDRESS_FIELDS = [
|
|
|
194
194
|
*/
|
|
195
195
|
const ADDRESS_AUTO_SAVE_DEBOUNCE_MS = 600
|
|
196
196
|
|
|
197
|
-
/**
|
|
197
|
+
/** Cartbase COD provider resolution: `pp_cod` exactly, with the Medusa-era
|
|
198
198
|
* `pp_system_default*` prefix kept as fallback (lib/payment-constants),
|
|
199
199
|
* and `pp_manual` accepted as the offline tab when no true COD provider
|
|
200
200
|
* is enabled. Fee prediction only ever applies via `codConfig` (pp_cod). */
|
|
@@ -322,7 +322,7 @@ export function useCheckoutOrchestration({
|
|
|
322
322
|
: true
|
|
323
323
|
)
|
|
324
324
|
|
|
325
|
-
//
|
|
325
|
+
// Cartbase regions have no embedded countries — the caller-supplied list
|
|
326
326
|
// (or the countryCode fallback) is the authority for both the select
|
|
327
327
|
// options and the saved-address region filter.
|
|
328
328
|
const regionCountries = useMemo(
|
|
@@ -500,7 +500,7 @@ export function useCheckoutOrchestration({
|
|
|
500
500
|
lastSavedSnapshotRef.current = snapshot
|
|
501
501
|
|
|
502
502
|
if (customer) {
|
|
503
|
-
// Sync the profile best-effort.
|
|
503
|
+
// Sync the profile best-effort. Cartbase has first-class company
|
|
504
504
|
// fields on the customer (company_name / company_eik —
|
|
505
505
|
// api/customers.ts), so the invoice data also lands on the
|
|
506
506
|
// profile — an upgrade over the @1click cart-metadata-only
|
|
@@ -702,7 +702,7 @@ export function useCheckoutOrchestration({
|
|
|
702
702
|
[paymentMethodFilter, availablePaymentMethods, selectedShippingOption]
|
|
703
703
|
)
|
|
704
704
|
|
|
705
|
-
// Calculated-rate price resolution.
|
|
705
|
+
// Calculated-rate price resolution. Cartbase serves flat prices today
|
|
706
706
|
// (`price_type: "flat"` always — api/checkout.ts), so this effect
|
|
707
707
|
// normally resolves immediately; the calculated branch is kept for
|
|
708
708
|
// forward-compat with calculated-rate carriers (the /calculate route
|
|
@@ -818,7 +818,7 @@ export function useCheckoutOrchestration({
|
|
|
818
818
|
|
|
819
819
|
// Optimistic COD-fee state. Painted instantly on tab toggle so the
|
|
820
820
|
// totals row shows the predicted fee BEFORE the server applies the
|
|
821
|
-
// native fee (
|
|
821
|
+
// native fee (Cartbase: at prepare, when the pp_cod session is minted).
|
|
822
822
|
// Three values:
|
|
823
823
|
// - null → no prediction; render whatever the cart says
|
|
824
824
|
// - 0 → predict no fee (toggling away from COD)
|
|
@@ -1089,7 +1089,7 @@ export function useCheckoutOrchestration({
|
|
|
1089
1089
|
// shipping/fee)
|
|
1090
1090
|
// if optimisticShippingCost set: replace cart.shipping_total with it
|
|
1091
1091
|
// if optimisticCodFee set: replace cart.cod_fee_total with it
|
|
1092
|
-
// (
|
|
1092
|
+
// (Cartbase: the COD fee is CART-LEVEL decoration folded into total,
|
|
1093
1093
|
// not a fee line item)
|
|
1094
1094
|
// When nothing is optimistic, this collapses to cart.total.
|
|
1095
1095
|
const optimisticTotal = useMemo(() => {
|
|
@@ -1287,7 +1287,7 @@ export function useCheckoutOrchestration({
|
|
|
1287
1287
|
provider_id: prep.provider_id,
|
|
1288
1288
|
})
|
|
1289
1289
|
|
|
1290
|
-
// ── Zero-remainder gift path (
|
|
1290
|
+
// ── Zero-remainder gift path (Cartbase gift-card tender) ──────────
|
|
1291
1291
|
// When applied gift cards cover the whole total, prepare skips the
|
|
1292
1292
|
// provider session: client_secret AND provider_id come back null
|
|
1293
1293
|
// and the cart completes on the internal pp_giftcard session alone
|
|
@@ -9,7 +9,7 @@ import { useCartDrawer } from "../cart-drawer/context"
|
|
|
9
9
|
* Header cart button (badge + opens the cart drawer). Ported from
|
|
10
10
|
* `@1click/ui/src/common/cart-button-client.tsx` (v2.3.1); seams:
|
|
11
11
|
*
|
|
12
|
-
* - `cart` is the
|
|
12
|
+
* - `cart` is the Cartbase decorated cart (`api/carts` `Cart`).
|
|
13
13
|
* - Badge count uses `productItemCount` (fee-line-aware) instead of the
|
|
14
14
|
* source's raw `reduce` — keeps the badge consistent with the cart
|
|
15
15
|
* drawer's own count, which already excludes backend-injected fee
|
|
@@ -5,7 +5,7 @@ import { CartButtonClient } from "./cart-button-client"
|
|
|
5
5
|
/**
|
|
6
6
|
* Server wrapper for the header cart button. Ported from
|
|
7
7
|
* `@1click/ui/src/common/cart-button.tsx` (v2.3.1); data seam: the source
|
|
8
|
-
* called the cookie-reading `retrieveCart()` server action —
|
|
8
|
+
* called the cookie-reading `retrieveCart()` server action — Cartbase takes
|
|
9
9
|
* the `StorefrontClient` + the app-persisted cart id explicitly (the app
|
|
10
10
|
* owns the cart-id cookie, see docs/storefront/carts.md) and calls
|
|
11
11
|
* `api/carts.retrieveCart`. Any fetch failure (stale id, 404) degrades to
|
|
@@ -9,12 +9,12 @@ import type { StoreRegion } from "../api/regions"
|
|
|
9
9
|
* change forced by the SDK (SDK wins over the source):
|
|
10
10
|
*
|
|
11
11
|
* - Medusa regions embedded a `countries[]` array and the select listed
|
|
12
|
-
* countries;
|
|
12
|
+
* countries; Cartbase regions carry NO countries on the store surface
|
|
13
13
|
* (divergence documented in `api/regions` — tax scoping is by
|
|
14
14
|
* `tax_regions` server-side). The select therefore lists the REGIONS
|
|
15
15
|
* themselves (`regions.listRegions`), valued by region id.
|
|
16
16
|
* - The source called the `updateRegion(countryCode, path)` server
|
|
17
|
-
* action bound to URL `[countryCode]` routing;
|
|
17
|
+
* action bound to URL `[countryCode]` routing; Cartbase region
|
|
18
18
|
* persistence is app-owned (usually `carts.updateCart(client, cartId,
|
|
19
19
|
* {region_id})` + a cookie) — so the change surfaces via `onChange`.
|
|
20
20
|
*/
|
|
@@ -10,7 +10,7 @@ import { cn } from "../lib/utils"
|
|
|
10
10
|
* Cart-line delete button with spinner. Ported from
|
|
11
11
|
* `@1click/ui/src/common/delete-button.tsx` (v2.3.1); data seam: the
|
|
12
12
|
* source called the cookie-scoped `deleteLineItem(id)` server action —
|
|
13
|
-
*
|
|
13
|
+
* Cartbase calls `api/carts.deleteLineItem(client, cartId, lineItemId)`
|
|
14
14
|
* (idempotent server-side) and hands the refreshed cart to `onDeleted`
|
|
15
15
|
* so the app can update its state / `router.refresh()`. The spinner
|
|
16
16
|
* resets on failure exactly like the source's `.catch`.
|
package/src/common/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @cartbase/storefront/common — shared storefront chrome. Ported from
|
|
3
3
|
* `@1click/ui/src/common` (v2.3.1) + `LocalizedLink` (from the
|
|
4
|
-
* mindpages-storefront production component);
|
|
4
|
+
* mindpages-storefront production component); Cartbase data seams per
|
|
5
5
|
* module JSDoc.
|
|
6
6
|
*/
|
|
7
7
|
export { LocalizedLink, type LocalizedLinkProps } from "./localized-link"
|
|
@@ -7,13 +7,13 @@ import { useTransition } from "react"
|
|
|
7
7
|
* `@1click/ui/src/common/language-select.tsx` (v2.3.1); data seam:
|
|
8
8
|
*
|
|
9
9
|
* - The locale list feeds from `api/regions.listLocales(client)` — an
|
|
10
|
-
* array of bare codes, store default first (
|
|
10
|
+
* array of bare codes, store default first (Cartbase has no `{code,
|
|
11
11
|
* name}` locale objects on the store surface; SDK wins). Display
|
|
12
12
|
* names resolve via `localeDisplayName()` below (native-language
|
|
13
13
|
* autonym through `Intl.DisplayNames`), overridable per store with
|
|
14
14
|
* the `labels` map.
|
|
15
15
|
* - The source awaited an `updateLocale(code)` server action then
|
|
16
|
-
* `router.refresh()`;
|
|
16
|
+
* `router.refresh()`; Cartbase locale persistence is app-owned (the
|
|
17
17
|
* cookie read by `StorefrontClient.getLocale`) — the async `onChange`
|
|
18
18
|
* callback carries it, and the `useTransition` pending-disable UX is
|
|
19
19
|
* preserved around it.
|
|
@@ -10,7 +10,7 @@ import type { ReactNode } from "react"
|
|
|
10
10
|
* `LocalizedClientLink` (src/modules/common/components/localized-client-link)
|
|
11
11
|
* — the component `@1click/ui` consumers used for the same job.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
13
|
+
* Cartbase seam: Cartbase storefronts default to COOKIE-based locale
|
|
14
14
|
* (`StorefrontClient.getLocale`, see docs/storefront/BUILD-A-STOREFRONT.md)
|
|
15
15
|
* with no mandatory URL segment — so the prefix is applied ONLY when the
|
|
16
16
|
* route actually has a `[countryCode]` (or custom `paramName`) dynamic
|
package/src/lib/cart-helpers.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Ported from `@1click/ui/src/lib/cart-helpers.ts` (v2.3.1) with its
|
|
5
5
|
* production reasoning intact; the `@medusajs/types` generic constraint is
|
|
6
6
|
* replaced by the structural `LineLike` shape so this module stays decoupled
|
|
7
|
-
* from any SDK layer (
|
|
7
|
+
* from any SDK layer (Cartbase cart/order line items are structurally
|
|
8
8
|
* compatible: they carry `metadata`, `total`, `quantity`).
|
|
9
9
|
*
|
|
10
10
|
* Why this exists. There is no native "fee" / "surcharge" primitive in the
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* `findFeeLine`. Customer SHOULD see the fee in checkout / on the
|
|
30
30
|
* order; they should NOT see it in the cart drawer pre-checkout.
|
|
31
31
|
*
|
|
32
|
-
* Money note (
|
|
32
|
+
* Money note (Cartbase): amounts are decimal EUR major units everywhere in
|
|
33
33
|
* the store API — the arithmetic below is unit-agnostic and unchanged.
|
|
34
34
|
*/
|
|
35
35
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cookie names the store API + @cartbase/storefront agree on
|
|
3
|
+
* (platform-fingerprints card, fingerprint #4 — cookie names are a
|
|
4
|
+
* primary Wappalyzer/BuiltWith detection signal).
|
|
5
|
+
*
|
|
6
|
+
* The app still OWNS *setting* the cart cookie — carts.md is explicit that
|
|
7
|
+
* "the SDK never persists the cart" — this module is the single source of
|
|
8
|
+
* the NAME so every consumer emits the same wire fingerprint instead of
|
|
9
|
+
* inventing its own prefix (the reference app previously hardcoded
|
|
10
|
+
* `_barter_cart_id` locally in `examples/storefront/src/lib/config.ts`).
|
|
11
|
+
*
|
|
12
|
+
* Renamed from the `_barter_*` prefix (2026-08-04, docs/features/
|
|
13
|
+
* cartbase-rename.md). `readCartCookie` accepts the legacy name for the
|
|
14
|
+
* transition window; every write uses the new name only.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** Current cart-id cookie name. */
|
|
18
|
+
export const CART_COOKIE = "_cartbase_cart"
|
|
19
|
+
|
|
20
|
+
/** @deprecated Legacy name — read-only compat, never written. */
|
|
21
|
+
export const LEGACY_CART_COOKIE = "_barter_cart_id"
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Reserved for a future cookie-backed customer session. Auth today is pure
|
|
25
|
+
* Bearer-JWT, persisted by the consuming app (auth.md — "the Cartbase API
|
|
26
|
+
* does not proxy token refresh"); nothing sets this cookie yet. Defined now
|
|
27
|
+
* so a future cookie-based session launches with the fingerprint-correct
|
|
28
|
+
* name from day one rather than inventing a new prefix later.
|
|
29
|
+
*/
|
|
30
|
+
export const SESSION_COOKIE = "_cartbase_session"
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Read a cart id given a cookie-name → value lookup, preferring the
|
|
34
|
+
* current name and falling back to the legacy `_barter_cart_id` name so an
|
|
35
|
+
* existing visitor's cart survives the rename.
|
|
36
|
+
*/
|
|
37
|
+
export function readCartCookie(
|
|
38
|
+
get: (name: string) => string | undefined
|
|
39
|
+
): string | undefined {
|
|
40
|
+
return get(CART_COOKIE) ?? get(LEGACY_CART_COOKIE)
|
|
41
|
+
}
|
|
@@ -6,11 +6,11 @@ import { convertToLocale } from "./money"
|
|
|
6
6
|
* `@1click/ui/src/lib/get-product-price.ts` (v2.3.1). The
|
|
7
7
|
* `@medusajs/types` `HttpTypes.StoreProduct` parameter is replaced by the
|
|
8
8
|
* structural shapes below so this module stays decoupled from the SDK
|
|
9
|
-
* domain modules (
|
|
9
|
+
* domain modules (Cartbase's `variant.calculated_price` is the b2b-v1
|
|
10
10
|
* pricing context documented in `@cartbase/storefront/api/types`
|
|
11
11
|
* `CalculatedPrice`, decimal EUR major units).
|
|
12
12
|
*
|
|
13
|
-
* Wire-shape note (code truth, `src/lib/products/pricing.ts`):
|
|
13
|
+
* Wire-shape note (code truth, `src/lib/products/pricing.ts`): Cartbase serves
|
|
14
14
|
* `calculated_price.price_list_type` FLAT on the calculated_price object.
|
|
15
15
|
* The Medusa wire shape ALSO nests a `calculated_price.calculated_price`
|
|
16
16
|
* detail object carrying `price_list_type` — the original library read the
|
|
@@ -71,7 +71,7 @@ export const getPricesForVariant = (
|
|
|
71
71
|
currency_code: cp.currency_code,
|
|
72
72
|
}),
|
|
73
73
|
currency_code: cp.currency_code,
|
|
74
|
-
// Flat key (
|
|
74
|
+
// Flat key (Cartbase wire shape) first — an explicit null stays null;
|
|
75
75
|
// the nested Medusa detail is the fallback only when the flat key is
|
|
76
76
|
// absent entirely (the original @1click read path).
|
|
77
77
|
price_type:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Payment provider helpers — identifier sniffing for store payment sessions.
|
|
3
3
|
*
|
|
4
4
|
* Ported from `@1click/ui/src/lib/payment-constants.ts` (v2.3.1) and
|
|
5
|
-
* translated to the
|
|
5
|
+
* translated to the Cartbase provider-id seam (code truth:
|
|
6
6
|
* `src/lib/stripe/providers.ts isStripeProviderId` matches exactly
|
|
7
7
|
* `"pp_stripe" | "stripe"`; the accepted checkout ids are
|
|
8
8
|
* `pp_stripe | pp_cod | pp_manual` — docs/contracts/store-api.md).
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* True if the provider id is a Stripe-backed card payment provider.
|
|
21
|
-
*
|
|
21
|
+
* Cartbase's canonical ids first (`pp_stripe` / `stripe`), then the
|
|
22
22
|
* Medusa-era prefixes (`pp_stripe_*`, `pp_medusa-*`).
|
|
23
23
|
*/
|
|
24
24
|
export const isStripeLike = (providerId?: string): boolean => {
|
|
@@ -6,7 +6,7 @@ import { StoreApiError } from "../api/types"
|
|
|
6
6
|
* Normalizes any error thrown by the `@cartbase/storefront` SDK layer into a
|
|
7
7
|
* clean, user-presentable `Error` whose message starts with a capital and
|
|
8
8
|
* ends with a period — the same normalization contract the Medusa-era
|
|
9
|
-
* helper enforced, adapted to the
|
|
9
|
+
* helper enforced, adapted to the Cartbase error contract (`StoreApiError`
|
|
10
10
|
* with `status` + `code` + raw `body`, see `../api/types`).
|
|
11
11
|
*
|
|
12
12
|
* Always throws — never returns. Use in `.catch()` chains:
|
package/src/order/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @cartbase/storefront/order — order confirmation + account order views.
|
|
3
|
-
* Ported from `@1click/ui/src/order` (v2.3.1) with
|
|
3
|
+
* Ported from `@1click/ui/src/order` (v2.3.1) with Cartbase data seams; see
|
|
4
4
|
* per-module JSDoc and docs/storefront/components.md for the contracts.
|
|
5
5
|
*/
|
|
6
6
|
export { OrderLabelsProvider, useOrderLabels } from "./context"
|
package/src/order/labels.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Order family label pack. Ported from `@1click/ui/src/order/labels.ts`
|
|
3
|
-
* (v2.3.1) unchanged, plus `tracking` — new in the
|
|
4
|
-
*
|
|
3
|
+
* (v2.3.1) unchanged, plus `tracking` — new in the Cartbase port because the
|
|
4
|
+
* Cartbase order surface exposes fulfillment tracking labels
|
|
5
5
|
* (`StoreOrderDetail.fulfillments[].fulfillment.labels`) that
|
|
6
6
|
* `OrderDeliveryCard` now renders.
|
|
7
7
|
*/
|
|
@@ -40,7 +40,7 @@ export type OrderLabels = {
|
|
|
40
40
|
free: string
|
|
41
41
|
continueShopping: string
|
|
42
42
|
qty: string
|
|
43
|
-
/** Heading for the fulfillment tracking-numbers block (
|
|
43
|
+
/** Heading for the fulfillment tracking-numbers block (Cartbase addition). */
|
|
44
44
|
tracking: string
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -3,7 +3,7 @@ import { defaultOrderLabels, type OrderLabels } from "./labels"
|
|
|
3
3
|
/**
|
|
4
4
|
* Contact-info card (name, phone, email). Ported from
|
|
5
5
|
* `@1click/ui/src/order/order-address-card.tsx` (v2.3.1); data seam:
|
|
6
|
-
* structural pick —
|
|
6
|
+
* structural pick — Cartbase's `StoreOrderDetail` (with its embedded
|
|
7
7
|
* `order_addresses` row) satisfies it directly.
|
|
8
8
|
*/
|
|
9
9
|
export interface OrderContactData {
|
|
@@ -25,11 +25,11 @@ import { OrderHelpSection } from "./order-help-section"
|
|
|
25
25
|
/**
|
|
26
26
|
* Full order-confirmation page. Ported from
|
|
27
27
|
* `@1click/ui/src/order/order-completed-template.tsx` (v2.3.1) with the
|
|
28
|
-
*
|
|
29
|
-
* with totals, order totals, shipping methods and payments —
|
|
28
|
+
* Cartbase data seam: Medusa's `StoreOrder` was one object carrying items
|
|
29
|
+
* with totals, order totals, shipping methods and payments — Cartbase splits
|
|
30
30
|
* those across surfaces, so the template takes them as separate props:
|
|
31
31
|
*
|
|
32
|
-
* - `order` —
|
|
32
|
+
* - `order` — Cartbase `StoreOrderDetail` (or any structural equivalent:
|
|
33
33
|
* id/display_id/email/created_at/currency_code/metadata/addresses/
|
|
34
34
|
* fulfillments). The fulfillment timeline is derived from
|
|
35
35
|
* `order.fulfillments` internally.
|
|
@@ -4,7 +4,7 @@ import { defaultOrderLabels, type OrderLabels } from "./labels"
|
|
|
4
4
|
/**
|
|
5
5
|
* Confirmation hero (check mark + order number + date). Ported from
|
|
6
6
|
* `@1click/ui/src/order/order-confirmation-header.tsx` (v2.3.1); data
|
|
7
|
-
* seam: structural pick instead of Medusa's `StoreOrder` — both
|
|
7
|
+
* seam: structural pick instead of Medusa's `StoreOrder` — both Cartbase's
|
|
8
8
|
* `StoreOrderDetail` and `completeCart()`'s `CompletedOrder` satisfy it.
|
|
9
9
|
*/
|
|
10
10
|
export interface OrderHeaderData {
|
|
@@ -5,17 +5,17 @@ import { defaultOrderLabels, type OrderLabels } from "./labels"
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Delivery card: pickup point / address, shipping method row, and — new in
|
|
8
|
-
* the
|
|
8
|
+
* the Cartbase port — the fulfillment tracking labels. Ported from
|
|
9
9
|
* `@1click/ui/src/order/order-delivery-card.tsx` (v2.3.1) with these seam
|
|
10
10
|
* translations:
|
|
11
11
|
*
|
|
12
|
-
* - Medusa's `order.shipping_methods[0]` embed does not exist on
|
|
12
|
+
* - Medusa's `order.shipping_methods[0]` embed does not exist on Cartbase's
|
|
13
13
|
* `StoreOrderDetail`; the method comes via the `shippingMethod` prop
|
|
14
14
|
* (structural — `api/carts` `CartShippingMethod` from the decorated
|
|
15
15
|
* cart on the confirmation page satisfies it; `data` still carries the
|
|
16
16
|
* fulfillment-option id used for the pickup detection below).
|
|
17
17
|
* - `order.fulfillments[].fulfillment.labels` (tracking_number /
|
|
18
|
-
* tracking_url) ARE on the
|
|
18
|
+
* tracking_url) ARE on the Cartbase surface (docs/storefront/orders.md);
|
|
19
19
|
* `pickTrackingLabels()` selects the renderable ones and the card shows
|
|
20
20
|
* them under a `labels.tracking` heading.
|
|
21
21
|
* - Pickup detection preserved verbatim: `metadata.econt_office_code` (the
|
package/src/order/order-item.tsx
CHANGED
|
@@ -6,10 +6,10 @@ import { defaultOrderLabels, type OrderLabels } from "./labels"
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Order line row. Ported from `@1click/ui/src/order/order-item.tsx`
|
|
9
|
-
* (v2.3.1) with the data seam translated to
|
|
9
|
+
* (v2.3.1) with the data seam translated to Cartbase:
|
|
10
10
|
*
|
|
11
11
|
* Medusa's `StoreOrderLineItem` carried per-line computed totals
|
|
12
|
-
* (`total` / `original_total`).
|
|
12
|
+
* (`total` / `original_total`). Cartbase's store order surface does NOT —
|
|
13
13
|
* `StoreOrderDetail.items` are version-pivot rows (`{quantity, line_item}`)
|
|
14
14
|
* whose embedded `order_line_items` row carries only `unit_price` (see
|
|
15
15
|
* docs/storefront/orders.md). The component therefore renders a normalized
|
|
@@ -5,7 +5,7 @@ import { defaultOrderLabels, type OrderLabels } from "./labels"
|
|
|
5
5
|
/**
|
|
6
6
|
* Order items list. Ported from `@1click/ui/src/order/order-items-list.tsx`
|
|
7
7
|
* (v2.3.1); data seam: takes normalized `OrderDisplayItem[]` (see
|
|
8
|
-
* `order-item.tsx` converters) + the currency, since
|
|
8
|
+
* `order-item.tsx` converters) + the currency, since Cartbase's
|
|
9
9
|
* `StoreOrderDetail` carries no order-level computed fields beyond
|
|
10
10
|
* `currency_code`.
|
|
11
11
|
*/
|
|
@@ -23,9 +23,9 @@ export function OrderItemsList({
|
|
|
23
23
|
const l = { ...defaultOrderLabels, ...labels }
|
|
24
24
|
// Render product lines only — backend-injected fee lines (legacy COD-fee
|
|
25
25
|
// line items tagged `metadata.is_cod_fee`) are hidden here and surfaced
|
|
26
|
-
// as a dedicated row in OrderTotals instead.
|
|
26
|
+
// as a dedicated row in OrderTotals instead. Cartbase's NATIVE COD fee is
|
|
27
27
|
// never a line item (order_summaries.totals.cod_fee_total), so on pure
|
|
28
|
-
//
|
|
28
|
+
// Cartbase data this filter is a no-op safety net.
|
|
29
29
|
const visible = (items || []).filter((i) => isProductLine(i))
|
|
30
30
|
|
|
31
31
|
return (
|
|
@@ -11,7 +11,7 @@ import { defaultOrderLabels, type OrderLabels } from "./labels"
|
|
|
11
11
|
* Payment-method card. Ported from
|
|
12
12
|
* `@1click/ui/src/order/order-payment-card.tsx` (v2.3.1); data seam:
|
|
13
13
|
* Medusa's `order.payment_collections[0].payments[0]` embed does not exist
|
|
14
|
-
* on
|
|
14
|
+
* on Cartbase's `StoreOrderDetail` (payment internals never cross the store
|
|
15
15
|
* surface) — the provider id arrives via props instead: from
|
|
16
16
|
* `prepareCheckout().payment_provider`, the decorated cart's
|
|
17
17
|
* `payment_collection` sessions, or the app's checkout state.
|
|
@@ -33,7 +33,7 @@ import { defaultOrderLabels, type OrderLabels } from "./labels"
|
|
|
33
33
|
*
|
|
34
34
|
* The bucket is determined by the same `isStripeLike` / `isManual` /
|
|
35
35
|
* `isPaypal` predicates used everywhere else in the library (translated to
|
|
36
|
-
*
|
|
36
|
+
* Cartbase ids in `lib/payment-constants`), so a single change to those
|
|
37
37
|
* propagates here automatically.
|
|
38
38
|
*/
|
|
39
39
|
export function resolvePaymentTitle(
|
|
@@ -6,14 +6,14 @@ import { defaultOrderLabels, type OrderLabels } from "./labels"
|
|
|
6
6
|
/**
|
|
7
7
|
* Fulfillment progress timeline. Ported from
|
|
8
8
|
* `@1click/ui/src/order/order-timeline.tsx` (v2.3.1) — the step state
|
|
9
|
-
* machine and rendering are unchanged.
|
|
9
|
+
* machine and rendering are unchanged. Cartbase seam: there is no
|
|
10
10
|
* `order.fulfillment_status` column on the store surface; derive the
|
|
11
11
|
* status from the fulfillments' lifecycle timestamps with
|
|
12
12
|
* `deriveFulfillmentStatus(order.fulfillments)` and pass it in.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* Derive a Medusa-style fulfillment status from
|
|
16
|
+
* Derive a Medusa-style fulfillment status from Cartbase's fulfillment
|
|
17
17
|
* lifecycle timestamps (packed_at → fulfilled, shipped_at → shipped,
|
|
18
18
|
* delivered_at → delivered; canceled fulfillments are ignored). Returns
|
|
19
19
|
* `undefined` for "not fulfilled yet" (timeline shows "Placed"). Partial
|
|
@@ -4,9 +4,9 @@ import { defaultOrderLabels, type OrderLabels } from "./labels"
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Order totals breakdown. Ported from `@1click/ui/src/order/order-totals.tsx`
|
|
7
|
-
* (v2.3.1) with the
|
|
7
|
+
* (v2.3.1) with the Cartbase money seam:
|
|
8
8
|
*
|
|
9
|
-
* Totals are SERVER truth and the
|
|
9
|
+
* Totals are SERVER truth and the Cartbase store order read
|
|
10
10
|
* (`StoreOrderDetail`) carries NONE of them — they live where the server
|
|
11
11
|
* computed them: the decorated cart the app holds on the confirmation page
|
|
12
12
|
* (`api/carts` `Cart`: `item_subtotal`, `shipping_subtotal`,
|
|
@@ -18,7 +18,7 @@ import { defaultOrderLabels, type OrderLabels } from "./labels"
|
|
|
18
18
|
* computes money beyond the two production subtractions documented below.
|
|
19
19
|
*
|
|
20
20
|
* COD fee display, in preference order (production fix preserved):
|
|
21
|
-
* 1.
|
|
21
|
+
* 1. Cartbase-native fee: `cod_fee_total` > 0 (never a line item;
|
|
22
22
|
* already folded into `total` server-side), labeled by the
|
|
23
23
|
* admin-editable `cod_fee_label`.
|
|
24
24
|
* 2. legacy Medusa-era fee LINE ITEM (`metadata.is_cod_fee`) found in
|
|
@@ -42,7 +42,7 @@ export interface OrderTotalsSource {
|
|
|
42
42
|
discount_total?: number | null
|
|
43
43
|
tax_total?: number | null
|
|
44
44
|
total?: number | null
|
|
45
|
-
/**
|
|
45
|
+
/** Cartbase-native COD fee (already folded into `total`). */
|
|
46
46
|
cod_fee_total?: number | null
|
|
47
47
|
/** Admin-editable COD fee label from the server. */
|
|
48
48
|
cod_fee_label?: string | null
|
|
@@ -98,7 +98,7 @@ export function selectOrderTotalsRows(
|
|
|
98
98
|
const l = { ...defaultOrderLabels, ...opts.labels }
|
|
99
99
|
const rows: OrderTotalsRow[] = []
|
|
100
100
|
|
|
101
|
-
// Legacy fee LINE (Medusa-era injection) —
|
|
101
|
+
// Legacy fee LINE (Medusa-era injection) — Cartbase-native carts never
|
|
102
102
|
// have one, but ported data / hybrid stores might.
|
|
103
103
|
const codFeeItem = findFeeLine(opts.items || [])
|
|
104
104
|
const nativeFee = Number(source.cod_fee_total ?? 0)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform identity — MIRROR of `src/lib/platform/identity.ts` in the main
|
|
3
|
+
* app (docs/cards/platform-fingerprints.md). This library publishes to npm
|
|
4
|
+
* independently, so it cannot import the app's root `package.json` across
|
|
5
|
+
* the package boundary — it reads its OWN `package.json` instead.
|
|
6
|
+
*
|
|
7
|
+
* Sync obligation: if `PLATFORM_NAME` ever changes, update BOTH copies in
|
|
8
|
+
* the same commit. There is no automated link between them.
|
|
9
|
+
*
|
|
10
|
+
* Emitters that consume this: `./metadata.ts` (generator meta tag),
|
|
11
|
+
* `./platform-init.tsx` (window.Cartbase).
|
|
12
|
+
*/
|
|
13
|
+
import pkg from "../../package.json"
|
|
14
|
+
|
|
15
|
+
export const PLATFORM_NAME = "Cartbase"
|
|
16
|
+
|
|
17
|
+
/** This package's own published version. */
|
|
18
|
+
export const PLATFORM_VERSION: string = pkg.version
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @cartbase/storefront/platform — platform-fingerprint emitters
|
|
3
|
+
* (docs/cards/platform-fingerprints.md). Same barrel discipline as every
|
|
4
|
+
* other feature folder (tracking, checkout, cart-drawer, ...).
|
|
5
|
+
*/
|
|
6
|
+
export { PLATFORM_NAME, PLATFORM_VERSION } from "./identity"
|
|
7
|
+
export { createStorefrontMetadata } from "./metadata"
|
|
8
|
+
export { PlatformInit, type PlatformInitProps, type CartbaseWindowGlobal } from "./platform-init"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Metadata } from "next"
|
|
2
|
+
import { PLATFORM_NAME } from "./identity"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* createStorefrontMetadata — root-layout metadata helper
|
|
6
|
+
* (platform-fingerprints card, fingerprint #1). Every storefront built on
|
|
7
|
+
* `@cartbase/storefront` calls this instead of hand-writing
|
|
8
|
+
* `export const metadata`, so `<meta name="generator" content="Cartbase" />`
|
|
9
|
+
* ships by construction on every storefront-rendered page.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
*
|
|
13
|
+
* export const metadata: Metadata = createStorefrontMetadata({
|
|
14
|
+
* title: "My Store",
|
|
15
|
+
* description: "...",
|
|
16
|
+
* })
|
|
17
|
+
*
|
|
18
|
+
* `generator` always resolves to the platform name — it is not
|
|
19
|
+
* overridable from `overrides` (a stray key there is simply shadowed).
|
|
20
|
+
*/
|
|
21
|
+
export function createStorefrontMetadata(overrides: Metadata = {}): Metadata {
|
|
22
|
+
return {
|
|
23
|
+
...overrides,
|
|
24
|
+
generator: PLATFORM_NAME,
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { PLATFORM_VERSION } from "./identity"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The shape of `window.Cartbase` — the strongest platform-detection signal
|
|
5
|
+
* (same mechanism as `window.Shopify`). No PII: store id + platform
|
|
6
|
+
* version only.
|
|
7
|
+
*/
|
|
8
|
+
export interface CartbaseWindowGlobal {
|
|
9
|
+
version: string
|
|
10
|
+
storeId: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
declare global {
|
|
14
|
+
interface Window {
|
|
15
|
+
Cartbase?: CartbaseWindowGlobal
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface PlatformInitProps {
|
|
20
|
+
/** The store's client id — the ONLY identifying value exposed. */
|
|
21
|
+
storeId: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* PlatformInit — mounts `window.Cartbase = { version, storeId }`
|
|
26
|
+
* (platform-fingerprints card, fingerprint #3). Same shape as
|
|
27
|
+
* `<ConsentInit />` (`../tracking/consent-init.tsx`): a server component
|
|
28
|
+
* rendering one synchronous inline `<script>`, mounted once near the top
|
|
29
|
+
* of `<body>` — no hydration wait, no client bundle cost.
|
|
30
|
+
*
|
|
31
|
+
* Storefront-surface only. Never mount this in an admin bundle — admin is
|
|
32
|
+
* not a storefront (category discipline: Cartbase exists ONLY as
|
|
33
|
+
* Ecommerce, and `window.Cartbase` must only ever appear where commerce
|
|
34
|
+
* actually happens).
|
|
35
|
+
*/
|
|
36
|
+
export function PlatformInit({ storeId }: PlatformInitProps) {
|
|
37
|
+
const payload: CartbaseWindowGlobal = { version: PLATFORM_VERSION, storeId }
|
|
38
|
+
return (
|
|
39
|
+
<script
|
|
40
|
+
id="cartbase-platform-init"
|
|
41
|
+
dangerouslySetInnerHTML={{
|
|
42
|
+
__html: `window.Cartbase=${JSON.stringify(payload)};`,
|
|
43
|
+
}}
|
|
44
|
+
/>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PDP image gallery — ported from
|
|
3
3
|
* `@1click/ui/src/products/image-gallery.tsx` (v2.3.1). Data seam:
|
|
4
|
-
* `HttpTypes.StoreProductImage[]` →
|
|
4
|
+
* `HttpTypes.StoreProductImage[]` → Cartbase `StoreProductImage[]`
|
|
5
5
|
* (`@cartbase/storefront/api/products`; images arrive rank-ordered from the
|
|
6
6
|
* API). Server-safe.
|
|
7
7
|
*/
|