@replohq/sdk 0.10.0 → 0.13.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/_vendor/schemas/generated/consent.d.ts +2 -2
- package/_vendor/schemas/loaderKeys.mjs +2 -0
- package/analytics/get-analytics-sinks.js +5 -1
- package/analytics/get-analytics-sinks.js.map +2 -2
- package/analytics/sinks/converge-sink.d.ts +21 -0
- package/analytics/sinks/converge-sink.js +51 -0
- package/analytics/sinks/converge-sink.js.map +7 -0
- package/analytics/sinks/northbeam-sink.d.ts +20 -0
- package/analytics/sinks/northbeam-sink.js +41 -0
- package/analytics/sinks/northbeam-sink.js.map +7 -0
- package/cart/buy-now-action.js +8 -12
- package/cart/buy-now-action.js.map +2 -2
- package/cart/cart-actions.js +13 -10
- package/cart/cart-actions.js.map +2 -2
- package/cart/cart-provider.js +5 -4
- package/cart/cart-provider.js.map +2 -2
- package/cart/gateways/cart-constants.d.ts +14 -0
- package/cart/gateways/cart-constants.js +23 -1
- package/cart/gateways/cart-constants.js.map +2 -2
- package/cart/storefront-urls.d.ts +10 -0
- package/cart/storefront-urls.js +44 -0
- package/cart/storefront-urls.js.map +7 -0
- package/cart/utils/cart-utils.d.ts +8 -0
- package/cart/utils/cart-utils.js +13 -1
- package/cart/utils/cart-utils.js.map +2 -2
- package/cart/utils/cookie-cart-persistence.js +4 -21
- package/cart/utils/cookie-cart-persistence.js.map +2 -2
- package/consent/script-snippets.js +19 -1
- package/consent/script-snippets.js.map +2 -2
- package/consent/types.d.ts +2 -2
- package/lib/buildMetadata.js +3 -3
- package/loaders/loader-keys.d.ts +2 -0
- package/loaders/loader-keys.js +2 -0
- package/loaders/loader-keys.js.map +2 -2
- package/loaders/loader-schemas.d.ts +40 -0
- package/loaders/loader-schemas.js +27 -0
- package/loaders/loader-schemas.js.map +2 -2
- package/loaders/loox-review-aggregate-loader.d.ts +12 -0
- package/loaders/loox-review-aggregate-loader.js +50 -0
- package/loaders/loox-review-aggregate-loader.js.map +7 -0
- package/loaders/loox-reviews-loader.d.ts +12 -0
- package/loaders/loox-reviews-loader.js +50 -0
- package/loaders/loox-reviews-loader.js.map +7 -0
- package/package.json +12 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type ReploScriptType = "GA4" | "GoogleTagManager" | "Meta" | "TikTok" | "Pinterest" | "Reddit" | "Snapchat" | "Hotjar" | "MicrosoftClarity" | "Contentsquare" | "Segment" | "Northbeam" | "Gorgias" | "Cookiebot" | "snippet" | "custom" | "consentPlatform";
|
|
1
|
+
export type ReploScriptType = "GA4" | "GoogleTagManager" | "Meta" | "TikTok" | "Pinterest" | "Reddit" | "Snapchat" | "Hotjar" | "MicrosoftClarity" | "Contentsquare" | "Segment" | "Northbeam" | "Gorgias" | "Converge" | "Cookiebot" | "snippet" | "custom" | "consentPlatform";
|
|
2
2
|
export type ConsentCategory = "necessary" | "analytics" | "marketing" | "preferences" | "sale_of_data";
|
|
3
3
|
export type ConsentMode = "off" | "simple" | "per-category";
|
|
4
4
|
export type ScriptTagDescriptor = {
|
|
@@ -13,7 +13,7 @@ export type ScriptTagDescriptor = {
|
|
|
13
13
|
module?: boolean;
|
|
14
14
|
};
|
|
15
15
|
export type ReploScriptEntry = {
|
|
16
|
-
type: "GA4" | "GoogleTagManager" | "Meta" | "TikTok" | "Pinterest" | "Reddit" | "Snapchat" | "Hotjar" | "MicrosoftClarity" | "Contentsquare" | "Segment" | "Northbeam" | "Gorgias";
|
|
16
|
+
type: "GA4" | "GoogleTagManager" | "Meta" | "TikTok" | "Pinterest" | "Reddit" | "Snapchat" | "Hotjar" | "MicrosoftClarity" | "Contentsquare" | "Segment" | "Northbeam" | "Gorgias" | "Converge";
|
|
17
17
|
identifier: string;
|
|
18
18
|
requiredConsent?: ConsentCategory[];
|
|
19
19
|
} | {
|
|
@@ -19,6 +19,8 @@ var DATA_LOADER_KEYS = {
|
|
|
19
19
|
YOTPO_PRODUCT_REVIEW_AGGREGATE: "yotpo.product_review_aggregate",
|
|
20
20
|
REVIEWS_IO_PRODUCT_REVIEWS: "reviews_io.product_reviews",
|
|
21
21
|
REVIEWS_IO_PRODUCT_REVIEW_AGGREGATE: "reviews_io.product_review_aggregate",
|
|
22
|
+
LOOX_PRODUCT_REVIEWS: "loox.product_reviews",
|
|
23
|
+
LOOX_PRODUCT_REVIEW_AGGREGATE: "loox.product_review_aggregate",
|
|
22
24
|
STATSIG_EXPERIMENT: "statsig.experiment",
|
|
23
25
|
CONTENTFUL_ENTRY: "contentful.entry",
|
|
24
26
|
CONTENTFUL_ENTRIES: "contentful.entries"
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { isConsentAllowed } from "../consent/consent-store";
|
|
2
|
+
import { ConvergeSink } from "./sinks/converge-sink";
|
|
2
3
|
import { GA4Sink } from "./sinks/ga4-sink";
|
|
3
4
|
import { MetaSink } from "./sinks/meta-sink";
|
|
5
|
+
import { NorthbeamSink } from "./sinks/northbeam-sink";
|
|
4
6
|
import { PinterestSink } from "./sinks/pinterest-sink";
|
|
5
7
|
import { RedditSink } from "./sinks/reddit-sink";
|
|
6
8
|
import { ReploSink } from "./sinks/replo-sink";
|
|
@@ -14,7 +16,9 @@ const PROVIDER_SINK_FACTORIES = {
|
|
|
14
16
|
TikTok: () => new TikTokSink(),
|
|
15
17
|
Snapchat: () => new SnapchatSink(),
|
|
16
18
|
Pinterest: () => new PinterestSink(),
|
|
17
|
-
Reddit: () => new RedditSink()
|
|
19
|
+
Reddit: () => new RedditSink(),
|
|
20
|
+
Northbeam: () => new NorthbeamSink(),
|
|
21
|
+
Converge: () => new ConvergeSink()
|
|
18
22
|
};
|
|
19
23
|
function getAnalyticsSinks({
|
|
20
24
|
consent,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../analytics/get-analytics-sinks.ts"],
|
|
4
|
-
"sourcesContent": ["import type { ReploScriptType } from \"schemas/generated/consent\";\nimport type { ConsentState } from \"../consent/consent-store\";\nimport type { RegisteredScript } from \"../consent/script-registration-store\";\nimport type { CheckoutProvider } from \"../lib/integration-utils\";\nimport type { AnalyticsSink } from \"./analytics-sink\";\n\nimport { isConsentAllowed } from \"../consent/consent-store\";\nimport { GA4Sink } from \"./sinks/ga4-sink\";\nimport { MetaSink } from \"./sinks/meta-sink\";\nimport { PinterestSink } from \"./sinks/pinterest-sink\";\nimport { RedditSink } from \"./sinks/reddit-sink\";\nimport { ReploSink } from \"./sinks/replo-sink\";\nimport { SnapchatSink } from \"./sinks/snapchat-sink\";\nimport { TikTokSink } from \"./sinks/tiktok-sink\";\nimport { WindowSink } from \"./sinks/window-sink\";\nimport { gateShopifyOwnedFunnelEvents } from \"./utils/shopify-funnel-gate\";\n\n/**\n * Named providers that have a corresponding ecommerce-event sink. The other\n * managed providers (GoogleTagManager, Hotjar, MicrosoftClarity, Segment) and
|
|
5
|
-
"mappings": "AAMA,SAAS,wBAAwB;AACjC,SAAS,eAAe;AACxB,SAAS,gBAAgB;AACzB,SAAS,qBAAqB;AAC9B,SAAS,kBAAkB;AAC3B,SAAS,iBAAiB;AAC1B,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAC3B,SAAS,kBAAkB;AAC3B,SAAS,oCAAoC;AAQ7C,MAAM,0BAEF;AAAA,EACF,KAAK,MAAM,IAAI,QAAQ;AAAA,EACvB,MAAM,MAAM,IAAI,SAAS;AAAA,EACzB,QAAQ,MAAM,IAAI,WAAW;AAAA,EAC7B,UAAU,MAAM,IAAI,aAAa;AAAA,EACjC,WAAW,MAAM,IAAI,cAAc;AAAA,EACnC,QAAQ,MAAM,IAAI,WAAW;
|
|
4
|
+
"sourcesContent": ["import type { ReploScriptType } from \"schemas/generated/consent\";\nimport type { ConsentState } from \"../consent/consent-store\";\nimport type { RegisteredScript } from \"../consent/script-registration-store\";\nimport type { CheckoutProvider } from \"../lib/integration-utils\";\nimport type { AnalyticsSink } from \"./analytics-sink\";\n\nimport { isConsentAllowed } from \"../consent/consent-store\";\nimport { ConvergeSink } from \"./sinks/converge-sink\";\nimport { GA4Sink } from \"./sinks/ga4-sink\";\nimport { MetaSink } from \"./sinks/meta-sink\";\nimport { NorthbeamSink } from \"./sinks/northbeam-sink\";\nimport { PinterestSink } from \"./sinks/pinterest-sink\";\nimport { RedditSink } from \"./sinks/reddit-sink\";\nimport { ReploSink } from \"./sinks/replo-sink\";\nimport { SnapchatSink } from \"./sinks/snapchat-sink\";\nimport { TikTokSink } from \"./sinks/tiktok-sink\";\nimport { WindowSink } from \"./sinks/window-sink\";\nimport { gateShopifyOwnedFunnelEvents } from \"./utils/shopify-funnel-gate\";\n\n/**\n * Named providers that have a corresponding ecommerce-event sink. The other\n * managed providers (GoogleTagManager, Hotjar, MicrosoftClarity, Segment,\n * Gorgias) and generic `snippet`/`custom` entries load scripts but do not map\n * to `AnalyticsSink` events today, so they never produce a sink here.\n */\nconst PROVIDER_SINK_FACTORIES: Partial<\n Record<ReploScriptType, () => AnalyticsSink>\n> = {\n GA4: () => new GA4Sink(),\n Meta: () => new MetaSink(),\n TikTok: () => new TikTokSink(),\n Snapchat: () => new SnapchatSink(),\n Pinterest: () => new PinterestSink(),\n Reddit: () => new RedditSink(),\n Northbeam: () => new NorthbeamSink(),\n Converge: () => new ConvergeSink(),\n};\n\n/**\n * Builds the set of analytics sinks that may fire given the current consent\n * state and which managed scripts are registered on the page.\n *\n * Consent gates data egress, not in-page plumbing:\n * - `WindowSink` is a local `CustomEvent` bus (no data leaves the device), so it\n * always ships regardless of consent. Its consumers self-gate: managed pixels\n * are already gated at the injection layer (they never mount without consent,\n * so they never subscribe), and unmanaged custom scripts are the author's\n * responsibility.\n * - `ReploSink` forwards into Replo's first-party analytics pipeline (real\n * egress / processing), so it counts as `analytics`-category tracking and is\n * gated by that consent. It is an implicit sink this scaffold always ships\n * (not tied to a registered entry) and is never editable in the miniapp.\n * Under a delegated platform, `analytics` reflects that platform's answer.\n *\n * Third-party provider sinks are created only when a matching `<ReploScripts>`\n * entry is registered AND its required consent is granted.\n */\nexport function getAnalyticsSinks({\n consent,\n registeredScripts,\n checkoutProvider,\n}: {\n consent: ConsentState;\n registeredScripts: RegisteredScript[];\n checkoutProvider?: CheckoutProvider;\n}): AnalyticsSink[] {\n const sinks: AnalyticsSink[] = [new WindowSink()];\n\n const firstPartyAllowed = isConsentAllowed({\n state: consent,\n requiredConsent: [\"analytics\"],\n });\n if (firstPartyAllowed) {\n sinks.push(new ReploSink());\n }\n\n // When Shopify hosts checkout, its native channels report the checkout-funnel\n // events to ad platforms server-side. Suppress those events on third-party\n // storefront pixels so they don't double-count Shopify's copy.\n const shopifyOwnsCheckout = checkoutProvider === \"shopify\";\n\n const addedTypes = new Set<ReploScriptType>();\n for (const script of registeredScripts) {\n const factory = PROVIDER_SINK_FACTORIES[script.type];\n if (!factory || addedTypes.has(script.type)) {\n continue;\n }\n if (\n isConsentAllowed({\n state: consent,\n requiredConsent: script.requiredConsent,\n })\n ) {\n const sink = factory();\n sinks.push(\n shopifyOwnsCheckout ? gateShopifyOwnedFunnelEvents(sink) : sink,\n );\n addedTypes.add(script.type);\n }\n }\n\n return sinks;\n}\n"],
|
|
5
|
+
"mappings": "AAMA,SAAS,wBAAwB;AACjC,SAAS,oBAAoB;AAC7B,SAAS,eAAe;AACxB,SAAS,gBAAgB;AACzB,SAAS,qBAAqB;AAC9B,SAAS,qBAAqB;AAC9B,SAAS,kBAAkB;AAC3B,SAAS,iBAAiB;AAC1B,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAC3B,SAAS,kBAAkB;AAC3B,SAAS,oCAAoC;AAQ7C,MAAM,0BAEF;AAAA,EACF,KAAK,MAAM,IAAI,QAAQ;AAAA,EACvB,MAAM,MAAM,IAAI,SAAS;AAAA,EACzB,QAAQ,MAAM,IAAI,WAAW;AAAA,EAC7B,UAAU,MAAM,IAAI,aAAa;AAAA,EACjC,WAAW,MAAM,IAAI,cAAc;AAAA,EACnC,QAAQ,MAAM,IAAI,WAAW;AAAA,EAC7B,WAAW,MAAM,IAAI,cAAc;AAAA,EACnC,UAAU,MAAM,IAAI,aAAa;AACnC;AAqBO,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AACF,GAIoB;AAClB,QAAM,QAAyB,CAAC,IAAI,WAAW,CAAC;AAEhD,QAAM,oBAAoB,iBAAiB;AAAA,IACzC,OAAO;AAAA,IACP,iBAAiB,CAAC,WAAW;AAAA,EAC/B,CAAC;AACD,MAAI,mBAAmB;AACrB,UAAM,KAAK,IAAI,UAAU,CAAC;AAAA,EAC5B;AAKA,QAAM,sBAAsB,qBAAqB;AAEjD,QAAM,aAAa,oBAAI,IAAqB;AAC5C,aAAW,UAAU,mBAAmB;AACtC,UAAM,UAAU,wBAAwB,OAAO,IAAI;AACnD,QAAI,CAAC,WAAW,WAAW,IAAI,OAAO,IAAI,GAAG;AAC3C;AAAA,IACF;AACA,QACE,iBAAiB;AAAA,MACf,OAAO;AAAA,MACP,iBAAiB,OAAO;AAAA,IAC1B,CAAC,GACD;AACA,YAAM,OAAO,QAAQ;AACrB,YAAM;AAAA,QACJ,sBAAsB,6BAA6B,IAAI,IAAI;AAAA,MAC7D;AACA,iBAAW,IAAI,OAAO,IAAI;AAAA,IAC5B;AAAA,EACF;AAEA,SAAO;AACT;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AnalyticsSink, DataPayload, PurchasePayload } from "../analytics-sink";
|
|
2
|
+
/**
|
|
3
|
+
* Event names and property shapes follow Converge's client-side event spec
|
|
4
|
+
* (https://docs.runconverge.com/sources/converge-spec); our funnel payload keys
|
|
5
|
+
* already match the spec's property names, so payloads pass through unmapped.
|
|
6
|
+
* The Converge snippet does not self-fire `$page_load`, so this sink owns every
|
|
7
|
+
* pageview: the NavigationTracker broadcast covers both the initial load and
|
|
8
|
+
* soft navigations, which is Converge's documented SPA integration.
|
|
9
|
+
*/
|
|
10
|
+
export declare class ConvergeSink implements AnalyticsSink {
|
|
11
|
+
private track;
|
|
12
|
+
pageView(): Promise<void>;
|
|
13
|
+
purchase({ amount, currency, data }: PurchasePayload): Promise<void>;
|
|
14
|
+
click(): Promise<void>;
|
|
15
|
+
productViewed({ data }: DataPayload): Promise<void>;
|
|
16
|
+
addToCart({ data }: DataPayload): Promise<void>;
|
|
17
|
+
removeFromCart({ data }: DataPayload): Promise<void>;
|
|
18
|
+
viewCart({ data }: DataPayload): Promise<void>;
|
|
19
|
+
startCheckout({ data }: DataPayload): Promise<void>;
|
|
20
|
+
viewCollection({ data }: DataPayload): Promise<void>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { createEventQueue } from "../utils/analytics-utils";
|
|
2
|
+
function cvgQueue() {
|
|
3
|
+
if (typeof window === "undefined") {
|
|
4
|
+
return null;
|
|
5
|
+
}
|
|
6
|
+
return window.cvg ?? null;
|
|
7
|
+
}
|
|
8
|
+
class ConvergeSink {
|
|
9
|
+
track = createEventQueue(
|
|
10
|
+
() => cvgQueue() !== null,
|
|
11
|
+
(eventName, properties) => {
|
|
12
|
+
cvgQueue()?.({ method: "track", eventName, properties });
|
|
13
|
+
},
|
|
14
|
+
"Converge"
|
|
15
|
+
);
|
|
16
|
+
async pageView() {
|
|
17
|
+
this.track("$page_load");
|
|
18
|
+
}
|
|
19
|
+
async purchase({ amount, currency, data }) {
|
|
20
|
+
this.track("Placed Order", {
|
|
21
|
+
...data,
|
|
22
|
+
total_price: amount,
|
|
23
|
+
currency,
|
|
24
|
+
...data.order_id === null || data.order_id === void 0 ? {} : { eventID: String(data.order_id) }
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
async click() {
|
|
28
|
+
}
|
|
29
|
+
async productViewed({ data }) {
|
|
30
|
+
this.track("Viewed Product", data);
|
|
31
|
+
}
|
|
32
|
+
async addToCart({ data }) {
|
|
33
|
+
this.track("Added To Cart", { ...data, quantity: data.quantity ?? 1 });
|
|
34
|
+
}
|
|
35
|
+
async removeFromCart({ data }) {
|
|
36
|
+
this.track("Removed From Cart", { ...data, quantity: data.quantity ?? 1 });
|
|
37
|
+
}
|
|
38
|
+
async viewCart({ data }) {
|
|
39
|
+
this.track("Viewed Cart", data);
|
|
40
|
+
}
|
|
41
|
+
async startCheckout({ data }) {
|
|
42
|
+
this.track("Started Checkout", data);
|
|
43
|
+
}
|
|
44
|
+
async viewCollection({ data }) {
|
|
45
|
+
this.track("Viewed Collection", data);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export {
|
|
49
|
+
ConvergeSink
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=converge-sink.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../analytics/sinks/converge-sink.ts"],
|
|
4
|
+
"sourcesContent": ["import type {\n AnalyticsSink,\n DataPayload,\n PurchasePayload,\n} from \"../analytics-sink\";\n\nimport { createEventQueue } from \"../utils/analytics-utils\";\n\ntype ConvergeTrackCall = {\n method: \"track\";\n eventName: string;\n properties?: Record<string, unknown>;\n};\n\ntype WindowWithCvg = Window & { cvg?: (call: ConvergeTrackCall) => void };\n\nfunction cvgQueue(): ((call: ConvergeTrackCall) => void) | null {\n if (typeof window === \"undefined\") {\n return null;\n }\n // oxlint-disable-next-line replo/no-as-cast -- the pixel's queue stub is an untyped window global; this is the one place it's narrowed to its known call shape\n return (window as WindowWithCvg).cvg ?? null;\n}\n\n/**\n * Event names and property shapes follow Converge's client-side event spec\n * (https://docs.runconverge.com/sources/converge-spec); our funnel payload keys\n * already match the spec's property names, so payloads pass through unmapped.\n * The Converge snippet does not self-fire `$page_load`, so this sink owns every\n * pageview: the NavigationTracker broadcast covers both the initial load and\n * soft navigations, which is Converge's documented SPA integration.\n */\nexport class ConvergeSink implements AnalyticsSink {\n private track = createEventQueue(\n () => cvgQueue() !== null,\n (eventName, properties) => {\n cvgQueue()?.({ method: \"track\", eventName, properties });\n },\n \"Converge\",\n );\n\n async pageView() {\n this.track(\"$page_load\");\n }\n\n async purchase({ amount, currency, data }: PurchasePayload) {\n // `eventID` dedupes the client-side conversion against Converge's\n // server-side order source (e.g. their Shopify backend integration).\n this.track(\"Placed Order\", {\n ...data,\n total_price: amount,\n currency,\n ...(data.order_id === null || data.order_id === undefined\n ? {}\n : { eventID: String(data.order_id) }),\n });\n }\n\n async click() {\n // No Converge equivalent\n }\n\n async productViewed({ data }: DataPayload) {\n this.track(\"Viewed Product\", data);\n }\n\n async addToCart({ data }: DataPayload) {\n this.track(\"Added To Cart\", { ...data, quantity: data.quantity ?? 1 });\n }\n\n async removeFromCart({ data }: DataPayload) {\n this.track(\"Removed From Cart\", { ...data, quantity: data.quantity ?? 1 });\n }\n\n async viewCart({ data }: DataPayload) {\n this.track(\"Viewed Cart\", data);\n }\n\n async startCheckout({ data }: DataPayload) {\n this.track(\"Started Checkout\", data);\n }\n\n async viewCollection({ data }: DataPayload) {\n this.track(\"Viewed Collection\", data);\n }\n}\n"],
|
|
5
|
+
"mappings": "AAMA,SAAS,wBAAwB;AAUjC,SAAS,WAAuD;AAC9D,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO;AAAA,EACT;AAEA,SAAQ,OAAyB,OAAO;AAC1C;AAUO,MAAM,aAAsC;AAAA,EACzC,QAAQ;AAAA,IACd,MAAM,SAAS,MAAM;AAAA,IACrB,CAAC,WAAW,eAAe;AACzB,eAAS,IAAI,EAAE,QAAQ,SAAS,WAAW,WAAW,CAAC;AAAA,IACzD;AAAA,IACA;AAAA,EACF;AAAA,EAEA,MAAM,WAAW;AACf,SAAK,MAAM,YAAY;AAAA,EACzB;AAAA,EAEA,MAAM,SAAS,EAAE,QAAQ,UAAU,KAAK,GAAoB;AAG1D,SAAK,MAAM,gBAAgB;AAAA,MACzB,GAAG;AAAA,MACH,aAAa;AAAA,MACb;AAAA,MACA,GAAI,KAAK,aAAa,QAAQ,KAAK,aAAa,SAC5C,CAAC,IACD,EAAE,SAAS,OAAO,KAAK,QAAQ,EAAE;AAAA,IACvC,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,QAAQ;AAAA,EAEd;AAAA,EAEA,MAAM,cAAc,EAAE,KAAK,GAAgB;AACzC,SAAK,MAAM,kBAAkB,IAAI;AAAA,EACnC;AAAA,EAEA,MAAM,UAAU,EAAE,KAAK,GAAgB;AACrC,SAAK,MAAM,iBAAiB,EAAE,GAAG,MAAM,UAAU,KAAK,YAAY,EAAE,CAAC;AAAA,EACvE;AAAA,EAEA,MAAM,eAAe,EAAE,KAAK,GAAgB;AAC1C,SAAK,MAAM,qBAAqB,EAAE,GAAG,MAAM,UAAU,KAAK,YAAY,EAAE,CAAC;AAAA,EAC3E;AAAA,EAEA,MAAM,SAAS,EAAE,KAAK,GAAgB;AACpC,SAAK,MAAM,eAAe,IAAI;AAAA,EAChC;AAAA,EAEA,MAAM,cAAc,EAAE,KAAK,GAAgB;AACzC,SAAK,MAAM,oBAAoB,IAAI;AAAA,EACrC;AAAA,EAEA,MAAM,eAAe,EAAE,KAAK,GAAgB;AAC1C,SAAK,MAAM,qBAAqB,IAAI;AAAA,EACtC;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AnalyticsSink } from "../analytics-sink";
|
|
2
|
+
import type { PageMetadata } from "../utils/navigation-tracker";
|
|
3
|
+
/**
|
|
4
|
+
* The Northbeam snippet does not self-fire `trackPageViewInitial`, so this
|
|
5
|
+
* sink owns every pageview: the NavigationTracker broadcast covers both the
|
|
6
|
+
* initial load and soft navigations. Purchases are not fired client-side —
|
|
7
|
+
* Northbeam ingests orders server-side via its own commerce integrations.
|
|
8
|
+
*/
|
|
9
|
+
export declare class NorthbeamSink implements AnalyticsSink {
|
|
10
|
+
private call;
|
|
11
|
+
pageView(metadata?: PageMetadata): Promise<void>;
|
|
12
|
+
purchase(): Promise<void>;
|
|
13
|
+
click(): Promise<void>;
|
|
14
|
+
productViewed(): Promise<void>;
|
|
15
|
+
addToCart(): Promise<void>;
|
|
16
|
+
removeFromCart(): Promise<void>;
|
|
17
|
+
viewCart(): Promise<void>;
|
|
18
|
+
startCheckout(): Promise<void>;
|
|
19
|
+
viewCollection(): Promise<void>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createMethodQueue } from "../utils/analytics-utils";
|
|
2
|
+
function northbeamPixel() {
|
|
3
|
+
if (typeof window === "undefined") {
|
|
4
|
+
return null;
|
|
5
|
+
}
|
|
6
|
+
return window.Northbeam ?? null;
|
|
7
|
+
}
|
|
8
|
+
let hasFiredInitialPageView = false;
|
|
9
|
+
class NorthbeamSink {
|
|
10
|
+
call = createMethodQueue(northbeamPixel, {
|
|
11
|
+
queueName: "Northbeam"
|
|
12
|
+
});
|
|
13
|
+
async pageView(metadata) {
|
|
14
|
+
if (!hasFiredInitialPageView) {
|
|
15
|
+
hasFiredInitialPageView = true;
|
|
16
|
+
this.call("trackPageViewInitial", metadata?.url ?? window.location.href);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
this.call("trackPageView");
|
|
20
|
+
}
|
|
21
|
+
async purchase() {
|
|
22
|
+
}
|
|
23
|
+
async click() {
|
|
24
|
+
}
|
|
25
|
+
async productViewed() {
|
|
26
|
+
}
|
|
27
|
+
async addToCart() {
|
|
28
|
+
}
|
|
29
|
+
async removeFromCart() {
|
|
30
|
+
}
|
|
31
|
+
async viewCart() {
|
|
32
|
+
}
|
|
33
|
+
async startCheckout() {
|
|
34
|
+
}
|
|
35
|
+
async viewCollection() {
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
NorthbeamSink
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=northbeam-sink.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../analytics/sinks/northbeam-sink.ts"],
|
|
4
|
+
"sourcesContent": ["import type { AnalyticsSink } from \"../analytics-sink\";\nimport type { PageMetadata } from \"../utils/navigation-tracker\";\n\nimport { createMethodQueue } from \"../utils/analytics-utils\";\n\ntype NorthbeamPixel = Record<string, (...args: unknown[]) => void>;\n\ntype WindowWithNorthbeam = Window & { Northbeam?: NorthbeamPixel };\n\nfunction northbeamPixel(): NorthbeamPixel | null {\n if (typeof window === \"undefined\") {\n return null;\n }\n // oxlint-disable-next-line replo/no-as-cast -- the pixel's method map is an untyped window global; this is the one place it's narrowed to its known shape\n return (window as WindowWithNorthbeam).Northbeam ?? null;\n}\n\n// Northbeam distinguishes the first pageview of a page load\n// (`trackPageViewInitial`) from subsequent SPA pageviews (`trackPageView`).\n// The flag lives at module scope so it survives sink rebuilds (sinks are\n// recreated whenever consent or script registration changes) and still resets\n// on every hard page load.\nlet hasFiredInitialPageView = false;\n\n/**\n * The Northbeam snippet does not self-fire `trackPageViewInitial`, so this\n * sink owns every pageview: the NavigationTracker broadcast covers both the\n * initial load and soft navigations. Purchases are not fired client-side \u2014\n * Northbeam ingests orders server-side via its own commerce integrations.\n */\nexport class NorthbeamSink implements AnalyticsSink {\n private call = createMethodQueue<NorthbeamPixel>(northbeamPixel, {\n queueName: \"Northbeam\",\n });\n\n async pageView(metadata?: PageMetadata) {\n if (!hasFiredInitialPageView) {\n hasFiredInitialPageView = true;\n this.call(\"trackPageViewInitial\", metadata?.url ?? window.location.href);\n return;\n }\n this.call(\"trackPageView\");\n }\n\n async purchase() {\n // Northbeam ingests orders server-side via its commerce integrations\n }\n\n async click() {\n // No Northbeam equivalent\n }\n\n async productViewed() {\n // No Northbeam equivalent\n }\n\n async addToCart() {\n // No Northbeam equivalent\n }\n\n async removeFromCart() {\n // No Northbeam equivalent\n }\n\n async viewCart() {\n // No Northbeam equivalent\n }\n\n async startCheckout() {\n // No Northbeam equivalent\n }\n\n async viewCollection() {\n // No Northbeam equivalent\n }\n}\n"],
|
|
5
|
+
"mappings": "AAGA,SAAS,yBAAyB;AAMlC,SAAS,iBAAwC;AAC/C,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO;AAAA,EACT;AAEA,SAAQ,OAA+B,aAAa;AACtD;AAOA,IAAI,0BAA0B;AAQvB,MAAM,cAAuC;AAAA,EAC1C,OAAO,kBAAkC,gBAAgB;AAAA,IAC/D,WAAW;AAAA,EACb,CAAC;AAAA,EAED,MAAM,SAAS,UAAyB;AACtC,QAAI,CAAC,yBAAyB;AAC5B,gCAA0B;AAC1B,WAAK,KAAK,wBAAwB,UAAU,OAAO,OAAO,SAAS,IAAI;AACvE;AAAA,IACF;AACA,SAAK,KAAK,eAAe;AAAA,EAC3B;AAAA,EAEA,MAAM,WAAW;AAAA,EAEjB;AAAA,EAEA,MAAM,QAAQ;AAAA,EAEd;AAAA,EAEA,MAAM,gBAAgB;AAAA,EAEtB;AAAA,EAEA,MAAM,YAAY;AAAA,EAElB;AAAA,EAEA,MAAM,iBAAiB;AAAA,EAEvB;AAAA,EAEA,MAAM,WAAW;AAAA,EAEjB;AAAA,EAEA,MAAM,gBAAgB;AAAA,EAEtB;AAAA,EAEA,MAAM,iBAAiB;AAAA,EAEvB;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/cart/buy-now-action.js
CHANGED
|
@@ -8,6 +8,7 @@ import { getCheckoutProvider } from "../lib/integration-utils";
|
|
|
8
8
|
import { buildBaseUrl } from "../lib/url-utils";
|
|
9
9
|
import { createCartAction } from "./cart-actions";
|
|
10
10
|
import { REPLO_ATTRIBUTION_PROPERTY } from "./cart-types";
|
|
11
|
+
import { getStorefrontUrls } from "./storefront-urls";
|
|
11
12
|
class BuyNowError extends CanopyError {
|
|
12
13
|
}
|
|
13
14
|
class BuyNowCartCreationError extends CanopyError {
|
|
@@ -19,9 +20,13 @@ async function buyNowAction(input) {
|
|
|
19
20
|
if (!host) {
|
|
20
21
|
throw new BuyNowError({ message: "Missing host header" });
|
|
21
22
|
}
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
const { origin, cancelUrl } = getStorefrontUrls({
|
|
24
|
+
host,
|
|
25
|
+
forwardedHost: headersList.get("x-forwarded-host"),
|
|
26
|
+
forwardedProto: headersList.get("x-forwarded-proto"),
|
|
27
|
+
requestOrigin: headersList.get("origin"),
|
|
28
|
+
pageUrl
|
|
29
|
+
});
|
|
25
30
|
const checkoutProvider = await getCheckoutProvider();
|
|
26
31
|
const cartLines = lineItems.map((item) => {
|
|
27
32
|
const hasReploAttribution = item.properties.some(
|
|
@@ -37,15 +42,6 @@ async function buyNowAction(input) {
|
|
|
37
42
|
});
|
|
38
43
|
if (checkoutProvider === "stripe") {
|
|
39
44
|
const env = await getEnv();
|
|
40
|
-
const isSameOrigin = (() => {
|
|
41
|
-
try {
|
|
42
|
-
return Boolean(pageUrl) && new URL(pageUrl).origin === origin;
|
|
43
|
-
} catch {
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
})();
|
|
47
|
-
const safePageUrl = isSameOrigin ? pageUrl : origin;
|
|
48
|
-
const cancelUrl = safePageUrl;
|
|
49
45
|
const temporaryCartId = uuidv4();
|
|
50
46
|
const body = {
|
|
51
47
|
cartId: temporaryCartId,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../cart/buy-now-action.ts"],
|
|
4
|
-
"sourcesContent": ["\"use server\";\n\nimport type { CartLineAttribute } from \"./cart-types\";\n\nimport { headers } from \"next/headers\";\n\nimport { v4 as uuidv4 } from \"uuid\";\nimport { z } from \"zod\";\n\nimport { getEnv } from \"../env\";\nimport { CanopyError } from \"../lib/canopy-error\";\nimport { getCheckoutProvider } from \"../lib/integration-utils\";\nimport { buildBaseUrl } from \"../lib/url-utils\";\nimport { createCartAction } from \"./cart-actions\";\nimport { REPLO_ATTRIBUTION_PROPERTY } from \"./cart-types\";\n\nclass BuyNowError extends CanopyError {}\nclass BuyNowCartCreationError extends CanopyError {}\n\n/**\n * Server action that handles the full buy-now flow:\n * 1. For Shopify: creates a cart via the canopy-api-backed gateway, returns checkoutUrl.\n * 2. For Stripe: creates a Stripe checkout session via canopy-api, returns session URL.\n *\n * Variant IDs are expected to already be Storefront GIDs (from data loader output).\n * No internal\u2192external ID mapping is needed.\n */\nexport async function buyNowAction(input: {\n lineItems: {\n variantId: string;\n quantity: number;\n sellingPlanId: string | null;\n properties: CartLineAttribute[];\n }[];\n discountCodes: string[];\n pageUrl: string;\n}): Promise<{ redirectUrl: string }> {\n const { lineItems, discountCodes, pageUrl } = input;\n\n const headersList = await headers();\n const host = headersList.get(\"host\");\n if (!host) {\n throw new BuyNowError({ message: \"Missing host header\" });\n }\n const
|
|
5
|
-
"mappings": ";AAIA,SAAS,eAAe;AAExB,SAAS,MAAM,cAAc;AAC7B,SAAS,SAAS;AAElB,SAAS,cAAc;AACvB,SAAS,mBAAmB;AAC5B,SAAS,2BAA2B;AACpC,SAAS,oBAAoB;AAC7B,SAAS,wBAAwB;AACjC,SAAS,kCAAkC;
|
|
4
|
+
"sourcesContent": ["\"use server\";\n\nimport type { CartLineAttribute } from \"./cart-types\";\n\nimport { headers } from \"next/headers\";\n\nimport { v4 as uuidv4 } from \"uuid\";\nimport { z } from \"zod\";\n\nimport { getEnv } from \"../env\";\nimport { CanopyError } from \"../lib/canopy-error\";\nimport { getCheckoutProvider } from \"../lib/integration-utils\";\nimport { buildBaseUrl } from \"../lib/url-utils\";\nimport { createCartAction } from \"./cart-actions\";\nimport { REPLO_ATTRIBUTION_PROPERTY } from \"./cart-types\";\nimport { getStorefrontUrls } from \"./storefront-urls\";\n\nclass BuyNowError extends CanopyError {}\nclass BuyNowCartCreationError extends CanopyError {}\n\n/**\n * Server action that handles the full buy-now flow:\n * 1. For Shopify: creates a cart via the canopy-api-backed gateway, returns checkoutUrl.\n * 2. For Stripe: creates a Stripe checkout session via canopy-api, returns session URL.\n *\n * Variant IDs are expected to already be Storefront GIDs (from data loader output).\n * No internal\u2192external ID mapping is needed.\n */\nexport async function buyNowAction(input: {\n lineItems: {\n variantId: string;\n quantity: number;\n sellingPlanId: string | null;\n properties: CartLineAttribute[];\n }[];\n discountCodes: string[];\n pageUrl: string;\n}): Promise<{ redirectUrl: string }> {\n const { lineItems, discountCodes, pageUrl } = input;\n\n const headersList = await headers();\n const host = headersList.get(\"host\");\n if (!host) {\n throw new BuyNowError({ message: \"Missing host header\" });\n }\n const { origin, cancelUrl } = getStorefrontUrls({\n host,\n forwardedHost: headersList.get(\"x-forwarded-host\"),\n forwardedProto: headersList.get(\"x-forwarded-proto\"),\n requestOrigin: headersList.get(\"origin\"),\n pageUrl,\n });\n\n const checkoutProvider = await getCheckoutProvider();\n\n const cartLines = lineItems.map((item) => {\n const hasReploAttribution = item.properties.some(\n (prop) => prop.key === REPLO_ATTRIBUTION_PROPERTY.key,\n );\n const properties = hasReploAttribution\n ? item.properties\n : [REPLO_ATTRIBUTION_PROPERTY, ...item.properties];\n\n return {\n id: item.variantId,\n quantity: item.quantity,\n sellingPlanId: item.sellingPlanId,\n properties,\n };\n });\n\n if (checkoutProvider === \"stripe\") {\n const env = await getEnv();\n const temporaryCartId = uuidv4();\n\n const body = {\n cartId: temporaryCartId,\n lines: cartLines.map((line) => ({\n variantId: line.id,\n quantity: line.quantity,\n properties: line.properties,\n })),\n projectId: env.PROJECT_ID,\n // canopy appends session_id={CHECKOUT_SESSION_ID} at session creation;\n // the success route resolves it into the order status page.\n successUrl: `${origin}/checkout/success`,\n cancelUrl,\n };\n\n const canopyApiUrl = `${buildBaseUrl(env.CANOPY_API_HOST)}/api/v1/stripe-checkout/create-session`;\n const response = await fetch(canopyApiUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n throw new BuyNowCartCreationError({\n message: \"Failed to create Stripe checkout session for buy now\",\n additionalData: {\n status: response.status,\n statusText: response.statusText,\n },\n });\n }\n\n const data = z.object({ url: z.string() }).safeParse(await response.json());\n if (!data.success) {\n throw new BuyNowCartCreationError({\n message: \"Stripe checkout session response missing redirect URL\",\n cause: data.error,\n });\n }\n return { redirectUrl: data.data.url };\n }\n\n // Shopify and other integrations: create cart via canopy-api-backed gateway\n const cart = await createCartAction({\n lines: cartLines,\n skipStoringCart: true,\n discountCodes: discountCodes.length > 0 ? discountCodes : undefined,\n });\n\n if (!cart?.checkoutUrl) {\n throw new BuyNowCartCreationError({\n message: \"Failed to create cart or get checkout URL\",\n additionalData: { lineItems, discountCodes },\n });\n }\n\n const checkoutUrl = new URL(cart.checkoutUrl, origin);\n return { redirectUrl: checkoutUrl.toString() };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/cart/cart-actions.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
"use server";
|
|
2
2
|
import { cookies } from "next/headers";
|
|
3
3
|
import { getCheckoutProvider } from "../lib/integration-utils";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
getCartCookieOptions,
|
|
6
|
+
REPLO_CART_COOKIE_NAME
|
|
7
|
+
} from "./gateways/cart-constants";
|
|
5
8
|
import { getCartGateway } from "./gateways/cart-gateway";
|
|
6
9
|
import {
|
|
7
10
|
loadCartFromCookie,
|
|
8
11
|
saveCartToCookie
|
|
9
12
|
} from "./utils/cookie-cart-persistence";
|
|
10
13
|
const SHOPIFY_CART_COOKIE_NAME = "replo_cart_id";
|
|
11
|
-
const CART_COOKIE_MAX_AGE = 60 * 60 * 24 * 30;
|
|
12
14
|
async function getOrCreateCartAction() {
|
|
13
15
|
const existingCart = await getCartAction();
|
|
14
16
|
if (existingCart) {
|
|
@@ -30,7 +32,10 @@ async function getCartAction() {
|
|
|
30
32
|
const cart = await cartGateway.getCart(existingCartId);
|
|
31
33
|
if (!cart) {
|
|
32
34
|
try {
|
|
33
|
-
cookieStore.
|
|
35
|
+
cookieStore.set(SHOPIFY_CART_COOKIE_NAME, "", {
|
|
36
|
+
...getCartCookieOptions(),
|
|
37
|
+
maxAge: 0
|
|
38
|
+
});
|
|
34
39
|
} catch (error) {
|
|
35
40
|
console.warn("Failed to delete cart cookie:", error);
|
|
36
41
|
}
|
|
@@ -48,13 +53,11 @@ async function createCartAction(options) {
|
|
|
48
53
|
await saveCartToCookie(cart, REPLO_CART_COOKIE_NAME);
|
|
49
54
|
} else {
|
|
50
55
|
const cookieStore = await cookies();
|
|
51
|
-
cookieStore.set(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
path: "/"
|
|
57
|
-
});
|
|
56
|
+
cookieStore.set(
|
|
57
|
+
SHOPIFY_CART_COOKIE_NAME,
|
|
58
|
+
cart.id,
|
|
59
|
+
getCartCookieOptions()
|
|
60
|
+
);
|
|
58
61
|
}
|
|
59
62
|
} catch (cookieError) {
|
|
60
63
|
console.warn("Failed to set cart cookie:", cookieError);
|
package/cart/cart-actions.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../cart/cart-actions.ts"],
|
|
4
|
-
"sourcesContent": ["\"use server\";\n\nimport type {\n AddToCartPayload,\n Cart,\n CartLinePayload,\n RemoveFromCartPayload,\n UpdateCartLineItemPayload,\n} from \"./cart-types\";\n\nimport { cookies } from \"next/headers\";\n\nimport { getCheckoutProvider } from \"../lib/integration-utils\";\nimport {
|
|
5
|
-
"mappings": ";AAUA,SAAS,eAAe;AAExB,SAAS,2BAA2B;AACpC,
|
|
4
|
+
"sourcesContent": ["\"use server\";\n\nimport type {\n AddToCartPayload,\n Cart,\n CartLinePayload,\n RemoveFromCartPayload,\n UpdateCartLineItemPayload,\n} from \"./cart-types\";\n\nimport { cookies } from \"next/headers\";\n\nimport { getCheckoutProvider } from \"../lib/integration-utils\";\nimport {\n getCartCookieOptions,\n REPLO_CART_COOKIE_NAME,\n} from \"./gateways/cart-constants\";\nimport { getCartGateway } from \"./gateways/cart-gateway\";\nimport {\n loadCartFromCookie,\n saveCartToCookie,\n} from \"./utils/cookie-cart-persistence\";\n\nconst SHOPIFY_CART_COOKIE_NAME = \"replo_cart_id\";\n\n/**\n * Gets the existing cart or creates a new one if none exists. This is necessary\n * because we may not have cookies in the server context during initial load.\n *\n * See: https://nextjs.org/docs/app/api-reference/functions/cookies#understanding-cookie-behavior-in-server-components\n *\n * @returns The existing cart or a newly created cart\n */\nexport async function getOrCreateCartAction(): Promise<Cart | null> {\n const existingCart = await getCartAction();\n if (existingCart) {\n return existingCart;\n }\n return await createCartAction();\n}\n\n/**\n * Retrieves the current cart from cookies. Returns null if no cart exists or\n * if the cart has expired/been deleted.\n *\n * @returns The current cart or null if none exists\n */\nexport async function getCartAction(): Promise<Cart | null> {\n const checkoutProvider = await getCheckoutProvider();\n\n if (checkoutProvider === \"stripe\") {\n return await loadCartFromCookie(REPLO_CART_COOKIE_NAME);\n }\n\n const cookieStore = await cookies();\n const existingCartId = cookieStore.get(SHOPIFY_CART_COOKIE_NAME)?.value;\n\n if (!existingCartId) {\n return null;\n }\n\n const cartGateway = await getCartGateway();\n const cart = await cartGateway.getCart(existingCartId);\n\n // If we had a cart ID in the cookie but couldn't fetch the cart,\n // it means the cart is invalid/expired - clear the cookie\n if (!cart) {\n try {\n cookieStore.set(SHOPIFY_CART_COOKIE_NAME, \"\", {\n ...getCartCookieOptions(),\n maxAge: 0,\n });\n } catch (error) {\n console.warn(\"Failed to delete cart cookie:\", error);\n }\n }\n\n return cart;\n}\n\n/**\n * Creates a new cart with optional initial line items. The cart ID is stored\n * in a cookie for future retrieval.\n *\n * @param options.lines - Optional initial cart line items\n * @param options.skipStoringCart - If true, skips storing the cart in cookies (used for buy-now flows)\n * @param options.discountCodes - Optional discount codes to apply on cart creation\n * @returns The newly created cart\n */\nexport async function createCartAction(options?: {\n lines?: CartLinePayload[];\n skipStoringCart?: boolean;\n discountCodes?: string[];\n}): Promise<Cart | null> {\n const { lines, skipStoringCart = false, discountCodes } = options ?? {};\n const checkoutProvider = await getCheckoutProvider();\n const cartGateway = await getCartGateway();\n const cart = await cartGateway.createCart(lines, discountCodes);\n\n if (cart && !skipStoringCart) {\n // Set the cart ID in a cookie - this works in Server Action context\n try {\n if (checkoutProvider === \"stripe\") {\n await saveCartToCookie(cart, REPLO_CART_COOKIE_NAME);\n } else {\n const cookieStore = await cookies();\n cookieStore.set(\n SHOPIFY_CART_COOKIE_NAME,\n cart.id,\n getCartCookieOptions(),\n );\n }\n } catch (cookieError) {\n console.warn(\"Failed to set cart cookie:\", cookieError);\n }\n }\n\n return cart;\n}\n\n/**\n * Adds line items to an existing cart.\n *\n * @param lines - Array of cart line items to add\n * @param cartId - The ID of the cart to add items to\n * @returns The updated cart\n */\nexport async function addToCartAction(\n lines: AddToCartPayload[\"lines\"],\n cartId: string,\n): Promise<Cart | null> {\n const cartGateway = await getCartGateway();\n return await cartGateway.addToCart({ lines, cartId });\n}\n\n/**\n * Updates a cart line item (typically quantity).\n *\n * @param line - The cart line item with updated values\n * @param cartId - The ID of the cart containing the line item\n * @returns The updated cart\n */\nexport async function updateCartLineItemAction(\n line: UpdateCartLineItemPayload[\"line\"],\n cartId: string,\n): Promise<Cart | null> {\n const cartGateway = await getCartGateway();\n return await cartGateway.updateCartLineItem({ line, cartId });\n}\n\n/**\n * Removes one or more line items from the cart.\n *\n * @param lineIds - Array of line item IDs to remove\n * @param cartId - The ID of the cart to remove items from\n * @returns The updated cart\n */\nexport async function removeCartLineItemsAction(\n lineIds: RemoveFromCartPayload[\"lineIds\"],\n cartId: string,\n): Promise<Cart | null> {\n const cartGateway = await getCartGateway();\n return await cartGateway.removeCartLineItems({ lineIds, cartId });\n}\n\n/**\n * Updates discount codes for a cart.\n *\n * @param discountCodes - Array of discount code strings to apply\n * @param cartId - The ID of the cart to update\n * @returns The updated cart\n */\nexport async function updateCartDiscountCodesAction(\n discountCodes: string[],\n cartId: string,\n): Promise<Cart | null> {\n const cartGateway = await getCartGateway();\n return await cartGateway.updateCartDiscountCodes({ discountCodes, cartId });\n}\n"],
|
|
5
|
+
"mappings": ";AAUA,SAAS,eAAe;AAExB,SAAS,2BAA2B;AACpC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,sBAAsB;AAC/B;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEP,MAAM,2BAA2B;AAUjC,eAAsB,wBAA8C;AAClE,QAAM,eAAe,MAAM,cAAc;AACzC,MAAI,cAAc;AAChB,WAAO;AAAA,EACT;AACA,SAAO,MAAM,iBAAiB;AAChC;AAQA,eAAsB,gBAAsC;AAC1D,QAAM,mBAAmB,MAAM,oBAAoB;AAEnD,MAAI,qBAAqB,UAAU;AACjC,WAAO,MAAM,mBAAmB,sBAAsB;AAAA,EACxD;AAEA,QAAM,cAAc,MAAM,QAAQ;AAClC,QAAM,iBAAiB,YAAY,IAAI,wBAAwB,GAAG;AAElE,MAAI,CAAC,gBAAgB;AACnB,WAAO;AAAA,EACT;AAEA,QAAM,cAAc,MAAM,eAAe;AACzC,QAAM,OAAO,MAAM,YAAY,QAAQ,cAAc;AAIrD,MAAI,CAAC,MAAM;AACT,QAAI;AACF,kBAAY,IAAI,0BAA0B,IAAI;AAAA,QAC5C,GAAG,qBAAqB;AAAA,QACxB,QAAQ;AAAA,MACV,CAAC;AAAA,IACH,SAAS,OAAO;AACd,cAAQ,KAAK,iCAAiC,KAAK;AAAA,IACrD;AAAA,EACF;AAEA,SAAO;AACT;AAWA,eAAsB,iBAAiB,SAId;AACvB,QAAM,EAAE,OAAO,kBAAkB,OAAO,cAAc,IAAI,WAAW,CAAC;AACtE,QAAM,mBAAmB,MAAM,oBAAoB;AACnD,QAAM,cAAc,MAAM,eAAe;AACzC,QAAM,OAAO,MAAM,YAAY,WAAW,OAAO,aAAa;AAE9D,MAAI,QAAQ,CAAC,iBAAiB;AAE5B,QAAI;AACF,UAAI,qBAAqB,UAAU;AACjC,cAAM,iBAAiB,MAAM,sBAAsB;AAAA,MACrD,OAAO;AACL,cAAM,cAAc,MAAM,QAAQ;AAClC,oBAAY;AAAA,UACV;AAAA,UACA,KAAK;AAAA,UACL,qBAAqB;AAAA,QACvB;AAAA,MACF;AAAA,IACF,SAAS,aAAa;AACpB,cAAQ,KAAK,8BAA8B,WAAW;AAAA,IACxD;AAAA,EACF;AAEA,SAAO;AACT;AASA,eAAsB,gBACpB,OACA,QACsB;AACtB,QAAM,cAAc,MAAM,eAAe;AACzC,SAAO,MAAM,YAAY,UAAU,EAAE,OAAO,OAAO,CAAC;AACtD;AASA,eAAsB,yBACpB,MACA,QACsB;AACtB,QAAM,cAAc,MAAM,eAAe;AACzC,SAAO,MAAM,YAAY,mBAAmB,EAAE,MAAM,OAAO,CAAC;AAC9D;AASA,eAAsB,0BACpB,SACA,QACsB;AACtB,QAAM,cAAc,MAAM,eAAe;AACzC,SAAO,MAAM,YAAY,oBAAoB,EAAE,SAAS,OAAO,CAAC;AAClE;AASA,eAAsB,8BACpB,eACA,QACsB;AACtB,QAAM,cAAc,MAAM,eAAe;AACzC,SAAO,MAAM,YAAY,wBAAwB,EAAE,eAAe,OAAO,CAAC;AAC5E;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/cart/cart-provider.js
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
updateCartDiscountCodesAction,
|
|
14
14
|
updateCartLineItemAction
|
|
15
15
|
} from "./cart-actions";
|
|
16
|
+
import { recalculateCartCost } from "./utils/cart-utils";
|
|
16
17
|
import { createOptimisticSellingPlanAllocation } from "./utils/variant-to-cart-line";
|
|
17
18
|
class CartContextError extends CanopyError {
|
|
18
19
|
}
|
|
@@ -34,7 +35,7 @@ function addLineToCart(cart, line) {
|
|
|
34
35
|
...updatedLines[existingLineIndex],
|
|
35
36
|
quantity: updatedLines[existingLineIndex].quantity + line.quantity
|
|
36
37
|
};
|
|
37
|
-
return { ...cart, lines: updatedLines };
|
|
38
|
+
return recalculateCartCost({ ...cart, lines: updatedLines });
|
|
38
39
|
} else {
|
|
39
40
|
const optimisticSellingPlanAllocation = line.sellingPlanId && line.merchandise ? createOptimisticSellingPlanAllocation({
|
|
40
41
|
merchandise: line.merchandise,
|
|
@@ -48,18 +49,18 @@ function addLineToCart(cart, line) {
|
|
|
48
49
|
attributes: line.properties ?? [],
|
|
49
50
|
sellingPlanAllocation: optimisticSellingPlanAllocation
|
|
50
51
|
};
|
|
51
|
-
return { ...cart, lines: [newLine, ...cart.lines] };
|
|
52
|
+
return recalculateCartCost({ ...cart, lines: [newLine, ...cart.lines] });
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
function updateLineInCart(cart, line) {
|
|
55
56
|
const updatedLines = cart.lines.map((existingLine) => {
|
|
56
57
|
return existingLine.id === line.id ? { ...existingLine, quantity: line.quantity } : existingLine;
|
|
57
58
|
});
|
|
58
|
-
return { ...cart, lines: updatedLines };
|
|
59
|
+
return recalculateCartCost({ ...cart, lines: updatedLines });
|
|
59
60
|
}
|
|
60
61
|
function removeLineFromCart(cart, lineId) {
|
|
61
62
|
const updatedLines = cart.lines.filter((line) => line.id !== lineId);
|
|
62
|
-
return { ...cart, lines: updatedLines };
|
|
63
|
+
return recalculateCartCost({ ...cart, lines: updatedLines });
|
|
63
64
|
}
|
|
64
65
|
const CartContext = React.createContext(
|
|
65
66
|
void 0
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../cart/cart-provider.tsx"],
|
|
4
|
-
"sourcesContent": ["/**\n * Use useCart() hook to access cart UI state (itemsCount, isCartOpen, openCart, closeCart). For adding products to cart, use useAddToCart. For buy now functionality, use useBuyNow.\n * @module\n */\n\"use client\";\n\nimport type { Cart, CartLine, CartLinePayload } from \"./cart-types\";\n\nimport React from \"react\";\n\nimport { fromMinorUnitsToMajorUnits } from \"schemas/money\";\nimport { v4 as uuidv4 } from \"uuid\";\n\nimport { useAnalyticsOptional } from \"../analytics/analytics-provider\";\nimport { CanopyError } from \"../lib/canopy-error\";\nimport {\n addToCartAction,\n createCartAction,\n getOrCreateCartAction,\n removeCartLineItemsAction,\n updateCartDiscountCodesAction,\n updateCartLineItemAction,\n} from \"./cart-actions\";\nimport { createOptimisticSellingPlanAllocation } from \"./utils/variant-to-cart-line\";\n\nclass CartContextError extends CanopyError {}\n\n/**\n * Detects if the app is running in editor mode (iframe).\n *\n * @returns True if running in an iframe (editor mode)\n */\nconst isEditorMode = () => {\n if (typeof window === \"undefined\") {\n return false;\n }\n\n return window.parent !== window;\n};\n\n// Global promise to prevent multiple cart creation attempts\nlet cartCreationPromise: Promise<Cart | null> | null = null;\n\n/**\n * Client-side cart operation to add a line item. Used in editor mode.\n * Merges with existing lines if they match by merchandise ID and selling plan.\n *\n * @param cart - The cart to add the line to\n * @param line - The cart line payload to add\n * @returns The updated cart\n */\nfunction addLineToCart(cart: Cart, line: CartLinePayload): Cart {\n const existingLineIndex = cart.lines.findIndex((existingLine) => {\n return (\n existingLine.merchandise?.id === line.id &&\n // Distinguish by selling plan to avoid merging subscription with one-time\n (line.sellingPlanId ?? null) ===\n (existingLine.sellingPlanAllocation?.sellingPlan.id ?? null)\n );\n });\n\n if (existingLineIndex >= 0) {\n // Update existing line\n const updatedLines = [...cart.lines];\n updatedLines[existingLineIndex] = {\n ...updatedLines[existingLineIndex]!,\n quantity: updatedLines[existingLineIndex]!.quantity + line.quantity,\n };\n return { ...cart, lines: updatedLines };\n } else {\n const optimisticSellingPlanAllocation =\n line.sellingPlanId && line.merchandise\n ? createOptimisticSellingPlanAllocation({\n merchandise: line.merchandise,\n sellingPlanId: line.sellingPlanId,\n currencyCode: cart.cost?.currencyCode ?? \"USD\",\n })\n : null;\n\n const newLine: CartLine = {\n id: `temp-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,\n quantity: line.quantity,\n merchandise: line.merchandise ?? null,\n attributes: line.properties ?? [],\n sellingPlanAllocation: optimisticSellingPlanAllocation,\n };\n return { ...cart, lines: [newLine, ...cart.lines] };\n }\n}\n\n/**\n * Client-side cart operation to update a line item. Used in editor mode.\n *\n * @param cart - The cart containing the line to update\n * @param line - The cart line payload with updated values\n * @returns The updated cart\n */\nfunction updateLineInCart(cart: Cart, line: CartLinePayload): Cart {\n const updatedLines = cart.lines.map((existingLine) => {\n return existingLine.id === line.id\n ? { ...existingLine, quantity: line.quantity }\n : existingLine;\n });\n return { ...cart, lines: updatedLines };\n}\n\n/**\n * Client-side cart operation to remove a line item. Used in editor mode.\n *\n * @param cart - The cart containing the line to remove\n * @param lineId - The ID of the line to remove\n * @returns The updated cart\n */\nfunction removeLineFromCart(cart: Cart, lineId: string): Cart {\n const updatedLines = cart.lines.filter((line) => line.id !== lineId);\n return { ...cart, lines: updatedLines };\n}\n\n/**\n * Internal cart context type with full functionality.\n * This is for internal use only - external code should use the limited CartContextType.\n */\nexport interface CartContextTypeInternal {\n /** The current cart (can be null) */\n cartData: Cart | null;\n /** Array of cart line items */\n lineItems: CartLine[];\n /** Total price of all items */\n subtotal: number;\n /** Total number of items in cart */\n itemsCount: number;\n /** Currency code for the cart */\n currencyCode: string;\n /** Function to add items to cart */\n addToCart: (\n lines: CartLinePayload[],\n openCartAfterAdd?: boolean,\n ) => Promise<void>;\n /** Function to update cart item quantity */\n updateCartItem: (line: CartLinePayload) => Promise<void>;\n /** Function to remove item from cart */\n removeCartItem: (lineId: string) => Promise<void>;\n /** Function to update discount codes on cart */\n updateDiscountCodes: (discountCodes: string[]) => Promise<void>;\n /** Function to create cart and checkout immediately */\n buyNow: (\n lines: CartLinePayload[],\n discountCodes?: string[],\n ) => Promise<Cart | null>;\n /** URL to redirect to checkout */\n checkoutUrl: string;\n /** Whether the cart UI is open */\n isCartOpen: boolean;\n /** Function to open the cart UI */\n openCart: () => void;\n /** Function to close the cart UI */\n closeCart: () => void;\n}\n\n// NOTE (Gabe, 2026-01-08): We intentionally duplicate properties from CartContextTypeInternal\n// rather than using Pick<> so that TypeDoc generates expanded documentation for each property.\n/**\n * Public cart context type for LLM use.\n * For adding products to cart, use useAddToCart.\n * For buy now functionality, use useBuyNow.\n */\nexport interface CartContextType {\n /** Total number of items in cart */\n itemsCount: number;\n /** Whether the cart UI is open */\n isCartOpen: boolean;\n /** Function to open the cart UI */\n openCart: () => void;\n /** Function to close the cart UI */\n closeCart: () => void;\n /** Function to update discount codes on cart */\n updateDiscountCodes: (discountCodes: string[]) => Promise<void>;\n}\n\nconst CartContext = React.createContext<CartContextTypeInternal | undefined>(\n undefined,\n);\n\n/**\n * Provider component that manages global cart state and operations. Handles both\n * server-side cart synchronization and client-side optimistic updates. Supports\n * editor mode for preview environments.\n *\n * @returns A context provider wrapping the children with cart functionality\n */\nexport function CartProvider({\n children,\n cart,\n}: React.PropsWithChildren<{\n /** Initial cart data from the server (can be null) */\n cart: Cart | null;\n}>) {\n const [cartData, setCartData] = React.useState<Cart | null>(cart);\n const [isCartOpen, setIsCartOpen] = React.useState(false);\n const hasInitialized = React.useRef(false);\n const analytics = useAnalyticsOptional();\n\n // Initialize cart on mount\n // eslint-disable-next-line replo/no-use-effect -- Legacy effect - may or may not be necessary\n React.useEffect(() => {\n // Early exit if already initialized\n if (hasInitialized.current) {\n return;\n }\n\n // If there's already a creation in progress, attach to it\n if (cartCreationPromise) {\n void cartCreationPromise.then((cart) => {\n if (cart) {\n setCartData(cart);\n }\n });\n return;\n }\n\n // Mark as initialized to prevent duplicate calls\n hasInitialized.current = true;\n\n // Get existing cart or create new one\n cartCreationPromise = getOrCreateCartAction()\n .then((cart) => {\n if (cart) {\n setCartData(cart);\n } else {\n console.warn(\n \"[Replo] Failed to get or create cart on the server. Reach out to support@replo.app if this persists.\",\n );\n }\n return cart;\n })\n .catch((error) => {\n console.error(\"[Replo] Error getting or creating cart:\", error);\n return null;\n })\n .finally(() => {\n // Reset the promise after completion\n cartCreationPromise = null;\n });\n }, []);\n\n // Derived values - handle null cart gracefully\n const lineItems = cartData?.lines ?? [];\n\n const subtotal =\n cartData?.lines.reduce((sum, lineItem) => {\n if (lineItem.quantity > 0 && lineItem.merchandise) {\n sum += lineItem.merchandise.price * lineItem.quantity;\n }\n return sum;\n }, 0) ?? 0;\n\n const itemsCount =\n cartData?.lines.reduce((sum, lineItem) => {\n if (lineItem.quantity > 0) {\n sum += lineItem.quantity;\n }\n return sum;\n }, 0) ?? 0;\n\n // Cart UI state functions\n const openCart = React.useCallback(() => {\n setIsCartOpen(true);\n\n if (analytics) {\n const analyticsCurrencyCode = cartData?.cost.currencyCode ?? \"USD\";\n // NOTE (Max, 2026-08-03): cart amounts are integer minor units, but the\n // analytics payload speaks decimal major units (ad pixels + the Replo\n // store), so convert at this boundary.\n void analytics.viewCart({\n data: {\n itemCount: itemsCount,\n subtotal: fromMinorUnitsToMajorUnits({\n amount: subtotal,\n currencyCode: analyticsCurrencyCode,\n }),\n currency: cartData?.cost.currencyCode,\n lineItems: cartData?.lines.flatMap((item) => {\n if (!item.merchandise) {\n return [];\n }\n return [\n {\n productId: item.merchandise.product.id,\n variantId: item.merchandise.id,\n quantity: item.quantity,\n price: fromMinorUnitsToMajorUnits({\n amount: item.merchandise.price,\n currencyCode: analyticsCurrencyCode,\n }),\n },\n ];\n }),\n },\n });\n }\n }, [analytics, cartData, itemsCount, subtotal]);\n\n const closeCart = React.useCallback(() => {\n setIsCartOpen(false);\n }, []);\n\n // Track the latest update request to avoid updating with stale responses\n const latestUpdateRequestId = React.useRef<string | null>(null);\n\n const addToCart = async (\n lines: CartLinePayload[],\n openCartAfterAdd: boolean = true,\n ) => {\n // If no cart exists, we can't add to it\n if (!cartData) {\n console.warn(\n \"[Replo] Cannot add to cart: no cart exists. Reach out to support@replo.app if this persists.\",\n );\n return;\n }\n\n // Optimistic update - use functional form to avoid stale state\n setCartData((currentCart) => {\n if (!currentCart) {\n return null;\n }\n let updatedCart = { ...currentCart };\n for (const line of lines) {\n updatedCart = addLineToCart(updatedCart, line);\n }\n return updatedCart;\n });\n\n if (openCartAfterAdd) {\n openCart();\n }\n\n if (analytics) {\n for (const line of lines) {\n if (!line.merchandise) {\n continue;\n }\n // NOTE (Max, 2026-08-03): cart amounts are integer minor units, but the\n // analytics payload speaks decimal major units (ad pixels + the Replo\n // store), so convert at this boundary.\n void analytics.addToCart({\n data: {\n productId: line.merchandise.product.id,\n variantId: line.merchandise.id,\n quantity: line.quantity,\n price: fromMinorUnitsToMajorUnits({\n amount: line.merchandise.price,\n currencyCode: cartData?.cost.currencyCode ?? \"USD\",\n }),\n currency: cartData?.cost.currencyCode,\n productTitle: line.merchandise.product.title,\n variantTitle: line.merchandise.title,\n },\n });\n }\n }\n\n // Direct server update (no debouncing)\n const serverCart = await addToCartAction(lines, cartData.id);\n if (serverCart) {\n setCartData(serverCart);\n } else {\n console.warn(\n \"[Replo] Failed to add item to cart on server. Cart may be out of sync. Reach out to support@replo.app if this persists.\",\n );\n }\n };\n\n const updateCartItem = async (line: CartLinePayload) => {\n // If no cart exists, we can't update it\n if (!cartData) {\n console.warn(\n \"[Replo] Cannot update cart item: no cart exists. Reach out to support@replo.app if this persists.\",\n );\n return;\n }\n\n // Generate a unique request ID for this update\n const requestId = uuidv4();\n latestUpdateRequestId.current = requestId;\n\n // Optimistic update - use functional form to avoid stale state\n setCartData((currentCart) => {\n if (!currentCart) {\n return null;\n }\n return updateLineInCart(currentCart, line);\n });\n\n // Server update\n const serverCart = await updateCartLineItemAction(line, cartData.id);\n\n // Only update if this is still the latest request\n if (latestUpdateRequestId.current === requestId) {\n if (serverCart) {\n setCartData(serverCart);\n } else {\n console.warn(\n \"[Replo] Failed to update cart item on server. Cart may be out of sync. Reach out to support@replo.app if this persists.\",\n );\n }\n }\n };\n\n const removeCartItem = async (lineId: string) => {\n if (!cartData) {\n console.warn(\n \"[Replo] Cannot remove cart item: no cart exists. Reach out to support@replo.app if this persists.\",\n );\n return;\n }\n\n const itemToRemove = cartData.lines.find((item) => item.id === lineId);\n if (analytics && itemToRemove && itemToRemove.merchandise) {\n // NOTE (Max, 2026-08-03): cart amounts are integer minor units, but the\n // analytics payload speaks decimal major units (ad pixels + the Replo\n // store), so convert at this boundary.\n void analytics.removeFromCart({\n data: {\n productId: itemToRemove.merchandise.product.id,\n variantId: itemToRemove.merchandise.id,\n quantity: itemToRemove.quantity,\n price: fromMinorUnitsToMajorUnits({\n amount: itemToRemove.merchandise.price,\n currencyCode: cartData.cost.currencyCode,\n }),\n currency: cartData.cost.currencyCode,\n productTitle: itemToRemove.merchandise.product.title,\n variantTitle: itemToRemove.merchandise.title,\n },\n });\n }\n\n // Optimistic update - use functional form to avoid stale state\n setCartData((currentCart) => {\n if (!currentCart) {\n return null;\n }\n return removeLineFromCart(currentCart, lineId);\n });\n\n // Direct server update (no debouncing)\n const serverCart = await removeCartLineItemsAction([lineId], cartData.id);\n if (serverCart) {\n setCartData(serverCart);\n } else {\n console.warn(\n \"[Replo] Failed to remove cart item on server. Cart may be out of sync. Reach out to support@replo.app if this persists.\",\n );\n }\n };\n\n const updateDiscountCodes = async (discountCodes: string[]) => {\n // If no cart exists, we can't update discount codes\n if (!cartData) {\n console.warn(\n \"[Replo] Cannot update discount codes: no cart exists. Reach out to support@replo.app if this persists.\",\n );\n return;\n }\n\n // Optimistic update - use functional form to avoid stale state\n setCartData((currentCart) => {\n if (!currentCart) {\n return null;\n }\n return {\n ...currentCart,\n discountCodes: discountCodes.map((code) => ({\n code,\n applicable: true,\n })),\n };\n });\n\n // Server update\n const serverCart = await updateCartDiscountCodesAction(\n discountCodes,\n cartData.id,\n );\n if (serverCart) {\n setCartData(serverCart);\n } else {\n console.warn(\n \"[Replo] Failed to update discount codes on server. Cart may be out of sync. Reach out to support@replo.app if this persists.\",\n );\n }\n };\n\n const buyNow = async (lines: CartLinePayload[], discountCodes?: string[]) => {\n // Create a new cart with the specified lines and discount codes\n const cart = await createCartAction({\n lines,\n skipStoringCart: true,\n discountCodes,\n });\n if (cart) {\n return cart;\n } else {\n console.warn(\n \"[Replo] Failed to create cart for buy now. Reach out to support@replo.app if this persists.\",\n );\n return null;\n }\n };\n\n const checkoutUrl: string = (() => {\n if (isEditorMode()) {\n return \"/\";\n }\n\n return cartData?.checkoutUrl ?? \"/\";\n })();\n\n return (\n <CartContext.Provider\n value={{\n cartData,\n lineItems,\n subtotal,\n itemsCount,\n currencyCode: cartData?.cost.currencyCode ?? \"USD\",\n addToCart,\n updateCartItem,\n removeCartItem,\n updateDiscountCodes,\n buyNow,\n checkoutUrl,\n isCartOpen,\n openCart,\n closeCart,\n }}\n >\n {children}\n </CartContext.Provider>\n );\n}\n\n/**\n * @deprecated For internal use only. Use useAddToCart or useBuyNow\n * for cart operations. Use useCart for cart UI state (itemsCount, openCart, closeCart).\n */\nexport function useCartInternal(): CartContextTypeInternal {\n const context = React.useContext(CartContext);\n if (context === undefined) {\n throw new CartContextError({\n message: \"useCartInternal must be used within a CartProvider\",\n });\n }\n return context;\n}\n\n/**\n * Hook to access cart UI state and controls. For adding products to cart,\n * use useAddToCart. For buy now functionality, use useBuyNow.\n *\n * @example\n * ```tsx\n * import { useCart } from \"@replohq/sdk/cart/cart-provider\";\n *\n * function MyComponent() {\n * const { itemsCount, openCart } = useCart();\n * ...\n * }\n * ```\n * @throws Error if used outside of a CartProvider\n */\nexport function useCart(): CartContextType {\n const context = React.useContext(CartContext);\n if (context === undefined) {\n throw new CartContextError({\n message: \"useCart must be used within a CartProvider\",\n });\n }\n return {\n itemsCount: context.itemsCount,\n isCartOpen: context.isCartOpen,\n openCart: context.openCart,\n closeCart: context.closeCart,\n updateDiscountCodes: context.updateDiscountCodes,\n };\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["/**\n * Use useCart() hook to access cart UI state (itemsCount, isCartOpen, openCart, closeCart). For adding products to cart, use useAddToCart. For buy now functionality, use useBuyNow.\n * @module\n */\n\"use client\";\n\nimport type { Cart, CartLine, CartLinePayload } from \"./cart-types\";\n\nimport React from \"react\";\n\nimport { fromMinorUnitsToMajorUnits } from \"schemas/money\";\nimport { v4 as uuidv4 } from \"uuid\";\n\nimport { useAnalyticsOptional } from \"../analytics/analytics-provider\";\nimport { CanopyError } from \"../lib/canopy-error\";\nimport {\n addToCartAction,\n createCartAction,\n getOrCreateCartAction,\n removeCartLineItemsAction,\n updateCartDiscountCodesAction,\n updateCartLineItemAction,\n} from \"./cart-actions\";\nimport { recalculateCartCost } from \"./utils/cart-utils\";\nimport { createOptimisticSellingPlanAllocation } from \"./utils/variant-to-cart-line\";\n\nclass CartContextError extends CanopyError {}\n\n/**\n * Detects if the app is running in editor mode (iframe).\n *\n * @returns True if running in an iframe (editor mode)\n */\nconst isEditorMode = () => {\n if (typeof window === \"undefined\") {\n return false;\n }\n\n return window.parent !== window;\n};\n\n// Global promise to prevent multiple cart creation attempts\nlet cartCreationPromise: Promise<Cart | null> | null = null;\n\n/**\n * Client-side cart operation to add a line item. Used in editor mode.\n * Merges with existing lines if they match by merchandise ID and selling plan.\n *\n * @param cart - The cart to add the line to\n * @param line - The cart line payload to add\n * @returns The updated cart\n */\nfunction addLineToCart(cart: Cart, line: CartLinePayload): Cart {\n const existingLineIndex = cart.lines.findIndex((existingLine) => {\n return (\n existingLine.merchandise?.id === line.id &&\n // Distinguish by selling plan to avoid merging subscription with one-time\n (line.sellingPlanId ?? null) ===\n (existingLine.sellingPlanAllocation?.sellingPlan.id ?? null)\n );\n });\n\n if (existingLineIndex >= 0) {\n // Update existing line\n const updatedLines = [...cart.lines];\n updatedLines[existingLineIndex] = {\n ...updatedLines[existingLineIndex]!,\n quantity: updatedLines[existingLineIndex]!.quantity + line.quantity,\n };\n return recalculateCartCost({ ...cart, lines: updatedLines });\n } else {\n const optimisticSellingPlanAllocation =\n line.sellingPlanId && line.merchandise\n ? createOptimisticSellingPlanAllocation({\n merchandise: line.merchandise,\n sellingPlanId: line.sellingPlanId,\n currencyCode: cart.cost?.currencyCode ?? \"USD\",\n })\n : null;\n\n const newLine: CartLine = {\n id: `temp-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,\n quantity: line.quantity,\n merchandise: line.merchandise ?? null,\n attributes: line.properties ?? [],\n sellingPlanAllocation: optimisticSellingPlanAllocation,\n };\n return recalculateCartCost({ ...cart, lines: [newLine, ...cart.lines] });\n }\n}\n\n/**\n * Client-side cart operation to update a line item. Used in editor mode.\n *\n * @param cart - The cart containing the line to update\n * @param line - The cart line payload with updated values\n * @returns The updated cart\n */\nfunction updateLineInCart(cart: Cart, line: CartLinePayload): Cart {\n const updatedLines = cart.lines.map((existingLine) => {\n return existingLine.id === line.id\n ? { ...existingLine, quantity: line.quantity }\n : existingLine;\n });\n return recalculateCartCost({ ...cart, lines: updatedLines });\n}\n\n/**\n * Client-side cart operation to remove a line item. Used in editor mode.\n *\n * @param cart - The cart containing the line to remove\n * @param lineId - The ID of the line to remove\n * @returns The updated cart\n */\nfunction removeLineFromCart(cart: Cart, lineId: string): Cart {\n const updatedLines = cart.lines.filter((line) => line.id !== lineId);\n return recalculateCartCost({ ...cart, lines: updatedLines });\n}\n\n/**\n * Internal cart context type with full functionality.\n * This is for internal use only - external code should use the limited CartContextType.\n */\nexport interface CartContextTypeInternal {\n /** The current cart (can be null) */\n cartData: Cart | null;\n /** Array of cart line items */\n lineItems: CartLine[];\n /** Total price of all items */\n subtotal: number;\n /** Total number of items in cart */\n itemsCount: number;\n /** Currency code for the cart */\n currencyCode: string;\n /** Function to add items to cart */\n addToCart: (\n lines: CartLinePayload[],\n openCartAfterAdd?: boolean,\n ) => Promise<void>;\n /** Function to update cart item quantity */\n updateCartItem: (line: CartLinePayload) => Promise<void>;\n /** Function to remove item from cart */\n removeCartItem: (lineId: string) => Promise<void>;\n /** Function to update discount codes on cart */\n updateDiscountCodes: (discountCodes: string[]) => Promise<void>;\n /** Function to create cart and checkout immediately */\n buyNow: (\n lines: CartLinePayload[],\n discountCodes?: string[],\n ) => Promise<Cart | null>;\n /** URL to redirect to checkout */\n checkoutUrl: string;\n /** Whether the cart UI is open */\n isCartOpen: boolean;\n /** Function to open the cart UI */\n openCart: () => void;\n /** Function to close the cart UI */\n closeCart: () => void;\n}\n\n// NOTE (Gabe, 2026-01-08): We intentionally duplicate properties from CartContextTypeInternal\n// rather than using Pick<> so that TypeDoc generates expanded documentation for each property.\n/**\n * Public cart context type for LLM use.\n * For adding products to cart, use useAddToCart.\n * For buy now functionality, use useBuyNow.\n */\nexport interface CartContextType {\n /** Total number of items in cart */\n itemsCount: number;\n /** Whether the cart UI is open */\n isCartOpen: boolean;\n /** Function to open the cart UI */\n openCart: () => void;\n /** Function to close the cart UI */\n closeCart: () => void;\n /** Function to update discount codes on cart */\n updateDiscountCodes: (discountCodes: string[]) => Promise<void>;\n}\n\nconst CartContext = React.createContext<CartContextTypeInternal | undefined>(\n undefined,\n);\n\n/**\n * Provider component that manages global cart state and operations. Handles both\n * server-side cart synchronization and client-side optimistic updates. Supports\n * editor mode for preview environments.\n *\n * @returns A context provider wrapping the children with cart functionality\n */\nexport function CartProvider({\n children,\n cart,\n}: React.PropsWithChildren<{\n /** Initial cart data from the server (can be null) */\n cart: Cart | null;\n}>) {\n const [cartData, setCartData] = React.useState<Cart | null>(cart);\n const [isCartOpen, setIsCartOpen] = React.useState(false);\n const hasInitialized = React.useRef(false);\n const analytics = useAnalyticsOptional();\n\n // Initialize cart on mount\n // eslint-disable-next-line replo/no-use-effect -- Legacy effect - may or may not be necessary\n React.useEffect(() => {\n // Early exit if already initialized\n if (hasInitialized.current) {\n return;\n }\n\n // If there's already a creation in progress, attach to it\n if (cartCreationPromise) {\n void cartCreationPromise.then((cart) => {\n if (cart) {\n setCartData(cart);\n }\n });\n return;\n }\n\n // Mark as initialized to prevent duplicate calls\n hasInitialized.current = true;\n\n // Get existing cart or create new one\n cartCreationPromise = getOrCreateCartAction()\n .then((cart) => {\n if (cart) {\n setCartData(cart);\n } else {\n console.warn(\n \"[Replo] Failed to get or create cart on the server. Reach out to support@replo.app if this persists.\",\n );\n }\n return cart;\n })\n .catch((error) => {\n console.error(\"[Replo] Error getting or creating cart:\", error);\n return null;\n })\n .finally(() => {\n // Reset the promise after completion\n cartCreationPromise = null;\n });\n }, []);\n\n // Derived values - handle null cart gracefully\n const lineItems = cartData?.lines ?? [];\n\n const subtotal =\n cartData?.lines.reduce((sum, lineItem) => {\n if (lineItem.quantity > 0 && lineItem.merchandise) {\n sum += lineItem.merchandise.price * lineItem.quantity;\n }\n return sum;\n }, 0) ?? 0;\n\n const itemsCount =\n cartData?.lines.reduce((sum, lineItem) => {\n if (lineItem.quantity > 0) {\n sum += lineItem.quantity;\n }\n return sum;\n }, 0) ?? 0;\n\n // Cart UI state functions\n const openCart = React.useCallback(() => {\n setIsCartOpen(true);\n\n if (analytics) {\n const analyticsCurrencyCode = cartData?.cost.currencyCode ?? \"USD\";\n // NOTE (Max, 2026-08-03): cart amounts are integer minor units, but the\n // analytics payload speaks decimal major units (ad pixels + the Replo\n // store), so convert at this boundary.\n void analytics.viewCart({\n data: {\n itemCount: itemsCount,\n subtotal: fromMinorUnitsToMajorUnits({\n amount: subtotal,\n currencyCode: analyticsCurrencyCode,\n }),\n currency: cartData?.cost.currencyCode,\n lineItems: cartData?.lines.flatMap((item) => {\n if (!item.merchandise) {\n return [];\n }\n return [\n {\n productId: item.merchandise.product.id,\n variantId: item.merchandise.id,\n quantity: item.quantity,\n price: fromMinorUnitsToMajorUnits({\n amount: item.merchandise.price,\n currencyCode: analyticsCurrencyCode,\n }),\n },\n ];\n }),\n },\n });\n }\n }, [analytics, cartData, itemsCount, subtotal]);\n\n const closeCart = React.useCallback(() => {\n setIsCartOpen(false);\n }, []);\n\n // Track the latest update request to avoid updating with stale responses\n const latestUpdateRequestId = React.useRef<string | null>(null);\n\n const addToCart = async (\n lines: CartLinePayload[],\n openCartAfterAdd: boolean = true,\n ) => {\n // If no cart exists, we can't add to it\n if (!cartData) {\n console.warn(\n \"[Replo] Cannot add to cart: no cart exists. Reach out to support@replo.app if this persists.\",\n );\n return;\n }\n\n // Optimistic update - use functional form to avoid stale state\n setCartData((currentCart) => {\n if (!currentCart) {\n return null;\n }\n let updatedCart = { ...currentCart };\n for (const line of lines) {\n updatedCart = addLineToCart(updatedCart, line);\n }\n return updatedCart;\n });\n\n if (openCartAfterAdd) {\n openCart();\n }\n\n if (analytics) {\n for (const line of lines) {\n if (!line.merchandise) {\n continue;\n }\n // NOTE (Max, 2026-08-03): cart amounts are integer minor units, but the\n // analytics payload speaks decimal major units (ad pixels + the Replo\n // store), so convert at this boundary.\n void analytics.addToCart({\n data: {\n productId: line.merchandise.product.id,\n variantId: line.merchandise.id,\n quantity: line.quantity,\n price: fromMinorUnitsToMajorUnits({\n amount: line.merchandise.price,\n currencyCode: cartData?.cost.currencyCode ?? \"USD\",\n }),\n currency: cartData?.cost.currencyCode,\n productTitle: line.merchandise.product.title,\n variantTitle: line.merchandise.title,\n },\n });\n }\n }\n\n // Direct server update (no debouncing)\n const serverCart = await addToCartAction(lines, cartData.id);\n if (serverCart) {\n setCartData(serverCart);\n } else {\n console.warn(\n \"[Replo] Failed to add item to cart on server. Cart may be out of sync. Reach out to support@replo.app if this persists.\",\n );\n }\n };\n\n const updateCartItem = async (line: CartLinePayload) => {\n // If no cart exists, we can't update it\n if (!cartData) {\n console.warn(\n \"[Replo] Cannot update cart item: no cart exists. Reach out to support@replo.app if this persists.\",\n );\n return;\n }\n\n // Generate a unique request ID for this update\n const requestId = uuidv4();\n latestUpdateRequestId.current = requestId;\n\n // Optimistic update - use functional form to avoid stale state\n setCartData((currentCart) => {\n if (!currentCart) {\n return null;\n }\n return updateLineInCart(currentCart, line);\n });\n\n // Server update\n const serverCart = await updateCartLineItemAction(line, cartData.id);\n\n // Only update if this is still the latest request\n if (latestUpdateRequestId.current === requestId) {\n if (serverCart) {\n setCartData(serverCart);\n } else {\n console.warn(\n \"[Replo] Failed to update cart item on server. Cart may be out of sync. Reach out to support@replo.app if this persists.\",\n );\n }\n }\n };\n\n const removeCartItem = async (lineId: string) => {\n if (!cartData) {\n console.warn(\n \"[Replo] Cannot remove cart item: no cart exists. Reach out to support@replo.app if this persists.\",\n );\n return;\n }\n\n const itemToRemove = cartData.lines.find((item) => item.id === lineId);\n if (analytics && itemToRemove && itemToRemove.merchandise) {\n // NOTE (Max, 2026-08-03): cart amounts are integer minor units, but the\n // analytics payload speaks decimal major units (ad pixels + the Replo\n // store), so convert at this boundary.\n void analytics.removeFromCart({\n data: {\n productId: itemToRemove.merchandise.product.id,\n variantId: itemToRemove.merchandise.id,\n quantity: itemToRemove.quantity,\n price: fromMinorUnitsToMajorUnits({\n amount: itemToRemove.merchandise.price,\n currencyCode: cartData.cost.currencyCode,\n }),\n currency: cartData.cost.currencyCode,\n productTitle: itemToRemove.merchandise.product.title,\n variantTitle: itemToRemove.merchandise.title,\n },\n });\n }\n\n // Optimistic update - use functional form to avoid stale state\n setCartData((currentCart) => {\n if (!currentCart) {\n return null;\n }\n return removeLineFromCart(currentCart, lineId);\n });\n\n // Direct server update (no debouncing)\n const serverCart = await removeCartLineItemsAction([lineId], cartData.id);\n if (serverCart) {\n setCartData(serverCart);\n } else {\n console.warn(\n \"[Replo] Failed to remove cart item on server. Cart may be out of sync. Reach out to support@replo.app if this persists.\",\n );\n }\n };\n\n const updateDiscountCodes = async (discountCodes: string[]) => {\n // If no cart exists, we can't update discount codes\n if (!cartData) {\n console.warn(\n \"[Replo] Cannot update discount codes: no cart exists. Reach out to support@replo.app if this persists.\",\n );\n return;\n }\n\n // Optimistic update - use functional form to avoid stale state\n setCartData((currentCart) => {\n if (!currentCart) {\n return null;\n }\n return {\n ...currentCart,\n discountCodes: discountCodes.map((code) => ({\n code,\n applicable: true,\n })),\n };\n });\n\n // Server update\n const serverCart = await updateCartDiscountCodesAction(\n discountCodes,\n cartData.id,\n );\n if (serverCart) {\n setCartData(serverCart);\n } else {\n console.warn(\n \"[Replo] Failed to update discount codes on server. Cart may be out of sync. Reach out to support@replo.app if this persists.\",\n );\n }\n };\n\n const buyNow = async (lines: CartLinePayload[], discountCodes?: string[]) => {\n // Create a new cart with the specified lines and discount codes\n const cart = await createCartAction({\n lines,\n skipStoringCart: true,\n discountCodes,\n });\n if (cart) {\n return cart;\n } else {\n console.warn(\n \"[Replo] Failed to create cart for buy now. Reach out to support@replo.app if this persists.\",\n );\n return null;\n }\n };\n\n const checkoutUrl: string = (() => {\n if (isEditorMode()) {\n return \"/\";\n }\n\n return cartData?.checkoutUrl ?? \"/\";\n })();\n\n return (\n <CartContext.Provider\n value={{\n cartData,\n lineItems,\n subtotal,\n itemsCount,\n currencyCode: cartData?.cost.currencyCode ?? \"USD\",\n addToCart,\n updateCartItem,\n removeCartItem,\n updateDiscountCodes,\n buyNow,\n checkoutUrl,\n isCartOpen,\n openCart,\n closeCart,\n }}\n >\n {children}\n </CartContext.Provider>\n );\n}\n\n/**\n * @deprecated For internal use only. Use useAddToCart or useBuyNow\n * for cart operations. Use useCart for cart UI state (itemsCount, openCart, closeCart).\n */\nexport function useCartInternal(): CartContextTypeInternal {\n const context = React.useContext(CartContext);\n if (context === undefined) {\n throw new CartContextError({\n message: \"useCartInternal must be used within a CartProvider\",\n });\n }\n return context;\n}\n\n/**\n * Hook to access cart UI state and controls. For adding products to cart,\n * use useAddToCart. For buy now functionality, use useBuyNow.\n *\n * @example\n * ```tsx\n * import { useCart } from \"@replohq/sdk/cart/cart-provider\";\n *\n * function MyComponent() {\n * const { itemsCount, openCart } = useCart();\n * ...\n * }\n * ```\n * @throws Error if used outside of a CartProvider\n */\nexport function useCart(): CartContextType {\n const context = React.useContext(CartContext);\n if (context === undefined) {\n throw new CartContextError({\n message: \"useCart must be used within a CartProvider\",\n });\n }\n return {\n itemsCount: context.itemsCount,\n isCartOpen: context.isCartOpen,\n openCart: context.openCart,\n closeCart: context.closeCart,\n updateDiscountCodes: context.updateDiscountCodes,\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["cart"]
|
|
7
7
|
}
|
|
@@ -1 +1,15 @@
|
|
|
1
1
|
export declare const REPLO_CART_COOKIE_NAME = "replo_cart_v2";
|
|
2
|
+
export declare function getCartCookieOptions(): {
|
|
3
|
+
readonly secure: true;
|
|
4
|
+
readonly sameSite: "none";
|
|
5
|
+
readonly partitioned: true;
|
|
6
|
+
readonly maxAge: number;
|
|
7
|
+
readonly httpOnly: true;
|
|
8
|
+
readonly path: "/";
|
|
9
|
+
} | {
|
|
10
|
+
readonly secure: boolean;
|
|
11
|
+
readonly sameSite: "lax";
|
|
12
|
+
readonly maxAge: number;
|
|
13
|
+
readonly httpOnly: true;
|
|
14
|
+
readonly path: "/";
|
|
15
|
+
};
|