@cartbase/storefront 0.1.2 → 0.3.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/carts.ts +5 -3
- package/src/api/checkout.ts +91 -40
- package/src/api/consent.ts +2 -2
- package/src/api/integrations.ts +4 -17
- 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 +2 -2
- package/src/cart-drawer/labels.ts +119 -119
- package/src/cart-drawer/summary-breakdown.tsx +197 -196
- 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 +15 -12
- 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/index.ts +0 -1
- package/src/checkout/labels-bg.ts +2 -2
- package/src/checkout/labels.ts +263 -263
- package/src/checkout/line-item-card.tsx +2 -2
- package/src/checkout/order-summary.tsx +48 -48
- package/src/checkout/payment-button.tsx +4 -4
- package/src/checkout/payment-error-copy.ts +6 -6
- package/src/checkout/payment-method-list.tsx +93 -33
- 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 +108 -98
- 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 +53 -66
- package/src/lib/store-api-error.ts +1 -1
- package/src/order/index.ts +1 -1
- package/src/order/labels-bg.ts +39 -39
- package/src/order/labels.ts +79 -79
- package/src/order/order-address-card.tsx +1 -1
- package/src/order/order-completed-template.tsx +20 -12
- 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 +27 -36
- package/src/order/order-timeline.tsx +2 -2
- package/src/order/order-totals.tsx +250 -245
- 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
|
@@ -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
|
*/
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Mobile sticky add-to-cart bar + options bottom sheet — ported from
|
|
5
5
|
* `@1click/ui/src/products/mobile-actions.tsx` (v2.3.1). Data seam:
|
|
6
|
-
* `HttpTypes.*` →
|
|
6
|
+
* `HttpTypes.*` → Cartbase SDK DTOs; everything else (layout, z-index
|
|
7
7
|
* layering above the cart drawer, morphing show/hide transition) unchanged.
|
|
8
8
|
* Pure-props component — the add flow lives in `ProductActions`.
|
|
9
9
|
*/
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Option selector — ported from
|
|
5
5
|
* `@1click/ui/src/products/option-select.tsx` (v2.3.1). Data seam:
|
|
6
|
-
* `HttpTypes.StoreProductOption` →
|
|
6
|
+
* `HttpTypes.StoreProductOption` → Cartbase `StoreProductOption`
|
|
7
7
|
* (`@cartbase/storefront/api/products`; `values` is present on
|
|
8
8
|
* `product.options[]`, which is what this component receives).
|
|
9
9
|
*/
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Preview (card) price — ported from
|
|
3
3
|
* `@1click/ui/src/products/preview-price.tsx` (v2.3.1) unchanged. Renders a
|
|
4
4
|
* pre-computed `VariantPrice` (from `lib/get-product-price`, which reads the
|
|
5
|
-
* server-computed `variant.calculated_price` —
|
|
5
|
+
* server-computed `variant.calculated_price` — Cartbase's flat
|
|
6
6
|
* `price_list_type` wire shape); this component never computes money.
|
|
7
7
|
*/
|
|
8
8
|
import { cn } from "../lib/utils"
|
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
* `@1click/ui/src/products/product-actions.tsx` (v2.3.1).
|
|
6
6
|
*
|
|
7
7
|
* Data-seam adaptations (batch-10 port):
|
|
8
|
-
* - `HttpTypes.*` →
|
|
8
|
+
* - `HttpTypes.*` → Cartbase SDK DTOs (`@cartbase/storefront/api/products`).
|
|
9
9
|
* - The `addToCart` server action (Medusa js-sdk, cart-cookie owner) is now
|
|
10
10
|
* an injected prop: the host app wires it to its own cart orchestration
|
|
11
11
|
* (typically `api/carts` `addLineItem` + its cart-id cookie). Same for the
|
|
12
12
|
* cart drawer: `openCart` replaces the `useCartDrawer()` context import so
|
|
13
13
|
* the products family has no hard dependency on the cart-drawer family.
|
|
14
|
-
* - `countryCode` (Medusa region routing) is dropped —
|
|
14
|
+
* - `countryCode` (Medusa region routing) is dropped — Cartbase pricing
|
|
15
15
|
* context travels with the product fetch, and the host's `addToCart`
|
|
16
16
|
* closes over whatever routing it needs.
|
|
17
|
-
* - Stock:
|
|
17
|
+
* - Stock: Cartbase's store surface does NOT expose `inventory_quantity`
|
|
18
18
|
* (availability is a search facet; the server enforces stock at add with
|
|
19
19
|
* 400 `insufficient_inventory`). Managed-inventory variants without an
|
|
20
20
|
* exposed quantity are treated as in stock optimistically; a thrown
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* quantity check.
|
|
24
24
|
*
|
|
25
25
|
* Variant matching is the extracted pure module `./variant-matching`
|
|
26
|
-
* (handles
|
|
26
|
+
* (handles Cartbase's option-value link shape).
|
|
27
27
|
*/
|
|
28
28
|
import { usePathname, useRouter, useSearchParams } from "next/navigation"
|
|
29
29
|
import { useEffect, useMemo, useRef, useState } from "react"
|
|
@@ -74,7 +74,7 @@ export function ProductActions({
|
|
|
74
74
|
const [options, setOptions] = useState<Record<string, string | undefined>>({})
|
|
75
75
|
const [isAdding, setIsAdding] = useState(false)
|
|
76
76
|
// Set when the server rejected an add with `insufficient_inventory`
|
|
77
|
-
// (
|
|
77
|
+
// (Cartbase exposes no inventory_quantity on the store surface — the server
|
|
78
78
|
// is the stock truth). Cleared when the selection changes.
|
|
79
79
|
const [stockExhausted, setStockExhausted] = useState(false)
|
|
80
80
|
|
|
@@ -123,7 +123,7 @@ export function ProductActions({
|
|
|
123
123
|
if (!selectedVariant) return false
|
|
124
124
|
if (!selectedVariant.manage_inventory) return true
|
|
125
125
|
if (selectedVariant.allow_backorder) return true
|
|
126
|
-
//
|
|
126
|
+
// Cartbase wire shape carries no inventory_quantity — optimistic; the
|
|
127
127
|
// server enforces stock on add. Richer DTOs keep the original check.
|
|
128
128
|
const qty = (selectedVariant as { inventory_quantity?: number })
|
|
129
129
|
.inventory_quantity
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PDP title/collection/description block — ported from
|
|
3
3
|
* `@1click/ui/src/products/product-info.tsx` (v2.3.1). Data seam:
|
|
4
|
-
* `HttpTypes.StoreProduct` →
|
|
4
|
+
* `HttpTypes.StoreProduct` → Cartbase `StoreProduct` (the collection embed is
|
|
5
5
|
* `{id, title, handle}` — exactly what the link needs). Server-safe.
|
|
6
6
|
*/
|
|
7
7
|
import Link from "next/link"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Product card — ported from `@1click/ui/src/products/product-preview.tsx`
|
|
3
|
-
* (v2.3.1). Data seam: `HttpTypes.StoreProduct` →
|
|
3
|
+
* (v2.3.1). Data seam: `HttpTypes.StoreProduct` → Cartbase `StoreProduct`.
|
|
4
4
|
* The ONE card renderer per the SDK doctrine: search results, collection
|
|
5
5
|
* membership pages, and related products all serve the same canonical
|
|
6
6
|
* product object — reuse this card (or pass a custom one via the templates'
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* PDP price display — ported from
|
|
5
5
|
* `@1click/ui/src/products/product-price.tsx` (v2.3.1). Data seam:
|
|
6
6
|
* `@medusajs/types` `HttpTypes.StoreProduct/StoreProductVariant` are
|
|
7
|
-
* replaced by the
|
|
7
|
+
* replaced by the Cartbase SDK DTOs (`@cartbase/storefront/api/products`).
|
|
8
8
|
* Pricing stays SERVER truth — `lib/get-product-price` only selects and
|
|
9
|
-
* formats `variant.calculated_price` (
|
|
9
|
+
* formats `variant.calculated_price` (Cartbase's flat `price_list_type`).
|
|
10
10
|
*/
|
|
11
11
|
import type { StoreProduct, StoreProductVariant } from "../api/products"
|
|
12
12
|
import { cn } from "../lib/utils"
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* PDP info/shipping accordion — ported from
|
|
5
5
|
* `@1click/ui/src/products/product-tabs.tsx` (v2.3.1). Data seam:
|
|
6
|
-
* `HttpTypes.StoreProduct` →
|
|
6
|
+
* `HttpTypes.StoreProduct` → Cartbase `StoreProduct` (same physical fields:
|
|
7
7
|
* material, origin_country, type.value, weight, length/width/height).
|
|
8
8
|
* Layout, morphing +/− trigger, and label wiring unchanged.
|
|
9
9
|
*/
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* PDP purchase options — one-time vs subscription plans (subscriptions-core
|
|
5
|
-
* card, step 4;
|
|
5
|
+
* card, step 4; Cartbase-native, no @1click/ui ancestor). Presentational and
|
|
6
6
|
* controlled: the host owns the selection and passes the chosen plan id
|
|
7
7
|
* into its add-to-cart seam (`carts.addLineItem` `selling_plan_id`). The
|
|
8
8
|
* SERVER prices the line — the preview here is display-only and computed
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Related-products strip — ported from
|
|
3
3
|
* `@1click/ui/src/products/related-products.tsx` (v2.3.1), REWIRED to the
|
|
4
|
-
*
|
|
4
|
+
* Cartbase related-products endpoint (docs/storefront/search.md).
|
|
5
5
|
*
|
|
6
6
|
* The original hand-rolled relatedness client-side (same collection + shared
|
|
7
|
-
* tags via a products listing).
|
|
7
|
+
* tags via a products listing). Cartbase owns that server-side:
|
|
8
8
|
* `api/search` `listRelatedProducts` returns manual admin picks first
|
|
9
9
|
* (Admin → Product → Related, position order), then a deterministic fallback
|
|
10
10
|
* fills to `limit` — same primary collection (newest first), then
|
|
11
11
|
* most-shared-tags (`auto_filled: true` when any fallback item is present).
|
|
12
12
|
* The anchor product is never in the results, so the source's self-filter is
|
|
13
|
-
* gone. Region resolution via `countryCode` is replaced by the
|
|
13
|
+
* gone. Region resolution via `countryCode` is replaced by the Cartbase
|
|
14
14
|
* pricing context (`currency_code`/`region_id`).
|
|
15
15
|
*
|
|
16
16
|
* Async server component — render inside `<Suspense>`.
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
* (tests/unit/storefront-catalog.test.ts).
|
|
6
6
|
*
|
|
7
7
|
* Data-seam translation: Medusa served `variant.options[]` as flat
|
|
8
|
-
* `{option_id, value}` rows;
|
|
8
|
+
* `{option_id, value}` rows; Cartbase serves option-value LINKS
|
|
9
9
|
* (`{value: {option_id, value, option}}` — see
|
|
10
10
|
* `@cartbase/storefront/api/products` `StoreVariantOptionValueLink`).
|
|
11
|
-
* `optionsAsKeymap` reads the
|
|
11
|
+
* `optionsAsKeymap` reads the Cartbase link shape (null-safe) and falls back
|
|
12
12
|
* to the flat Medusa row shape so both wire formats resolve identically.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
/** Either
|
|
15
|
+
/** Either Cartbase's option-value link or Medusa's flat option-value row. */
|
|
16
16
|
export type VariantOptionLike =
|
|
17
17
|
| {
|
|
18
18
|
option_id?: string
|
|
@@ -34,7 +34,7 @@ export function optionsAsKeymap(
|
|
|
34
34
|
if (!varopt) return acc
|
|
35
35
|
const v = varopt.value
|
|
36
36
|
if (v && typeof v === "object") {
|
|
37
|
-
//
|
|
37
|
+
// Cartbase link shape: { value: { option_id, value } }
|
|
38
38
|
if (v.option_id) acc[v.option_id] = v.value
|
|
39
39
|
} else if (varopt.option_id && typeof v === "string") {
|
|
40
40
|
// Medusa flat row shape: { option_id, value }
|
package/src/reviews-ui/labels.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Reviews UI label pack. The Alenika production storefront shipped this
|
|
3
3
|
* copy hardcoded in Bulgarian (src/components/product/reviews-list.tsx +
|
|
4
|
-
* src/app/(main)/review/[token]/review-form.tsx); the
|
|
4
|
+
* src/app/(main)/review/[token]/review-form.tsx); the Cartbase port lifts
|
|
5
5
|
* every string into the family's labels/labels-bg discipline. English
|
|
6
6
|
* defaults here; the exact production Bulgarian copy in `labels-bg.ts`.
|
|
7
7
|
*
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
/**
|
|
19
19
|
* Review media uploader — drag-drop zone + preview grid + the signed-PUT
|
|
20
20
|
* pipeline. Ported from the Alenika review-form's media handling
|
|
21
|
-
* (src/app/(main)/review/[token]/review-form.tsx) with the
|
|
21
|
+
* (src/app/(main)/review/[token]/review-form.tsx) with the Cartbase seam:
|
|
22
22
|
* the signed URL comes from `api/reviews.createUploadUrl(client, {token,
|
|
23
23
|
* name, type, size})` (docs/storefront/reviews.md) instead of the Medusa
|
|
24
24
|
* `/store/files/upload-url` helper; the raw-file PUT to R2 is unchanged.
|
|
@@ -15,7 +15,7 @@ import { StarRow } from "./star-badge"
|
|
|
15
15
|
* media is filtered out server-side — the client just renders what it
|
|
16
16
|
* gets).
|
|
17
17
|
*
|
|
18
|
-
* Verified-purchaser badge is unconditional — every
|
|
18
|
+
* Verified-purchaser badge is unconditional — every Cartbase review comes
|
|
19
19
|
* from a tokenized post-purchase email, so the badge is a tautology of
|
|
20
20
|
* the system, not an opt-in flag.
|
|
21
21
|
*
|
|
@@ -17,7 +17,7 @@ import { ReviewList } from "./review-list"
|
|
|
17
17
|
/**
|
|
18
18
|
* PDP reviews widget: aggregate header (score + distribution + sort) +
|
|
19
19
|
* masonry card list + load-more. Ported from the Alenika PDP section
|
|
20
|
-
* (src/components/product/reviews-list.tsx) with the
|
|
20
|
+
* (src/components/product/reviews-list.tsx) with the Cartbase data seam:
|
|
21
21
|
*
|
|
22
22
|
* - Bootstrap is ONE call — `api/reviews.getWidget(client, productId)`
|
|
23
23
|
* (aggregate + first page sized/sorted per the store's Settings →
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
* The review token wizard — everything behind `<review_link_base>/<token>`.
|
|
30
30
|
* Ported from the Alenika production storefront: the /review/[token]
|
|
31
31
|
* server page's branching (page.tsx) AND the two-step client form
|
|
32
|
-
* (review-form.tsx), merged into one component over the
|
|
32
|
+
* (review-form.tsx), merged into one component over the Cartbase SDK:
|
|
33
33
|
*
|
|
34
34
|
* entry: `resolveWizardEntry(validation)` — invalid/expired panels,
|
|
35
35
|
* the terminal "already submitted" panel, or the form at the
|
|
@@ -25,7 +25,7 @@ export type WizardEntry =
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
* THE resume rule (production behavior, verified against
|
|
28
|
+
* THE resume rule (production behavior, verified against Cartbase's token
|
|
29
29
|
* endpoint in batch 9): a consumed token does NOT mean "done".
|
|
30
30
|
*
|
|
31
31
|
* - `valid: false` → invalid panel
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Category page — ported from
|
|
3
3
|
* `@1click/ui/src/store/category-template.tsx` (v2.3.1). Data seam:
|
|
4
|
-
* `HttpTypes.StoreProductCategory` →
|
|
4
|
+
* `HttpTypes.StoreProductCategory` → Cartbase `StoreProductCategory`
|
|
5
5
|
* (`@cartbase/storefront/api/categories`); products come from the plain
|
|
6
6
|
* listing filtered by `category_id` (membership), via `PaginatedProducts`.
|
|
7
7
|
*
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* `category_children` — fetch the category with `include_ancestors_tree` +
|
|
10
10
|
* `include_descendants_tree` (`retrieveCategory` query flags), otherwise
|
|
11
11
|
* those sections simply don't render. Skeleton count is the fixed default
|
|
12
|
-
* (
|
|
12
|
+
* (Cartbase categories don't embed a products array).
|
|
13
13
|
*/
|
|
14
14
|
import { Suspense } from "react"
|
|
15
15
|
import { notFound } from "next/navigation"
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Collection page — ported from
|
|
3
3
|
* `@1click/ui/src/store/collection-template.tsx` (v2.3.1), REWIRED to the
|
|
4
|
-
*
|
|
4
|
+
* Cartbase collection MEMBERSHIP listing (docs/storefront/collections.md).
|
|
5
5
|
*
|
|
6
6
|
* The original filtered the plain products listing by `collection_id`
|
|
7
|
-
* (Medusa primary collection).
|
|
7
|
+
* (Medusa primary collection). Cartbase collections are curated groupings
|
|
8
8
|
* (manual or smart) with an admin-set `default_sort` — the membership
|
|
9
9
|
* endpoint `api/collections` `listCollectionProducts` honors it SERVER-side
|
|
10
10
|
* (manual position, title, EUR price asc/desc, newest/oldest, best_selling).
|
package/src/store/labels.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Store-family label map — ported from `@1click/ui/src/store/labels.ts`
|
|
3
3
|
* (v2.3.1), EXTENDED with the search-template keys (search-template is new
|
|
4
|
-
* in
|
|
4
|
+
* in Cartbase — no @1click source). i18n discipline: components merge
|
|
5
5
|
* `labels` props over these defaults; `labels-bg.ts` must cover every key
|
|
6
6
|
* (typed against the full shape — completeness is unit-tested).
|
|
7
7
|
*/
|
|
@@ -13,7 +13,7 @@ export type StoreLabels = {
|
|
|
13
13
|
latestArrivals: string
|
|
14
14
|
priceLowToHigh: string
|
|
15
15
|
priceHighToLow: string
|
|
16
|
-
// search-template (
|
|
16
|
+
// search-template (Cartbase addition)
|
|
17
17
|
searchTitle: string
|
|
18
18
|
searchPlaceholder: string
|
|
19
19
|
searchAction: string
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Paginated product grid — ported from
|
|
3
3
|
* `@1click/ui/src/store/paginated-products.tsx` (v2.3.1). Data seam: the
|
|
4
4
|
* Medusa `listProductsWithSort` server action (region-resolved via
|
|
5
|
-
* `countryCode`) becomes `api/products` `listProducts` with the
|
|
5
|
+
* `countryCode`) becomes `api/products` `listProducts` with the Cartbase
|
|
6
6
|
* pricing context (`currency_code`/`region_id`).
|
|
7
7
|
*
|
|
8
8
|
* Sorting (server truth first):
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Search results page — NEW in
|
|
2
|
+
* Search results page — NEW in Cartbase (no @1click source; the Medusa stores
|
|
3
3
|
* had no search endpoint). Built in the store-template idiom: filter
|
|
4
4
|
* sidebar, heading, product grid, pagination — over `api/search`
|
|
5
5
|
* `searchProducts` (docs/storefront/search.md).
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
/**
|
|
17
17
|
* ConsentBanner — the built-in Consent Mode v2 UI.
|
|
18
18
|
*
|
|
19
|
-
* Ported from the
|
|
19
|
+
* Ported from the Cartbase reference implementation
|
|
20
20
|
* `src/components/storefront/consent/consent-banner.tsx` (itself from
|
|
21
21
|
* `@1click/ui/src/tracking/consent-banner.tsx` v2.3.1, shipped on Alenika
|
|
22
22
|
* d3d3d39), parameterized per docs/cards/consent-management.md: copy +
|
|
@@ -3,7 +3,7 @@ import { CONSENT_INIT_SNIPPET } from "./consent"
|
|
|
3
3
|
/**
|
|
4
4
|
* ConsentInit — the synchronous Consent Mode v2 "default" snippet.
|
|
5
5
|
*
|
|
6
|
-
* Ported from the
|
|
6
|
+
* Ported from the Cartbase reference implementation
|
|
7
7
|
* `src/components/storefront/consent/consent-init.tsx` (itself from
|
|
8
8
|
* `@1click/ui/src/tracking/consent-init.tsx` v2.3.1); the snippet lives in
|
|
9
9
|
* ./consent.ts (CONSENT_INIT_SNIPPET, character-identical) so its ordering
|
package/src/tracking/consent.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Consent Mode v2 — shared state + appliers.
|
|
3
3
|
*
|
|
4
|
-
* Ported from the
|
|
4
|
+
* Ported from the Cartbase reference implementation
|
|
5
5
|
* `src/components/storefront/consent/consent.ts` (itself a verbatim port of
|
|
6
6
|
* `@1click/ui/src/tracking/consent.ts` v2.3.1, shipped on Alenika d3d3d39).
|
|
7
7
|
* THE COOKIE SEAM IS THE CONTRACT: the `_1c_consent` cookie name, JSON
|
|
8
8
|
* shape `{analytics, ads, ts}` and 12-month lifetime must stay IDENTICAL
|
|
9
9
|
* across the reference impl, this package, and any external CMP — every
|
|
10
|
-
*
|
|
10
|
+
* Cartbase-side gate reads it.
|
|
11
11
|
*
|
|
12
12
|
* One first-party cookie (`_1c_consent`) is the single source of truth
|
|
13
13
|
* for the visitor's choice. Three consumers read it:
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
* - fbq queues events client-side and sends nothing until 'grant'
|
|
28
28
|
*
|
|
29
29
|
* External-CMP escape hatch (consent-management card §4): in
|
|
30
|
-
* `mode: "external"`
|
|
30
|
+
* `mode: "external"` Cartbase renders no banner — the merchant's CMP must
|
|
31
31
|
* write the SAME consent state, either by setting the cookie itself or by
|
|
32
|
-
* calling `setConsent()` below. That one seam keeps every
|
|
32
|
+
* calling `setConsent()` below. That one seam keeps every Cartbase-side
|
|
33
33
|
* gate working with any outside app.
|
|
34
34
|
*
|
|
35
35
|
* Per-store configuration comes from `GET /api/store/consent`
|
|
@@ -123,7 +123,7 @@ export function applyConsent(choices: ConsentChoices): void {
|
|
|
123
123
|
* The documented external-CMP entry point: write the shared consent state
|
|
124
124
|
* (cookie) AND apply the live vendor update in one call. An outside CMP
|
|
125
125
|
* that calls this — or writes the cookie in the same shape — drives every
|
|
126
|
-
*
|
|
126
|
+
* Cartbase-side gate exactly like the built-in banner does.
|
|
127
127
|
*/
|
|
128
128
|
export function setConsent(choices: Omit<ConsentChoices, "ts"> & { ts?: number }): void {
|
|
129
129
|
const full: ConsentChoices = {
|
|
@@ -221,7 +221,7 @@ export type ConsentSettings = {
|
|
|
221
221
|
|
|
222
222
|
/**
|
|
223
223
|
* Mount `<ConsentBanner>` only when this is true. `external` mode renders
|
|
224
|
-
* NO
|
|
224
|
+
* NO Cartbase banner — the outside CMP owns the UI while `<ConsentInit>` +
|
|
225
225
|
* cookie gating keep working (the escape-hatch seam). Mirrors
|
|
226
226
|
* `shouldRenderBanner` in `src/lib/consent/model.ts`.
|
|
227
227
|
*/
|
package/src/tracking/fbq.ts
CHANGED
|
@@ -13,7 +13,7 @@ import type {
|
|
|
13
13
|
*
|
|
14
14
|
* Ported from `@1click/ui/src/tracking/fbq.ts` (v2.3.1) with ONE deliberate
|
|
15
15
|
* seam change: the browser-side `fireCapiEvent` dual-fire is REMOVED —
|
|
16
|
-
* in
|
|
16
|
+
* in Cartbase, server-side Conversions API sending is backend-owned (the
|
|
17
17
|
* `order.placed` forwarder, `src/lib/tracking/forward.ts` + admin-api.md
|
|
18
18
|
* §34). This package fires CLIENT Pixel events only; the ad-blocked /
|
|
19
19
|
* iOS-ATT recovery the dual-fire provided is covered by the backend CAPI
|
|
@@ -124,7 +124,7 @@ export function trackInitiateCheckout(data: InitiateCheckoutData): void {
|
|
|
124
124
|
|
|
125
125
|
/**
|
|
126
126
|
* Purchase event — eventID MUST equal `purchase_${order.display_id}`
|
|
127
|
-
* to dedupe with the
|
|
127
|
+
* to dedupe with the Cartbase backend's order.placed CAPI Purchase.
|
|
128
128
|
* Caller passes `order.display_id`; this helper builds the id so the
|
|
129
129
|
* format can never drift between the two sides.
|
|
130
130
|
*/
|
|
@@ -10,7 +10,7 @@ import type { TrackingAttribution, TrackingClientHints } from "./types"
|
|
|
10
10
|
* completion (consent-gated — only capture when the visitor's `_1c_consent`
|
|
11
11
|
* choice allows it; see store-api.md "Tracking block" wiring contract).
|
|
12
12
|
* Cart completion copies the keys onto `order.metadata`
|
|
13
|
-
* (`src/lib/carts/complete/_order.ts`) and the
|
|
13
|
+
* (`src/lib/carts/complete/_order.ts`) and the Cartbase `order.placed`
|
|
14
14
|
* forwarder reads exactly these keys to build the CAPI Purchase event's
|
|
15
15
|
* `user_data` and the GA4 Measurement Protocol Purchase's `client_id` /
|
|
16
16
|
* `session_id`.
|
|
@@ -8,7 +8,7 @@ import type { TrackingConfig, TrackingConfigResponse } from "./types"
|
|
|
8
8
|
* block-builder registry in `src/lib/integrations/store-config.ts`).
|
|
9
9
|
*
|
|
10
10
|
* CRITICAL ADAPTATION vs `@1click/ui`: the original read
|
|
11
|
-
* `{MEDUSA_BACKEND_URL}/store/integrations` from env vars; the
|
|
11
|
+
* `{MEDUSA_BACKEND_URL}/store/integrations` from env vars; the Cartbase
|
|
12
12
|
* version goes through the `StorefrontClient` (x-client-id + optional
|
|
13
13
|
* publishable key + locale handled by the ONE http seam). Only ENABLED
|
|
14
14
|
* integrations contribute keys, and only non-sensitive IDs appear
|
|
@@ -18,7 +18,7 @@ import type { TrackingConfig, TrackingConfigResponse } from "./types"
|
|
|
18
18
|
*
|
|
19
19
|
* The @1click predecessor once called `/store/integrations/tracking`,
|
|
20
20
|
* which 404'd and silently returned `{}` — pixel never loaded. The path
|
|
21
|
-
* here is the registered
|
|
21
|
+
* here is the registered Cartbase route (`src/app/api/store/integrations/
|
|
22
22
|
* route.ts`); the contract tests execute this exact call.
|
|
23
23
|
*
|
|
24
24
|
* Response is cached for 5 minutes when running inside Next.js
|
package/src/tracking/index.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @cartbase/storefront/tracking — Meta Pixel + GA4 + Rybbit + Consent Mode v2.
|
|
3
3
|
*
|
|
4
|
-
* Ported from `@1click/ui/src/tracking` (v2.3.1) with the
|
|
4
|
+
* Ported from `@1click/ui/src/tracking` (v2.3.1) with the Cartbase seams:
|
|
5
5
|
* - config comes from `GET /api/store/integrations` → `tracking` block
|
|
6
6
|
* via `getTrackingConfig(client)` (never env vars)
|
|
7
|
-
* - server-side CAPI / GA4-MP sending is
|
|
7
|
+
* - server-side CAPI / GA4-MP sending is Cartbase-backend-owned (the
|
|
8
8
|
* `order.placed` forwarder) — this package fires CLIENT events only
|
|
9
9
|
* and writes attribution for the server to inherit
|
|
10
10
|
* - Purchase dedupe: Pixel `eventID = "purchase_" + order.display_id`
|
|
@@ -101,7 +101,7 @@ declare global {
|
|
|
101
101
|
* of dedup compare the same digest).
|
|
102
102
|
*
|
|
103
103
|
* Also persists raw values via rememberKnownVisitor() so any future
|
|
104
|
-
* consumer of getKnownVisitor() sees them. In
|
|
104
|
+
* consumer of getKnownVisitor() sees them. In Cartbase the server-side
|
|
105
105
|
* CAPI enrichment is backend-owned (the order.placed forwarder reads
|
|
106
106
|
* hashed PII from the order itself), so this call's job is the
|
|
107
107
|
* browser-side Pixel Advanced Matching.
|
package/src/tracking/rybbit.tsx
CHANGED
|
@@ -12,7 +12,7 @@ import Script from "next/script"
|
|
|
12
12
|
*
|
|
13
13
|
* `siteId` and `baseUrl` are platform-managed — Rybbit is the platform's
|
|
14
14
|
* self-hosted, cookieless analytics, provisioned per store from platform
|
|
15
|
-
* env, and is deliberately ABSENT from the
|
|
15
|
+
* env, and is deliberately ABSENT from the Cartbase store-config `tracking`
|
|
16
16
|
* block (`src/lib/tracking/store-config-block.ts`). The host app passes
|
|
17
17
|
* the values as props directly (e.g. from its own env). The merchant
|
|
18
18
|
* never sees or configures them. Rybbit needs no consent gate.
|
package/src/tracking/types.ts
CHANGED
|
@@ -2,20 +2,20 @@
|
|
|
2
2
|
* Shared tracking types — Meta Pixel + Google Analytics 4 + consent.
|
|
3
3
|
*
|
|
4
4
|
* Ported from `@1click/ui/src/tracking/types.ts` (v2.3.1) and re-seated on
|
|
5
|
-
* the
|
|
5
|
+
* the Cartbase contract:
|
|
6
6
|
*
|
|
7
|
-
* - `TrackingConfig` mirrors the
|
|
7
|
+
* - `TrackingConfig` mirrors the Cartbase store-config `tracking` block
|
|
8
8
|
* served by `GET /api/store/integrations` (code truth:
|
|
9
9
|
* `src/lib/tracking/constants.ts` `StoreTrackingBlock` +
|
|
10
10
|
* `src/lib/tracking/store-block.ts`; contract: store-api.md "Tracking
|
|
11
|
-
* block"). Differences vs the @1click shape: `googleAds` is a
|
|
11
|
+
* block"). Differences vs the @1click shape: `googleAds` is a Cartbase
|
|
12
12
|
* extension, `consent_required` is new (consent-management card), and
|
|
13
13
|
* `rybbit` is deliberately ABSENT — Rybbit is platform analytics,
|
|
14
14
|
* provisioned from platform env, never a tenant integration; the
|
|
15
15
|
* `<Rybbit>` component takes its props directly from the host app.
|
|
16
16
|
*
|
|
17
17
|
* - Server-side CAPI + GA4 Measurement Protocol sending is
|
|
18
|
-
*
|
|
18
|
+
* Cartbase-backend-owned (the `order.placed` forwarder,
|
|
19
19
|
* `src/lib/tracking/forward.ts`) — this package fires CLIENT events
|
|
20
20
|
* only and writes attribution for the server to inherit.
|
|
21
21
|
*
|
|
@@ -37,7 +37,7 @@ export type TrackingConfig = {
|
|
|
37
37
|
gtm?: { containerId: string }
|
|
38
38
|
ga4?: { measurementId: string }
|
|
39
39
|
klaviyo?: { publicKey: string }
|
|
40
|
-
/**
|
|
40
|
+
/** Cartbase extension over the @1click TrackingConfig — explicit Google
|
|
41
41
|
* Ads tag config (the @1click engine wired Ads through GTM). */
|
|
42
42
|
googleAds?: { conversionId: string; conversionLabel?: string }
|
|
43
43
|
/** True when the store's consent CMP is enabled — client tags must
|
|
@@ -111,7 +111,7 @@ export type LeadData = {
|
|
|
111
111
|
/**
|
|
112
112
|
* Attribution payload written to `cart.metadata` before completion
|
|
113
113
|
* (consent-gated — see store-api.md "Tracking block" wiring contract).
|
|
114
|
-
* Cart completion copies these keys onto `order.metadata` and the
|
|
114
|
+
* Cart completion copies these keys onto `order.metadata` and the Cartbase
|
|
115
115
|
* `order.placed` forwarder reads exactly them to build the CAPI Purchase
|
|
116
116
|
* `user_data` and the GA4 MP Purchase `client_id` / `session_id`
|
|
117
117
|
* (code truth: `src/lib/tracking/constants.ts` TrackingAttributionMeta).
|