create-cartbase 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +21 -21
- package/template/app/docs/components.md +1134 -1090
- package/template/app/docs/integrations.md +15 -3
- package/template/app/package.json +1 -1
- package/template/app/src/app/layout.tsx +14 -0
- package/template/app/src/app/order/[id]/confirmed/page.tsx +92 -78
- package/template/app/tsconfig.tsbuildinfo +1 -1
|
@@ -56,6 +56,10 @@ key-by-key.
|
|
|
56
56
|
"ga4": { "measurementId": "G-…" },
|
|
57
57
|
"klaviyo": { "publicKey": "…" },
|
|
58
58
|
"googleAds": { "conversionId": "AW-…", "conversionLabel": "…" },
|
|
59
|
+
// Pixel id only. The TikTok Events API token stays on the platform
|
|
60
|
+
// and is used by the order.placed forwarder, exactly like Meta's
|
|
61
|
+
// CAPI token, which has never appeared on this endpoint.
|
|
62
|
+
"tiktok": { "pixelId": "C…" },
|
|
59
63
|
"consent_required": true // always present — mirrors the consent CMP's enabled
|
|
60
64
|
}
|
|
61
65
|
}
|
|
@@ -64,7 +68,10 @@ key-by-key.
|
|
|
64
68
|
- **Errors**: `400 missing_client_id` · `400 invalid_publishable_key`.
|
|
65
69
|
- **SDK**: `integrations.getIntegrationsConfig(client)`
|
|
66
70
|
- **Components**: carrier/locker pickers, COD fee row in checkout, tracking
|
|
67
|
-
mounts (`<MetaPixel>/<
|
|
71
|
+
mounts (`<MetaPixel>/<GoogleTag>/<TikTokPixel>/<Gtm>` behind the consent
|
|
72
|
+
gate). `<GoogleTag measurementId adsConversionId>` takes BOTH Google
|
|
73
|
+
destinations: one loader, two `config` calls, which is Google's own
|
|
74
|
+
instruction and is what lets Consent Mode gate the Ads tag for free.
|
|
68
75
|
- **Settings**: admin → Settings → Integrations (per-provider enable +
|
|
69
76
|
config); consent settings drive `tracking.consent_required`; Rybbit is
|
|
70
77
|
deliberately ABSENT (platform analytics, not a tenant integration).
|
|
@@ -72,8 +79,13 @@ key-by-key.
|
|
|
72
79
|
Tracking wiring contract: mount tags only through the consent gate when
|
|
73
80
|
`consent_required` (see [consent.md](consent.md)); Purchase events MUST use
|
|
74
81
|
`eventID = "purchase_" + order.display_id` so Meta dedupes browser Pixel vs
|
|
75
|
-
server CAPI
|
|
76
|
-
(consent-gated) so
|
|
82
|
+
server CAPI, and `event_id = "tt_purchase_" + order.display_id` for TikTok;
|
|
83
|
+
write TrackingAttribution keys into `cart.metadata` (consent-gated) so
|
|
84
|
+
server events inherit fbp/fbc/anon-id/ga signals AND the ad-click ids
|
|
85
|
+
(`tt_ttclid`, `tt_ttp`, `google_gclid`, `google_gbraid`, `google_wbraid`)
|
|
86
|
+
that let TikTok and Google tie a conversion back to the click that caused
|
|
87
|
+
it. Capture those with `captureClickIdsFromUrl()` beside
|
|
88
|
+
`captureUtmsFromUrl()`.
|
|
77
89
|
|
|
78
90
|
```bash
|
|
79
91
|
BODY=$(curl -sf "$BASE/api/store/integrations" -H "x-client-id: $CLIENT_ID")
|
|
@@ -5,6 +5,8 @@ import { retrieveCart, type Cart } from "@cartbase/storefront/api/carts"
|
|
|
5
5
|
import { getConsent } from "@cartbase/storefront/api/consent"
|
|
6
6
|
import { getMenu, type Menu } from "@cartbase/storefront/api/menus"
|
|
7
7
|
import { ConsentInit } from "@cartbase/storefront/tracking/consent-init"
|
|
8
|
+
import { StorefrontTags } from "@cartbase/storefront/tracking/storefront-tags"
|
|
9
|
+
import { TrackInit } from "@cartbase/storefront/tracking/track-init"
|
|
8
10
|
import { CartButtonClient } from "@cartbase/storefront/common/cart-button-client"
|
|
9
11
|
import { createStorefrontMetadata, PlatformInit } from "@cartbase/storefront/platform"
|
|
10
12
|
import { BARTER_CLIENT_ID, readCartCookie } from "@/lib/config"
|
|
@@ -80,6 +82,18 @@ export default async function RootLayout({
|
|
|
80
82
|
<html lang="en">
|
|
81
83
|
<body>
|
|
82
84
|
<ConsentInit />
|
|
85
|
+
{/* Every marketing tag the store configured in the admin, mounted
|
|
86
|
+
from its own config: Meta, TikTok, Google (GA4 + Ads on one
|
|
87
|
+
tag), GTM. Nothing to wire per vendor — saving the ids in
|
|
88
|
+
Settings → Integrations is the whole merchant-side act. Order
|
|
89
|
+
matters: ConsentInit sets the Consent Mode defaults
|
|
90
|
+
synchronously ABOVE this, so every tag below inherits the
|
|
91
|
+
gate. */}
|
|
92
|
+
<StorefrontTags client={client} />
|
|
93
|
+
{/* Captures UTMs and the ad-click ids (ttclid / gclid / gbraid /
|
|
94
|
+
wbraid) that exist ONLY on an ad's landing URL — miss them
|
|
95
|
+
here and no platform can attribute the order to the click. */}
|
|
96
|
+
<TrackInit />
|
|
83
97
|
<PlatformInit storeId={BARTER_CLIENT_ID} />
|
|
84
98
|
<Providers cart={cart} consent={consentRes.consent}>
|
|
85
99
|
<header className="border-b border-border">
|
|
@@ -1,78 +1,92 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import { useEffect, useState } from "react"
|
|
4
|
-
import Link from "next/link"
|
|
5
|
-
import type { CompletedOrder, CartLineItem } from "@cartbase/storefront/api/carts"
|
|
6
|
-
import {
|
|
7
|
-
OrderCompletedTemplate,
|
|
8
|
-
} from "@cartbase/storefront/order/order-completed-template"
|
|
9
|
-
import {
|
|
10
|
-
displayItemFromCartLine,
|
|
11
|
-
} from "@cartbase/storefront/order/order-item"
|
|
12
|
-
import {
|
|
13
|
-
orderTotalsFromSummary,
|
|
14
|
-
} from "@cartbase/storefront/order/order-totals"
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
</
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react"
|
|
4
|
+
import Link from "next/link"
|
|
5
|
+
import type { CompletedOrder, CartLineItem } from "@cartbase/storefront/api/carts"
|
|
6
|
+
import {
|
|
7
|
+
OrderCompletedTemplate,
|
|
8
|
+
} from "@cartbase/storefront/order/order-completed-template"
|
|
9
|
+
import {
|
|
10
|
+
displayItemFromCartLine,
|
|
11
|
+
} from "@cartbase/storefront/order/order-item"
|
|
12
|
+
import {
|
|
13
|
+
orderTotalsFromSummary,
|
|
14
|
+
} from "@cartbase/storefront/order/order-totals"
|
|
15
|
+
import { TrackOrderPurchase } from "@cartbase/storefront/tracking/track-order-purchase"
|
|
16
|
+
import { useTrackingConfig } from "@cartbase/storefront/tracking/use-tracking-config"
|
|
17
|
+
import { browserClient } from "@/lib/browser-client"
|
|
18
|
+
import { LAST_ORDER_STORAGE_KEY } from "../../../checkout/checkout-page-client"
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Order confirmation (runbook step 8.6 + orders.md): guests have NO
|
|
22
|
+
* anonymous order read — the `completeCart()` response is the only order
|
|
23
|
+
* handle, so the checkout page stashes it in sessionStorage and this page
|
|
24
|
+
* renders it through the order family. Totals come from the order's
|
|
25
|
+
* summary snapshot (`orderTotalsFromSummary`); items keep the decorated
|
|
26
|
+
* cart lines' server-computed totals (`displayItemFromCartLine`).
|
|
27
|
+
*/
|
|
28
|
+
type Stash = { order: CompletedOrder; cartItems: CartLineItem[] }
|
|
29
|
+
|
|
30
|
+
export default function OrderConfirmedPage() {
|
|
31
|
+
const [stash, setStash] = useState<Stash | null | "missing">(null)
|
|
32
|
+
// The store's public tag config — Google Ads needs the account id
|
|
33
|
+
// and the purchase label from it, and null means the conversion is
|
|
34
|
+
// skipped rather than fired malformed.
|
|
35
|
+
const tracking = useTrackingConfig(browserClient)
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
try {
|
|
39
|
+
const raw = sessionStorage.getItem(LAST_ORDER_STORAGE_KEY)
|
|
40
|
+
setStash(raw ? (JSON.parse(raw) as Stash) : "missing")
|
|
41
|
+
} catch {
|
|
42
|
+
setStash("missing")
|
|
43
|
+
}
|
|
44
|
+
}, [])
|
|
45
|
+
|
|
46
|
+
if (stash === null) return null // first paint before sessionStorage read
|
|
47
|
+
|
|
48
|
+
if (stash === "missing") {
|
|
49
|
+
return (
|
|
50
|
+
<div className="max-w-xl mx-auto px-4 py-16 text-center">
|
|
51
|
+
<h1 className="text-xl font-semibold mb-2">Order placed</h1>
|
|
52
|
+
<p className="text-muted-foreground mb-6">
|
|
53
|
+
Your order was placed successfully. A confirmation email is on its
|
|
54
|
+
way.
|
|
55
|
+
</p>
|
|
56
|
+
<Link href="/" className="underline">
|
|
57
|
+
Continue shopping
|
|
58
|
+
</Link>
|
|
59
|
+
</div>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const { order, cartItems } = stash
|
|
64
|
+
const totals = orderTotalsFromSummary(order.summary) ?? {
|
|
65
|
+
total: null,
|
|
66
|
+
}
|
|
67
|
+
// The completeCart() order carries FLATTENED items (checkout.md), not the
|
|
68
|
+
// order-detail pivot shape the template's default converter expects —
|
|
69
|
+
// pass the normalized cart-line items instead and drop `order.items`.
|
|
70
|
+
const orderForTemplate = { ...order, items: undefined }
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<>
|
|
74
|
+
{/* The purchase event, every configured vendor at once, fired once
|
|
75
|
+
per order per browser. Dedup keys match the platform's
|
|
76
|
+
server-side Purchase exactly, so the browser event and the
|
|
77
|
+
server event collapse into ONE conversion. */}
|
|
78
|
+
<TrackOrderPurchase order={order} items={cartItems} config={tracking} />
|
|
79
|
+
<OrderCompletedTemplate
|
|
80
|
+
order={orderForTemplate}
|
|
81
|
+
totals={totals}
|
|
82
|
+
items={cartItems.map(displayItemFromCartLine)}
|
|
83
|
+
// This reference store checks out via one merchant method (pp_* kill:
|
|
84
|
+
// methods are merchant-named, provider-less); a multi-tender store
|
|
85
|
+
// should stash the chosen tender from its checkout state alongside
|
|
86
|
+
// the order.
|
|
87
|
+
paymentMethodName={"Cash on delivery"}
|
|
88
|
+
storeHref="/"
|
|
89
|
+
/>
|
|
90
|
+
</>
|
|
91
|
+
)
|
|
92
|
+
}
|