@replohq/sdk 1.2.0 → 1.2.2

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.
Files changed (39) hide show
  1. package/analytics/hooks/use-product-viewed-analytics.d.ts +6 -1
  2. package/analytics/hooks/use-product-viewed-analytics.js +4 -1
  3. package/analytics/hooks/use-product-viewed-analytics.js.map +2 -2
  4. package/analytics/replo-pixel-script.d.ts +1 -1
  5. package/analytics/replo-pixel-script.js.map +1 -1
  6. package/analytics/sinks/converge-event-mapper.d.ts +41 -0
  7. package/analytics/sinks/converge-event-mapper.js +182 -0
  8. package/analytics/sinks/converge-event-mapper.js.map +7 -0
  9. package/analytics/sinks/converge-sink.d.ts +24 -9
  10. package/analytics/sinks/converge-sink.js +45 -23
  11. package/analytics/sinks/converge-sink.js.map +2 -2
  12. package/analytics/sinks/northbeam-sink.d.ts +2 -4
  13. package/analytics/sinks/northbeam-sink.js +1 -4
  14. package/analytics/sinks/northbeam-sink.js.map +2 -2
  15. package/analytics/utils/analytics-utils.d.ts +6 -1
  16. package/analytics/utils/analytics-utils.js +24 -11
  17. package/analytics/utils/analytics-utils.js.map +2 -2
  18. package/analytics/utils/navigation-tracker.d.ts +2 -0
  19. package/analytics/utils/navigation-tracker.js +7 -3
  20. package/analytics/utils/navigation-tracker.js.map +2 -2
  21. package/cart/cart-provider.js +4 -1
  22. package/cart/cart-provider.js.map +2 -2
  23. package/cart/cart-schema.d.ts +4 -0
  24. package/cart/cart-schema.js +6 -1
  25. package/cart/cart-schema.js.map +2 -2
  26. package/cart/cart-types.d.ts +12 -0
  27. package/cart/cart-types.js.map +1 -1
  28. package/cart/utils/cart-utils.d.ts +7 -6
  29. package/cart/utils/cart-utils.js +6 -3
  30. package/cart/utils/cart-utils.js.map +2 -2
  31. package/consent/inject-script-descriptors.d.ts +3 -3
  32. package/consent/inject-script-descriptors.js.map +1 -1
  33. package/consent/replo-scripts.d.ts +8 -2
  34. package/consent/replo-scripts.js +49 -33
  35. package/consent/replo-scripts.js.map +2 -2
  36. package/consent/script-snippets.js +2 -6
  37. package/consent/script-snippets.js.map +2 -2
  38. package/lib/buildMetadata.js +3 -3
  39. package/package.json +4 -4
@@ -1,5 +1,9 @@
1
1
  const QUEUE_TIMEOUT_MS = 3e4;
2
- function createEventQueue(checkPixelReady, sendEvent, pixelName) {
2
+ function createAnalyticsQueue({
3
+ isReady,
4
+ send,
5
+ queueName
6
+ }) {
3
7
  const queue = [];
4
8
  let flushTimer = null;
5
9
  let timeoutTimer = null;
@@ -15,14 +19,14 @@ function createEventQueue(checkPixelReady, sendEvent, pixelName) {
15
19
  }
16
20
  };
17
21
  const flush = () => {
18
- if (!checkPixelReady()) {
22
+ if (!isReady()) {
19
23
  return;
20
24
  }
21
25
  console.debug(
22
- `[analytics] flushing ${queue.length} queued events for ${pixelName}`
26
+ `[analytics] flushing ${queue.length} queued events for ${queueName}`
23
27
  );
24
28
  try {
25
- queue.forEach(({ event, params }) => sendEvent(event, params));
29
+ queue.forEach(send);
26
30
  } finally {
27
31
  queue.length = 0;
28
32
  cleanup();
@@ -36,26 +40,34 @@ function createEventQueue(checkPixelReady, sendEvent, pixelName) {
36
40
  timeoutTimer = window.setTimeout(() => {
37
41
  queue.length = 0;
38
42
  permanentlyDisabled = true;
39
- console.debug(`Analytics queue timed out for ${pixelName}`);
43
+ console.debug(`Analytics queue timed out for ${queueName}`);
40
44
  cleanup();
41
45
  }, QUEUE_TIMEOUT_MS);
42
46
  };
43
- return (event, params = {}) => {
47
+ return (payload) => {
44
48
  if (permanentlyDisabled) {
45
49
  console.debug(
46
- `[analytics] ${pixelName} queue is disabled, dropping event`,
47
- event
50
+ `[analytics] ${queueName} queue is disabled, dropping event`,
51
+ payload
48
52
  );
49
53
  return;
50
54
  }
51
- if (checkPixelReady()) {
52
- sendEvent(event, params);
55
+ if (isReady()) {
56
+ send(payload);
53
57
  } else {
54
- queue.push({ event, params });
58
+ queue.push(payload);
55
59
  scheduleFlush();
56
60
  }
57
61
  };
58
62
  }
63
+ function createEventQueue(checkPixelReady, sendEvent, pixelName) {
64
+ const enqueue = createAnalyticsQueue({
65
+ isReady: checkPixelReady,
66
+ send: ({ event, params }) => sendEvent(event, params),
67
+ queueName: pixelName
68
+ });
69
+ return (event, params = {}) => enqueue({ event, params });
70
+ }
59
71
  function createMethodQueue(checkInstanceReady, options) {
60
72
  const queue = [];
61
73
  let flushTimer = null;
@@ -123,6 +135,7 @@ function createMethodQueue(checkInstanceReady, options) {
123
135
  };
124
136
  }
125
137
  export {
138
+ createAnalyticsQueue,
126
139
  createEventQueue,
127
140
  createMethodQueue
128
141
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../analytics/utils/analytics-utils.ts"],
4
- "sourcesContent": ["// oxlint-disable-next-line typescript/no-explicit-any -- legacy any, may or may not be needed\ntype QueuedEvent = { event: string; params: Record<string, any> };\n\nconst QUEUE_TIMEOUT_MS = 30_000;\n\nexport function createEventQueue(\n checkPixelReady: () => boolean,\n // oxlint-disable-next-line typescript/no-explicit-any -- legacy any, may or may not be needed\n sendEvent: (event: string, params: Record<string, any>) => void,\n pixelName: string,\n // oxlint-disable-next-line typescript/no-explicit-any -- legacy any, may or may not be needed\n): (event: string, params?: Record<string, any>) => void {\n const queue: QueuedEvent[] = [];\n let flushTimer: number | null = null;\n let timeoutTimer: number | null = null;\n let permanentlyDisabled = false;\n\n const cleanup = () => {\n if (flushTimer !== null) {\n window.clearInterval(flushTimer);\n flushTimer = null;\n }\n if (timeoutTimer !== null) {\n window.clearTimeout(timeoutTimer);\n timeoutTimer = null;\n }\n };\n\n const flush = () => {\n if (!checkPixelReady()) {\n return;\n }\n console.debug(\n `[analytics] flushing ${queue.length} queued events for ${pixelName}`,\n );\n try {\n queue.forEach(({ event, params }) => sendEvent(event, params));\n } finally {\n queue.length = 0;\n cleanup();\n }\n };\n\n const scheduleFlush = () => {\n if (typeof window === \"undefined\" || flushTimer !== null) {\n return;\n }\n\n flushTimer = window.setInterval(flush, 500);\n timeoutTimer = window.setTimeout(() => {\n queue.length = 0;\n permanentlyDisabled = true;\n console.debug(`Analytics queue timed out for ${pixelName}`);\n cleanup();\n }, QUEUE_TIMEOUT_MS);\n };\n\n // oxlint-disable-next-line typescript/no-explicit-any -- legacy any, may or may not be needed\n return (event: string, params: Record<string, any> = {}) => {\n if (permanentlyDisabled) {\n console.debug(\n `[analytics] ${pixelName} queue is disabled, dropping event`,\n event,\n );\n return;\n }\n\n if (checkPixelReady()) {\n sendEvent(event, params);\n } else {\n queue.push({ event, params });\n scheduleFlush();\n }\n };\n}\n\ntype MethodQueueOptions = {\n queueName?: string;\n onTimeout?: () => void;\n};\n\nexport function createMethodQueue<\n // oxlint-disable-next-line typescript/no-explicit-any -- legacy any, may or may not be needed\n InstanceType extends Record<string, (...args: any[]) => any>,\n>(\n checkInstanceReady: () => InstanceType | null,\n options?: MethodQueueOptions,\n // oxlint-disable-next-line typescript/no-explicit-any -- legacy any, may or may not be needed\n): (method: keyof InstanceType, ...args: any[]) => void {\n // oxlint-disable-next-line typescript/no-explicit-any -- legacy any, may or may not be needed\n const queue: { method: keyof InstanceType; args: any[] }[] = [];\n let flushTimer: number | null = null;\n let timeoutTimer: number | null = null;\n let permanentlyDisabled = false;\n const queueName = options?.queueName ?? \"method queue\";\n\n const cleanup = () => {\n if (flushTimer !== null) {\n window.clearInterval(flushTimer);\n flushTimer = null;\n }\n if (timeoutTimer !== null) {\n window.clearTimeout(timeoutTimer);\n timeoutTimer = null;\n }\n };\n\n const flush = () => {\n const instance = checkInstanceReady();\n if (!instance) {\n return;\n }\n try {\n queue.forEach(({ method, args }) => {\n const methodFunction = instance[method];\n if (typeof methodFunction === \"function\") {\n void methodFunction.apply(instance, args);\n }\n });\n } finally {\n queue.length = 0;\n cleanup();\n }\n };\n\n const scheduleFlush = () => {\n if (typeof window === \"undefined\" || flushTimer !== null) {\n return;\n }\n\n flushTimer = window.setInterval(flush, 500);\n timeoutTimer = window.setTimeout(() => {\n queue.length = 0;\n permanentlyDisabled = true;\n if (typeof window !== \"undefined\") {\n console.debug(\n `[analytics] ${queueName} timed out and has been disabled`,\n );\n }\n options?.onTimeout?.();\n cleanup();\n }, QUEUE_TIMEOUT_MS);\n };\n\n // oxlint-disable-next-line typescript/no-explicit-any -- legacy any, may or may not be needed\n return (method: keyof InstanceType, ...args: any[]) => {\n if (permanentlyDisabled) {\n return;\n }\n\n const instance = checkInstanceReady();\n if (instance) {\n const methodFunction = instance[method];\n if (typeof methodFunction === \"function\") {\n void methodFunction.apply(instance, args);\n }\n } else {\n queue.push({ method, args });\n scheduleFlush();\n }\n };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["type QueuedEvent = { event: string; params: Record<string, unknown> };\n\nconst QUEUE_TIMEOUT_MS = 30_000;\n\nexport function createAnalyticsQueue<Payload>({\n isReady,\n send,\n queueName,\n}: {\n isReady: () => boolean;\n send: (payload: Payload) => void;\n queueName: string;\n}): (payload: Payload) => void {\n const queue: Payload[] = [];\n let flushTimer: number | null = null;\n let timeoutTimer: number | null = null;\n let permanentlyDisabled = false;\n\n const cleanup = () => {\n if (flushTimer !== null) {\n window.clearInterval(flushTimer);\n flushTimer = null;\n }\n if (timeoutTimer !== null) {\n window.clearTimeout(timeoutTimer);\n timeoutTimer = null;\n }\n };\n\n const flush = () => {\n if (!isReady()) {\n return;\n }\n console.debug(\n `[analytics] flushing ${queue.length} queued events for ${queueName}`,\n );\n try {\n queue.forEach(send);\n } finally {\n queue.length = 0;\n cleanup();\n }\n };\n\n const scheduleFlush = () => {\n if (typeof window === \"undefined\" || flushTimer !== null) {\n return;\n }\n\n flushTimer = window.setInterval(flush, 500);\n timeoutTimer = window.setTimeout(() => {\n queue.length = 0;\n permanentlyDisabled = true;\n console.debug(`Analytics queue timed out for ${queueName}`);\n cleanup();\n }, QUEUE_TIMEOUT_MS);\n };\n\n return (payload: Payload) => {\n if (permanentlyDisabled) {\n console.debug(\n `[analytics] ${queueName} queue is disabled, dropping event`,\n payload,\n );\n return;\n }\n\n if (isReady()) {\n send(payload);\n } else {\n queue.push(payload);\n scheduleFlush();\n }\n };\n}\n\nexport function createEventQueue(\n checkPixelReady: () => boolean,\n sendEvent: (event: string, params: Record<string, unknown>) => void,\n pixelName: string,\n): (event: string, params?: Record<string, unknown>) => void {\n const enqueue = createAnalyticsQueue<QueuedEvent>({\n isReady: checkPixelReady,\n send: ({ event, params }) => sendEvent(event, params),\n queueName: pixelName,\n });\n\n return (event, params = {}) => enqueue({ event, params });\n}\n\ntype MethodQueueOptions = {\n queueName?: string;\n onTimeout?: () => void;\n};\n\nexport function createMethodQueue<\n // oxlint-disable-next-line typescript/no-explicit-any -- legacy any, may or may not be needed\n InstanceType extends Record<string, (...args: any[]) => any>,\n>(\n checkInstanceReady: () => InstanceType | null,\n options?: MethodQueueOptions,\n // oxlint-disable-next-line typescript/no-explicit-any -- legacy any, may or may not be needed\n): (method: keyof InstanceType, ...args: any[]) => void {\n // oxlint-disable-next-line typescript/no-explicit-any -- legacy any, may or may not be needed\n const queue: { method: keyof InstanceType; args: any[] }[] = [];\n let flushTimer: number | null = null;\n let timeoutTimer: number | null = null;\n let permanentlyDisabled = false;\n const queueName = options?.queueName ?? \"method queue\";\n\n const cleanup = () => {\n if (flushTimer !== null) {\n window.clearInterval(flushTimer);\n flushTimer = null;\n }\n if (timeoutTimer !== null) {\n window.clearTimeout(timeoutTimer);\n timeoutTimer = null;\n }\n };\n\n const flush = () => {\n const instance = checkInstanceReady();\n if (!instance) {\n return;\n }\n try {\n queue.forEach(({ method, args }) => {\n const methodFunction = instance[method];\n if (typeof methodFunction === \"function\") {\n void methodFunction.apply(instance, args);\n }\n });\n } finally {\n queue.length = 0;\n cleanup();\n }\n };\n\n const scheduleFlush = () => {\n if (typeof window === \"undefined\" || flushTimer !== null) {\n return;\n }\n\n flushTimer = window.setInterval(flush, 500);\n timeoutTimer = window.setTimeout(() => {\n queue.length = 0;\n permanentlyDisabled = true;\n if (typeof window !== \"undefined\") {\n console.debug(\n `[analytics] ${queueName} timed out and has been disabled`,\n );\n }\n options?.onTimeout?.();\n cleanup();\n }, QUEUE_TIMEOUT_MS);\n };\n\n // oxlint-disable-next-line typescript/no-explicit-any -- legacy any, may or may not be needed\n return (method: keyof InstanceType, ...args: any[]) => {\n if (permanentlyDisabled) {\n return;\n }\n\n const instance = checkInstanceReady();\n if (instance) {\n const methodFunction = instance[method];\n if (typeof methodFunction === \"function\") {\n void methodFunction.apply(instance, args);\n }\n } else {\n queue.push({ method, args });\n scheduleFlush();\n }\n };\n}\n"],
5
+ "mappings": "AAEA,MAAM,mBAAmB;AAElB,SAAS,qBAA8B;AAAA,EAC5C;AAAA,EACA;AAAA,EACA;AACF,GAI+B;AAC7B,QAAM,QAAmB,CAAC;AAC1B,MAAI,aAA4B;AAChC,MAAI,eAA8B;AAClC,MAAI,sBAAsB;AAE1B,QAAM,UAAU,MAAM;AACpB,QAAI,eAAe,MAAM;AACvB,aAAO,cAAc,UAAU;AAC/B,mBAAa;AAAA,IACf;AACA,QAAI,iBAAiB,MAAM;AACzB,aAAO,aAAa,YAAY;AAChC,qBAAe;AAAA,IACjB;AAAA,EACF;AAEA,QAAM,QAAQ,MAAM;AAClB,QAAI,CAAC,QAAQ,GAAG;AACd;AAAA,IACF;AACA,YAAQ;AAAA,MACN,wBAAwB,MAAM,MAAM,sBAAsB,SAAS;AAAA,IACrE;AACA,QAAI;AACF,YAAM,QAAQ,IAAI;AAAA,IACpB,UAAE;AACA,YAAM,SAAS;AACf,cAAQ;AAAA,IACV;AAAA,EACF;AAEA,QAAM,gBAAgB,MAAM;AAC1B,QAAI,OAAO,WAAW,eAAe,eAAe,MAAM;AACxD;AAAA,IACF;AAEA,iBAAa,OAAO,YAAY,OAAO,GAAG;AAC1C,mBAAe,OAAO,WAAW,MAAM;AACrC,YAAM,SAAS;AACf,4BAAsB;AACtB,cAAQ,MAAM,iCAAiC,SAAS,EAAE;AAC1D,cAAQ;AAAA,IACV,GAAG,gBAAgB;AAAA,EACrB;AAEA,SAAO,CAAC,YAAqB;AAC3B,QAAI,qBAAqB;AACvB,cAAQ;AAAA,QACN,eAAe,SAAS;AAAA,QACxB;AAAA,MACF;AACA;AAAA,IACF;AAEA,QAAI,QAAQ,GAAG;AACb,WAAK,OAAO;AAAA,IACd,OAAO;AACL,YAAM,KAAK,OAAO;AAClB,oBAAc;AAAA,IAChB;AAAA,EACF;AACF;AAEO,SAAS,iBACd,iBACA,WACA,WAC2D;AAC3D,QAAM,UAAU,qBAAkC;AAAA,IAChD,SAAS;AAAA,IACT,MAAM,CAAC,EAAE,OAAO,OAAO,MAAM,UAAU,OAAO,MAAM;AAAA,IACpD,WAAW;AAAA,EACb,CAAC;AAED,SAAO,CAAC,OAAO,SAAS,CAAC,MAAM,QAAQ,EAAE,OAAO,OAAO,CAAC;AAC1D;AAOO,SAAS,kBAId,oBACA,SAEsD;AAEtD,QAAM,QAAuD,CAAC;AAC9D,MAAI,aAA4B;AAChC,MAAI,eAA8B;AAClC,MAAI,sBAAsB;AAC1B,QAAM,YAAY,SAAS,aAAa;AAExC,QAAM,UAAU,MAAM;AACpB,QAAI,eAAe,MAAM;AACvB,aAAO,cAAc,UAAU;AAC/B,mBAAa;AAAA,IACf;AACA,QAAI,iBAAiB,MAAM;AACzB,aAAO,aAAa,YAAY;AAChC,qBAAe;AAAA,IACjB;AAAA,EACF;AAEA,QAAM,QAAQ,MAAM;AAClB,UAAM,WAAW,mBAAmB;AACpC,QAAI,CAAC,UAAU;AACb;AAAA,IACF;AACA,QAAI;AACF,YAAM,QAAQ,CAAC,EAAE,QAAQ,KAAK,MAAM;AAClC,cAAM,iBAAiB,SAAS,MAAM;AACtC,YAAI,OAAO,mBAAmB,YAAY;AACxC,eAAK,eAAe,MAAM,UAAU,IAAI;AAAA,QAC1C;AAAA,MACF,CAAC;AAAA,IACH,UAAE;AACA,YAAM,SAAS;AACf,cAAQ;AAAA,IACV;AAAA,EACF;AAEA,QAAM,gBAAgB,MAAM;AAC1B,QAAI,OAAO,WAAW,eAAe,eAAe,MAAM;AACxD;AAAA,IACF;AAEA,iBAAa,OAAO,YAAY,OAAO,GAAG;AAC1C,mBAAe,OAAO,WAAW,MAAM;AACrC,YAAM,SAAS;AACf,4BAAsB;AACtB,UAAI,OAAO,WAAW,aAAa;AACjC,gBAAQ;AAAA,UACN,eAAe,SAAS;AAAA,QAC1B;AAAA,MACF;AACA,eAAS,YAAY;AACrB,cAAQ;AAAA,IACV,GAAG,gBAAgB;AAAA,EACrB;AAGA,SAAO,CAAC,WAA+B,SAAgB;AACrD,QAAI,qBAAqB;AACvB;AAAA,IACF;AAEA,UAAM,WAAW,mBAAmB;AACpC,QAAI,UAAU;AACZ,YAAM,iBAAiB,SAAS,MAAM;AACtC,UAAI,OAAO,mBAAmB,YAAY;AACxC,aAAK,eAAe,MAAM,UAAU,IAAI;AAAA,MAC1C;AAAA,IACF,OAAO;AACL,YAAM,KAAK,EAAE,QAAQ,KAAK,CAAC;AAC3B,oBAAc;AAAA,IAChB;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -3,6 +3,7 @@ export type PageMetadata = {
3
3
  title: string;
4
4
  referrer: string;
5
5
  timestamp: number;
6
+ navigationType?: "initial" | "soft";
6
7
  };
7
8
  export declare class NavigationTracker {
8
9
  private lastUrl;
@@ -12,5 +13,6 @@ export declare class NavigationTracker {
12
13
  init(): void;
13
14
  private setupSPATracking;
14
15
  private handleNavigation;
16
+ private emitNavigation;
15
17
  cleanup(): void;
16
18
  }
@@ -6,7 +6,7 @@ class NavigationTracker {
6
6
  this.onNavigate = onNavigate;
7
7
  }
8
8
  init() {
9
- this.handleNavigation();
9
+ this.emitNavigation("initial");
10
10
  window.addEventListener("popstate", this.handleNavigation);
11
11
  this.setupSPATracking();
12
12
  }
@@ -18,6 +18,9 @@ class NavigationTracker {
18
18
  };
19
19
  }
20
20
  handleNavigation = () => {
21
+ this.emitNavigation("soft");
22
+ };
23
+ emitNavigation(navigationType) {
21
24
  const currentUrl = window.location.href;
22
25
  if (this.lastUrl !== null && this.lastUrl === currentUrl) {
23
26
  return;
@@ -27,10 +30,11 @@ class NavigationTracker {
27
30
  url: currentUrl,
28
31
  title: document.title,
29
32
  referrer: document.referrer,
30
- timestamp: Date.now()
33
+ timestamp: Date.now(),
34
+ navigationType
31
35
  };
32
36
  this.onNavigate(metadata);
33
- };
37
+ }
34
38
  cleanup() {
35
39
  window.removeEventListener("popstate", this.handleNavigation);
36
40
  if (this.originalPushState) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../analytics/utils/navigation-tracker.ts"],
4
- "sourcesContent": ["export type PageMetadata = {\n url: string;\n title: string;\n referrer: string;\n timestamp: number;\n};\n\nexport class NavigationTracker {\n private lastUrl: string | null = null;\n private onNavigate: (metadata: PageMetadata) => void;\n private originalPushState: typeof window.history.pushState | null = null;\n\n constructor(onNavigate: (metadata: PageMetadata) => void) {\n this.onNavigate = onNavigate;\n }\n\n init() {\n this.handleNavigation();\n\n window.addEventListener(\"popstate\", this.handleNavigation);\n\n this.setupSPATracking();\n }\n\n private setupSPATracking() {\n this.originalPushState = window.history.pushState;\n\n window.history.pushState = (...args) => {\n this.originalPushState!.apply(window.history, args);\n this.handleNavigation();\n };\n }\n\n private handleNavigation = () => {\n const currentUrl = window.location.href;\n\n if (this.lastUrl !== null && this.lastUrl === currentUrl) {\n return;\n }\n this.lastUrl = currentUrl;\n\n const metadata: PageMetadata = {\n url: currentUrl,\n title: document.title,\n referrer: document.referrer,\n timestamp: Date.now(),\n };\n\n this.onNavigate(metadata);\n };\n\n cleanup() {\n window.removeEventListener(\"popstate\", this.handleNavigation);\n\n if (this.originalPushState) {\n window.history.pushState = this.originalPushState;\n }\n }\n}\n"],
5
- "mappings": "AAOO,MAAM,kBAAkB;AAAA,EACrB,UAAyB;AAAA,EACzB;AAAA,EACA,oBAA4D;AAAA,EAEpE,YAAY,YAA8C;AACxD,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,OAAO;AACL,SAAK,iBAAiB;AAEtB,WAAO,iBAAiB,YAAY,KAAK,gBAAgB;AAEzD,SAAK,iBAAiB;AAAA,EACxB;AAAA,EAEQ,mBAAmB;AACzB,SAAK,oBAAoB,OAAO,QAAQ;AAExC,WAAO,QAAQ,YAAY,IAAI,SAAS;AACtC,WAAK,kBAAmB,MAAM,OAAO,SAAS,IAAI;AAClD,WAAK,iBAAiB;AAAA,IACxB;AAAA,EACF;AAAA,EAEQ,mBAAmB,MAAM;AAC/B,UAAM,aAAa,OAAO,SAAS;AAEnC,QAAI,KAAK,YAAY,QAAQ,KAAK,YAAY,YAAY;AACxD;AAAA,IACF;AACA,SAAK,UAAU;AAEf,UAAM,WAAyB;AAAA,MAC7B,KAAK;AAAA,MACL,OAAO,SAAS;AAAA,MAChB,UAAU,SAAS;AAAA,MACnB,WAAW,KAAK,IAAI;AAAA,IACtB;AAEA,SAAK,WAAW,QAAQ;AAAA,EAC1B;AAAA,EAEA,UAAU;AACR,WAAO,oBAAoB,YAAY,KAAK,gBAAgB;AAE5D,QAAI,KAAK,mBAAmB;AAC1B,aAAO,QAAQ,YAAY,KAAK;AAAA,IAClC;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["export type PageMetadata = {\n url: string;\n title: string;\n referrer: string;\n timestamp: number;\n navigationType?: \"initial\" | \"soft\";\n};\n\nexport class NavigationTracker {\n private lastUrl: string | null = null;\n private onNavigate: (metadata: PageMetadata) => void;\n private originalPushState: typeof window.history.pushState | null = null;\n\n constructor(onNavigate: (metadata: PageMetadata) => void) {\n this.onNavigate = onNavigate;\n }\n\n init() {\n this.emitNavigation(\"initial\");\n\n window.addEventListener(\"popstate\", this.handleNavigation);\n\n this.setupSPATracking();\n }\n\n private setupSPATracking() {\n this.originalPushState = window.history.pushState;\n\n window.history.pushState = (...args) => {\n this.originalPushState!.apply(window.history, args);\n this.handleNavigation();\n };\n }\n\n private handleNavigation = () => {\n this.emitNavigation(\"soft\");\n };\n\n private emitNavigation(navigationType: PageMetadata[\"navigationType\"]) {\n const currentUrl = window.location.href;\n\n if (this.lastUrl !== null && this.lastUrl === currentUrl) {\n return;\n }\n this.lastUrl = currentUrl;\n\n const metadata: PageMetadata = {\n url: currentUrl,\n title: document.title,\n referrer: document.referrer,\n timestamp: Date.now(),\n navigationType,\n };\n\n this.onNavigate(metadata);\n }\n\n cleanup() {\n window.removeEventListener(\"popstate\", this.handleNavigation);\n\n if (this.originalPushState) {\n window.history.pushState = this.originalPushState;\n }\n }\n}\n"],
5
+ "mappings": "AAQO,MAAM,kBAAkB;AAAA,EACrB,UAAyB;AAAA,EACzB;AAAA,EACA,oBAA4D;AAAA,EAEpE,YAAY,YAA8C;AACxD,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,OAAO;AACL,SAAK,eAAe,SAAS;AAE7B,WAAO,iBAAiB,YAAY,KAAK,gBAAgB;AAEzD,SAAK,iBAAiB;AAAA,EACxB;AAAA,EAEQ,mBAAmB;AACzB,SAAK,oBAAoB,OAAO,QAAQ;AAExC,WAAO,QAAQ,YAAY,IAAI,SAAS;AACtC,WAAK,kBAAmB,MAAM,OAAO,SAAS,IAAI;AAClD,WAAK,iBAAiB;AAAA,IACxB;AAAA,EACF;AAAA,EAEQ,mBAAmB,MAAM;AAC/B,SAAK,eAAe,MAAM;AAAA,EAC5B;AAAA,EAEQ,eAAe,gBAAgD;AACrE,UAAM,aAAa,OAAO,SAAS;AAEnC,QAAI,KAAK,YAAY,QAAQ,KAAK,YAAY,YAAY;AACxD;AAAA,IACF;AACA,SAAK,UAAU;AAEf,UAAM,WAAyB;AAAA,MAC7B,KAAK;AAAA,MACL,OAAO,SAAS;AAAA,MAChB,UAAU,SAAS;AAAA,MACnB,WAAW,KAAK,IAAI;AAAA,MACpB;AAAA,IACF;AAEA,SAAK,WAAW,QAAQ;AAAA,EAC1B;AAAA,EAEA,UAAU;AACR,WAAO,oBAAoB,YAAY,KAAK,gBAAgB;AAE5D,QAAI,KAAK,mBAAmB;AAC1B,aAAO,QAAQ,YAAY,KAAK;AAAA,IAClC;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -133,7 +133,10 @@ function CartProvider({
133
133
  price: fromMinorUnitsToMajorUnits({
134
134
  amount: item.merchandise.price,
135
135
  currencyCode: analyticsCurrencyCode
136
- })
136
+ }),
137
+ currency: analyticsCurrencyCode,
138
+ productTitle: item.merchandise.product.title,
139
+ variantTitle: item.merchandise.title
137
140
  }
138
141
  ];
139
142
  })
@@ -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 { recalculateCartCost } from \"./utils/cart-utils\";\nimport { createOptimisticSellingPlanAllocation } from \"./utils/variant-to-cart-line\";\n\nclass CartContextError extends CanopyError {}\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.\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 = cartData?.checkoutUrl ?? \"/\";\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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",
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// 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.\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 currency: analyticsCurrencyCode,\n productTitle: item.merchandise.product.title,\n variantTitle: item.merchandise.title,\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 = cartData?.checkoutUrl ?? \"/\";\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
  }
@@ -70,5 +70,9 @@ export declare const cartSchema: z.ZodObject<{
70
70
  code: z.ZodString;
71
71
  applicable: z.ZodBoolean;
72
72
  }, z.core.$strip>>>;
73
+ discountAllocations: z.ZodOptional<z.ZodArray<z.ZodObject<{
74
+ discountedAmount: z.ZodNumber;
75
+ title: z.ZodNullable<z.ZodString>;
76
+ }, z.core.$strip>>>;
73
77
  }, z.core.$strip>;
74
78
  export type Cart = z.infer<typeof cartSchema>;
@@ -56,12 +56,17 @@ const cartDiscountCodeSchema = z.object({
56
56
  code: z.string(),
57
57
  applicable: z.boolean()
58
58
  });
59
+ const cartDiscountAllocationSchema = z.object({
60
+ discountedAmount: amountSchema,
61
+ title: z.string().nullable()
62
+ });
59
63
  const cartSchema = z.object({
60
64
  id: z.string(),
61
65
  lines: z.array(cartLineSchema),
62
66
  cost: cartCostSchema,
63
67
  checkoutUrl: z.string().optional(),
64
- discountCodes: z.array(cartDiscountCodeSchema).optional()
68
+ discountCodes: z.array(cartDiscountCodeSchema).optional(),
69
+ discountAllocations: z.array(cartDiscountAllocationSchema).optional()
65
70
  });
66
71
  export {
67
72
  cartSchema
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../cart/cart-schema.ts"],
4
- "sourcesContent": ["import { z } from \"zod\";\n\n// NOTE (Ryan, 2026-07-23, REPL-27611): Self-contained copy of the cart wire\n// schema that the SDK gateway validates on published pages, so nothing\n// shipped to a generated site imports from `schemas`. The backend keeps the\n// canonical copy in `schemas/cart.ts` (its cart services can't depend on\n// the SDK without a cycle); `cart-schema.spec.ts` keeps the two in lockstep.\n// This module is internal wire validation; public types live in `cart/cart-types`.\n\n// ---------------------------------------------------------------------------\n// Cart line attribute\n// ---------------------------------------------------------------------------\n\nconst cartLineAttributeSchema = z.object({\n key: z.string(),\n value: z.union([z.string(), z.number(), z.boolean()]),\n});\n\n// ---------------------------------------------------------------------------\n// Image (cart-scoped; lightweight compared to objectType image)\n// ---------------------------------------------------------------------------\n\nconst cartImageSchema = z.object({\n url: z.string(),\n altText: z.string().nullable(),\n});\n\n// ---------------------------------------------------------------------------\n// Selling plan types\n// ---------------------------------------------------------------------------\n\n// Integer minor units (Stripe convention): 1999 = $19.99 USD. Mirrors\n// amountSchema in schemas/money.ts, which shipped code cannot import.\nconst amountSchema = z.number().int();\nconst amountWithCurrencyCodeSchema = z.object({\n amount: amountSchema,\n currencyCode: z.string(),\n});\n\nconst sellingPlanPriceAdjustmentSchema = z.object({\n compareAtPrice: amountWithCurrencyCodeSchema,\n perDeliveryPrice: amountWithCurrencyCodeSchema,\n price: amountWithCurrencyCodeSchema,\n unitPrice: amountWithCurrencyCodeSchema.nullable(),\n});\n\nconst sellingPlanAllocationSchema = z.object({\n sellingPlan: z.object({\n id: z.string(),\n name: z.string(),\n description: z.string().nullable(),\n }),\n priceAdjustments: z.array(sellingPlanPriceAdjustmentSchema),\n checkoutChargeAmount: amountWithCurrencyCodeSchema,\n remainingBalanceChargeAmount: amountWithCurrencyCodeSchema,\n});\n\n// ---------------------------------------------------------------------------\n// Cart line merchandise\n// ---------------------------------------------------------------------------\n\nconst cartLineMerchandiseSchema = z.object({\n id: z.string(),\n product: z.object({\n id: z.string(),\n title: z.string(),\n }),\n title: z.string(),\n price: amountSchema,\n compareAtPrice: amountSchema.nullish(),\n image: cartImageSchema.nullable(),\n selectedOptions: z.array(z.object({ name: z.string(), value: z.string() })),\n});\n\n// ---------------------------------------------------------------------------\n// Cart line\n// ---------------------------------------------------------------------------\n\nconst cartLineSchema = z.object({\n id: z.string(),\n quantity: z.number(),\n merchandise: cartLineMerchandiseSchema,\n attributes: z.array(cartLineAttributeSchema),\n sellingPlanAllocation: sellingPlanAllocationSchema.nullish(),\n});\n\n// ---------------------------------------------------------------------------\n// Cart cost\n// ---------------------------------------------------------------------------\n\nconst cartCostSchema = z.object({\n subtotalAmount: amountSchema,\n totalAmount: amountSchema,\n currencyCode: z.string(),\n});\n\n// ---------------------------------------------------------------------------\n// Cart discount code\n// ---------------------------------------------------------------------------\n\nconst cartDiscountCodeSchema = z.object({\n code: z.string(),\n applicable: z.boolean(),\n});\n\n// ---------------------------------------------------------------------------\n// Cart (top level)\n// ---------------------------------------------------------------------------\n\nexport const cartSchema = z.object({\n id: z.string(),\n lines: z.array(cartLineSchema),\n cost: cartCostSchema,\n checkoutUrl: z.string().optional(),\n discountCodes: z.array(cartDiscountCodeSchema).optional(),\n});\n\nexport type Cart = z.infer<typeof cartSchema>;\n"],
5
- "mappings": "AAAA,SAAS,SAAS;AAalB,MAAM,0BAA0B,EAAE,OAAO;AAAA,EACvC,KAAK,EAAE,OAAO;AAAA,EACd,OAAO,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,CAAC;AACtD,CAAC;AAMD,MAAM,kBAAkB,EAAE,OAAO;AAAA,EAC/B,KAAK,EAAE,OAAO;AAAA,EACd,SAAS,EAAE,OAAO,EAAE,SAAS;AAC/B,CAAC;AAQD,MAAM,eAAe,EAAE,OAAO,EAAE,IAAI;AACpC,MAAM,+BAA+B,EAAE,OAAO;AAAA,EAC5C,QAAQ;AAAA,EACR,cAAc,EAAE,OAAO;AACzB,CAAC;AAED,MAAM,mCAAmC,EAAE,OAAO;AAAA,EAChD,gBAAgB;AAAA,EAChB,kBAAkB;AAAA,EAClB,OAAO;AAAA,EACP,WAAW,6BAA6B,SAAS;AACnD,CAAC;AAED,MAAM,8BAA8B,EAAE,OAAO;AAAA,EAC3C,aAAa,EAAE,OAAO;AAAA,IACpB,IAAI,EAAE,OAAO;AAAA,IACb,MAAM,EAAE,OAAO;AAAA,IACf,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,EACnC,CAAC;AAAA,EACD,kBAAkB,EAAE,MAAM,gCAAgC;AAAA,EAC1D,sBAAsB;AAAA,EACtB,8BAA8B;AAChC,CAAC;AAMD,MAAM,4BAA4B,EAAE,OAAO;AAAA,EACzC,IAAI,EAAE,OAAO;AAAA,EACb,SAAS,EAAE,OAAO;AAAA,IAChB,IAAI,EAAE,OAAO;AAAA,IACb,OAAO,EAAE,OAAO;AAAA,EAClB,CAAC;AAAA,EACD,OAAO,EAAE,OAAO;AAAA,EAChB,OAAO;AAAA,EACP,gBAAgB,aAAa,QAAQ;AAAA,EACrC,OAAO,gBAAgB,SAAS;AAAA,EAChC,iBAAiB,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;AAC5E,CAAC;AAMD,MAAM,iBAAiB,EAAE,OAAO;AAAA,EAC9B,IAAI,EAAE,OAAO;AAAA,EACb,UAAU,EAAE,OAAO;AAAA,EACnB,aAAa;AAAA,EACb,YAAY,EAAE,MAAM,uBAAuB;AAAA,EAC3C,uBAAuB,4BAA4B,QAAQ;AAC7D,CAAC;AAMD,MAAM,iBAAiB,EAAE,OAAO;AAAA,EAC9B,gBAAgB;AAAA,EAChB,aAAa;AAAA,EACb,cAAc,EAAE,OAAO;AACzB,CAAC;AAMD,MAAM,yBAAyB,EAAE,OAAO;AAAA,EACtC,MAAM,EAAE,OAAO;AAAA,EACf,YAAY,EAAE,QAAQ;AACxB,CAAC;AAMM,MAAM,aAAa,EAAE,OAAO;AAAA,EACjC,IAAI,EAAE,OAAO;AAAA,EACb,OAAO,EAAE,MAAM,cAAc;AAAA,EAC7B,MAAM;AAAA,EACN,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,EACjC,eAAe,EAAE,MAAM,sBAAsB,EAAE,SAAS;AAC1D,CAAC;",
4
+ "sourcesContent": ["import { z } from \"zod\";\n\n// NOTE (Ryan, 2026-07-23, REPL-27611): Self-contained copy of the cart wire\n// schema that the SDK gateway validates on published pages, so nothing\n// shipped to a generated site imports from `schemas`. The backend keeps the\n// canonical copy in `schemas/cart.ts` (its cart services can't depend on\n// the SDK without a cycle); `cart-schema.spec.ts` keeps the two in lockstep.\n// This module is internal wire validation; public types live in `cart/cart-types`.\n\n// ---------------------------------------------------------------------------\n// Cart line attribute\n// ---------------------------------------------------------------------------\n\nconst cartLineAttributeSchema = z.object({\n key: z.string(),\n value: z.union([z.string(), z.number(), z.boolean()]),\n});\n\n// ---------------------------------------------------------------------------\n// Image (cart-scoped; lightweight compared to objectType image)\n// ---------------------------------------------------------------------------\n\nconst cartImageSchema = z.object({\n url: z.string(),\n altText: z.string().nullable(),\n});\n\n// ---------------------------------------------------------------------------\n// Selling plan types\n// ---------------------------------------------------------------------------\n\n// Integer minor units (Stripe convention): 1999 = $19.99 USD. Mirrors\n// amountSchema in schemas/money.ts, which shipped code cannot import.\nconst amountSchema = z.number().int();\nconst amountWithCurrencyCodeSchema = z.object({\n amount: amountSchema,\n currencyCode: z.string(),\n});\n\nconst sellingPlanPriceAdjustmentSchema = z.object({\n compareAtPrice: amountWithCurrencyCodeSchema,\n perDeliveryPrice: amountWithCurrencyCodeSchema,\n price: amountWithCurrencyCodeSchema,\n unitPrice: amountWithCurrencyCodeSchema.nullable(),\n});\n\nconst sellingPlanAllocationSchema = z.object({\n sellingPlan: z.object({\n id: z.string(),\n name: z.string(),\n description: z.string().nullable(),\n }),\n priceAdjustments: z.array(sellingPlanPriceAdjustmentSchema),\n checkoutChargeAmount: amountWithCurrencyCodeSchema,\n remainingBalanceChargeAmount: amountWithCurrencyCodeSchema,\n});\n\n// ---------------------------------------------------------------------------\n// Cart line merchandise\n// ---------------------------------------------------------------------------\n\nconst cartLineMerchandiseSchema = z.object({\n id: z.string(),\n product: z.object({\n id: z.string(),\n title: z.string(),\n }),\n title: z.string(),\n price: amountSchema,\n compareAtPrice: amountSchema.nullish(),\n image: cartImageSchema.nullable(),\n selectedOptions: z.array(z.object({ name: z.string(), value: z.string() })),\n});\n\n// ---------------------------------------------------------------------------\n// Cart line\n// ---------------------------------------------------------------------------\n\nconst cartLineSchema = z.object({\n id: z.string(),\n quantity: z.number(),\n merchandise: cartLineMerchandiseSchema,\n attributes: z.array(cartLineAttributeSchema),\n sellingPlanAllocation: sellingPlanAllocationSchema.nullish(),\n});\n\n// ---------------------------------------------------------------------------\n// Cart cost\n// ---------------------------------------------------------------------------\n\nconst cartCostSchema = z.object({\n subtotalAmount: amountSchema,\n totalAmount: amountSchema,\n currencyCode: z.string(),\n});\n\n// ---------------------------------------------------------------------------\n// Cart discount code\n// ---------------------------------------------------------------------------\n\nconst cartDiscountCodeSchema = z.object({\n code: z.string(),\n applicable: z.boolean(),\n});\n\n// ---------------------------------------------------------------------------\n// Cart discount allocation\n// ---------------------------------------------------------------------------\n\nconst cartDiscountAllocationSchema = z.object({\n discountedAmount: amountSchema,\n title: z.string().nullable(),\n});\n\n// ---------------------------------------------------------------------------\n// Cart (top level)\n// ---------------------------------------------------------------------------\n\nexport const cartSchema = z.object({\n id: z.string(),\n lines: z.array(cartLineSchema),\n cost: cartCostSchema,\n checkoutUrl: z.string().optional(),\n discountCodes: z.array(cartDiscountCodeSchema).optional(),\n discountAllocations: z.array(cartDiscountAllocationSchema).optional(),\n});\n\nexport type Cart = z.infer<typeof cartSchema>;\n"],
5
+ "mappings": "AAAA,SAAS,SAAS;AAalB,MAAM,0BAA0B,EAAE,OAAO;AAAA,EACvC,KAAK,EAAE,OAAO;AAAA,EACd,OAAO,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,CAAC;AACtD,CAAC;AAMD,MAAM,kBAAkB,EAAE,OAAO;AAAA,EAC/B,KAAK,EAAE,OAAO;AAAA,EACd,SAAS,EAAE,OAAO,EAAE,SAAS;AAC/B,CAAC;AAQD,MAAM,eAAe,EAAE,OAAO,EAAE,IAAI;AACpC,MAAM,+BAA+B,EAAE,OAAO;AAAA,EAC5C,QAAQ;AAAA,EACR,cAAc,EAAE,OAAO;AACzB,CAAC;AAED,MAAM,mCAAmC,EAAE,OAAO;AAAA,EAChD,gBAAgB;AAAA,EAChB,kBAAkB;AAAA,EAClB,OAAO;AAAA,EACP,WAAW,6BAA6B,SAAS;AACnD,CAAC;AAED,MAAM,8BAA8B,EAAE,OAAO;AAAA,EAC3C,aAAa,EAAE,OAAO;AAAA,IACpB,IAAI,EAAE,OAAO;AAAA,IACb,MAAM,EAAE,OAAO;AAAA,IACf,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,EACnC,CAAC;AAAA,EACD,kBAAkB,EAAE,MAAM,gCAAgC;AAAA,EAC1D,sBAAsB;AAAA,EACtB,8BAA8B;AAChC,CAAC;AAMD,MAAM,4BAA4B,EAAE,OAAO;AAAA,EACzC,IAAI,EAAE,OAAO;AAAA,EACb,SAAS,EAAE,OAAO;AAAA,IAChB,IAAI,EAAE,OAAO;AAAA,IACb,OAAO,EAAE,OAAO;AAAA,EAClB,CAAC;AAAA,EACD,OAAO,EAAE,OAAO;AAAA,EAChB,OAAO;AAAA,EACP,gBAAgB,aAAa,QAAQ;AAAA,EACrC,OAAO,gBAAgB,SAAS;AAAA,EAChC,iBAAiB,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;AAC5E,CAAC;AAMD,MAAM,iBAAiB,EAAE,OAAO;AAAA,EAC9B,IAAI,EAAE,OAAO;AAAA,EACb,UAAU,EAAE,OAAO;AAAA,EACnB,aAAa;AAAA,EACb,YAAY,EAAE,MAAM,uBAAuB;AAAA,EAC3C,uBAAuB,4BAA4B,QAAQ;AAC7D,CAAC;AAMD,MAAM,iBAAiB,EAAE,OAAO;AAAA,EAC9B,gBAAgB;AAAA,EAChB,aAAa;AAAA,EACb,cAAc,EAAE,OAAO;AACzB,CAAC;AAMD,MAAM,yBAAyB,EAAE,OAAO;AAAA,EACtC,MAAM,EAAE,OAAO;AAAA,EACf,YAAY,EAAE,QAAQ;AACxB,CAAC;AAMD,MAAM,+BAA+B,EAAE,OAAO;AAAA,EAC5C,kBAAkB;AAAA,EAClB,OAAO,EAAE,OAAO,EAAE,SAAS;AAC7B,CAAC;AAMM,MAAM,aAAa,EAAE,OAAO;AAAA,EACjC,IAAI,EAAE,OAAO;AAAA,EACb,OAAO,EAAE,MAAM,cAAc;AAAA,EAC7B,MAAM;AAAA,EACN,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,EACjC,eAAe,EAAE,MAAM,sBAAsB,EAAE,SAAS;AAAA,EACxD,qBAAqB,EAAE,MAAM,4BAA4B,EAAE,SAAS;AACtE,CAAC;",
6
6
  "names": []
7
7
  }
@@ -100,12 +100,24 @@ export interface CartDiscountCode {
100
100
  */
101
101
  applicable: boolean;
102
102
  }
103
+ /**
104
+ * One discount Shopify applied to a cart line or to the whole cart. The amount
105
+ * is already subtracted from `cost.totalAmount` (and from `cost.subtotalAmount`
106
+ * for line-level discounts).
107
+ */
108
+ export interface CartDiscountAllocation {
109
+ /** Integer minor units (Stripe convention) in the cart's currencyCode */
110
+ discountedAmount: number;
111
+ /** Automatic discount title or discount code; null when Shopify has neither */
112
+ title: string | null;
113
+ }
103
114
  export interface Cart {
104
115
  id: string;
105
116
  lines: CartLine[];
106
117
  cost: CartCost;
107
118
  checkoutUrl?: string;
108
119
  discountCodes?: CartDiscountCode[];
120
+ discountAllocations?: CartDiscountAllocation[];
109
121
  }
110
122
  export interface CartLinePayload {
111
123
  id: string;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../cart/cart-types.ts"],
4
- "sourcesContent": ["/**\n * TypeScript types for cart data structures. Use these types when working with cart data, but don't import from this module directly in components - use the cart hooks instead.\n * @module\n */\n\nimport type { SellingPlanGroup } from \"../loaders/loader-schemas\";\n\nexport interface Image {\n url: string;\n altText: string | null;\n}\n\nexport interface CartLineMerchandise {\n id: string;\n product: {\n id: string;\n title: string;\n sellingPlanGroups?: SellingPlanGroup[];\n };\n title: string;\n /** Integer minor units (Stripe convention): 1999 = $19.99 USD */\n price: number;\n compareAtPrice?: number | null;\n image: Image | null;\n selectedOptions: {\n name: string;\n value: string;\n }[];\n}\n\nexport interface CartLineAttribute {\n key: string;\n value: string | number | boolean;\n}\n\n/** Replo attribution property added to cart/buy-now line items for order attribution in Shopify. */\nexport const REPLO_ATTRIBUTION_PROPERTY: CartLineAttribute = {\n key: \"_replo\",\n value: \"true\",\n};\n\nexport interface SellingPlanPriceAdjustment {\n compareAtPrice: {\n amount: number;\n currencyCode: string;\n };\n perDeliveryPrice: {\n amount: number;\n currencyCode: string;\n };\n price: {\n amount: number;\n currencyCode: string;\n };\n unitPrice: {\n amount: number;\n currencyCode: string;\n } | null;\n}\n\nexport interface SellingPlanAllocation {\n sellingPlan: {\n id: string;\n name: string;\n description: string | null;\n };\n priceAdjustments: SellingPlanPriceAdjustment[];\n checkoutChargeAmount: {\n amount: number;\n currencyCode: string;\n };\n remainingBalanceChargeAmount: {\n amount: number;\n currencyCode: string;\n };\n}\n\nexport interface CartLine {\n id: string;\n quantity: number;\n /**\n * Null when this is an optimistic line whose merchandise couldn't be\n * resolved from the query cache or passed explicitly. Consumers should\n * render a skeleton/loading state in that case; the line is replaced\n * with real merchandise once the server roundtrip lands.\n */\n merchandise: CartLineMerchandise | null;\n attributes: CartLineAttribute[];\n sellingPlanAllocation?: SellingPlanAllocation | null;\n}\n\nexport interface CartCost {\n /** Integer minor units (Stripe convention) in currencyCode */\n subtotalAmount: number;\n totalAmount: number;\n currencyCode: string;\n}\n\n/**\n * Represents a discount code applied to a cart.\n */\nexport interface CartDiscountCode {\n /** The discount code string */\n code: string;\n /**\n * Whether the discount code is applicable to the current cart.\n * A code may not be applicable due to:\n * - Cart contents not meeting product/collection eligibility\n * - Minimum purchase amount not met\n * - Code expired or usage limits reached\n * - Customer restrictions\n * - Conflicting discount rules\n */\n applicable: boolean;\n}\n\nexport interface Cart {\n id: string;\n lines: CartLine[];\n cost: CartCost;\n checkoutUrl?: string;\n discountCodes?: CartDiscountCode[];\n}\n\nexport interface CartLinePayload {\n id: string;\n quantity: number;\n merchandise?: CartLineMerchandise;\n properties?: CartLineAttribute[];\n sellingPlanId?: string | null;\n}\n\nexport interface AddToCartPayload {\n lines: CartLinePayload[];\n cartId: string;\n}\n\nexport interface UpdateCartLineItemPayload {\n line: CartLinePayload;\n cartId: string;\n}\n\nexport interface RemoveFromCartPayload {\n lineIds: string[];\n cartId: string;\n}\n\nexport interface UpdateCartDiscountCodesPayload {\n cartId: string;\n discountCodes: string[];\n}\n"],
4
+ "sourcesContent": ["/**\n * TypeScript types for cart data structures. Use these types when working with cart data, but don't import from this module directly in components - use the cart hooks instead.\n * @module\n */\n\nimport type { SellingPlanGroup } from \"../loaders/loader-schemas\";\n\nexport interface Image {\n url: string;\n altText: string | null;\n}\n\nexport interface CartLineMerchandise {\n id: string;\n product: {\n id: string;\n title: string;\n sellingPlanGroups?: SellingPlanGroup[];\n };\n title: string;\n /** Integer minor units (Stripe convention): 1999 = $19.99 USD */\n price: number;\n compareAtPrice?: number | null;\n image: Image | null;\n selectedOptions: {\n name: string;\n value: string;\n }[];\n}\n\nexport interface CartLineAttribute {\n key: string;\n value: string | number | boolean;\n}\n\n/** Replo attribution property added to cart/buy-now line items for order attribution in Shopify. */\nexport const REPLO_ATTRIBUTION_PROPERTY: CartLineAttribute = {\n key: \"_replo\",\n value: \"true\",\n};\n\nexport interface SellingPlanPriceAdjustment {\n compareAtPrice: {\n amount: number;\n currencyCode: string;\n };\n perDeliveryPrice: {\n amount: number;\n currencyCode: string;\n };\n price: {\n amount: number;\n currencyCode: string;\n };\n unitPrice: {\n amount: number;\n currencyCode: string;\n } | null;\n}\n\nexport interface SellingPlanAllocation {\n sellingPlan: {\n id: string;\n name: string;\n description: string | null;\n };\n priceAdjustments: SellingPlanPriceAdjustment[];\n checkoutChargeAmount: {\n amount: number;\n currencyCode: string;\n };\n remainingBalanceChargeAmount: {\n amount: number;\n currencyCode: string;\n };\n}\n\nexport interface CartLine {\n id: string;\n quantity: number;\n /**\n * Null when this is an optimistic line whose merchandise couldn't be\n * resolved from the query cache or passed explicitly. Consumers should\n * render a skeleton/loading state in that case; the line is replaced\n * with real merchandise once the server roundtrip lands.\n */\n merchandise: CartLineMerchandise | null;\n attributes: CartLineAttribute[];\n sellingPlanAllocation?: SellingPlanAllocation | null;\n}\n\nexport interface CartCost {\n /** Integer minor units (Stripe convention) in currencyCode */\n subtotalAmount: number;\n totalAmount: number;\n currencyCode: string;\n}\n\n/**\n * Represents a discount code applied to a cart.\n */\nexport interface CartDiscountCode {\n /** The discount code string */\n code: string;\n /**\n * Whether the discount code is applicable to the current cart.\n * A code may not be applicable due to:\n * - Cart contents not meeting product/collection eligibility\n * - Minimum purchase amount not met\n * - Code expired or usage limits reached\n * - Customer restrictions\n * - Conflicting discount rules\n */\n applicable: boolean;\n}\n\n/**\n * One discount Shopify applied to a cart line or to the whole cart. The amount\n * is already subtracted from `cost.totalAmount` (and from `cost.subtotalAmount`\n * for line-level discounts).\n */\nexport interface CartDiscountAllocation {\n /** Integer minor units (Stripe convention) in the cart's currencyCode */\n discountedAmount: number;\n /** Automatic discount title or discount code; null when Shopify has neither */\n title: string | null;\n}\n\nexport interface Cart {\n id: string;\n lines: CartLine[];\n cost: CartCost;\n checkoutUrl?: string;\n discountCodes?: CartDiscountCode[];\n discountAllocations?: CartDiscountAllocation[];\n}\n\nexport interface CartLinePayload {\n id: string;\n quantity: number;\n merchandise?: CartLineMerchandise;\n properties?: CartLineAttribute[];\n sellingPlanId?: string | null;\n}\n\nexport interface AddToCartPayload {\n lines: CartLinePayload[];\n cartId: string;\n}\n\nexport interface UpdateCartLineItemPayload {\n line: CartLinePayload;\n cartId: string;\n}\n\nexport interface RemoveFromCartPayload {\n lineIds: string[];\n cartId: string;\n}\n\nexport interface UpdateCartDiscountCodesPayload {\n cartId: string;\n discountCodes: string[];\n}\n"],
5
5
  "mappings": "AAoCO,MAAM,6BAAgD;AAAA,EAC3D,KAAK;AAAA,EACL,OAAO;AACT;",
6
6
  "names": []
7
7
  }
@@ -13,20 +13,21 @@ export interface CartTotals {
13
13
  export declare function calculateLineItemsSubtotal(lineItems: CartLine[]): number;
14
14
  /**
15
15
  * Recompute a cart's cost from its line items. Cookie carts are their own source
16
- * of truth (no server roundtrip) and apply no cart-level discounts, so subtotal
17
- * equals total. Without this the cost stays at the 0 it was created with, and
18
- * every consumer that trusts it (cart total, checkout analytics) reports $0 for
19
- * a priced cart.
16
+ * of truth (no server roundtrip) and apply no discounts, so subtotal equals
17
+ * total and there are no discount allocations. Without this the cost stays at
18
+ * the 0 it was created with, and every consumer that trusts it (cart total,
19
+ * checkout analytics) reports $0 for a priced cart.
20
20
  */
21
21
  export declare function recalculateCartCost(cart: Cart): Cart;
22
22
  /**
23
23
  * Calculate cart totals including subtotal, discounts, and final total.
24
24
  * When cart cost data is provided (from Shopify), uses the actual totalAmount
25
- * which includes cart-level discounts from discount codes.
25
+ * and sums the provider's discount allocations (automatic, code, and custom
26
+ * discounts, at both line and cart level) for the discount amount.
26
27
  *
27
28
  * @param options - Object containing lineItems and cart
28
29
  * @param options.lineItems - Array of cart line items
29
- * @param options.cart - Optional cart data from Shopify (includes discount codes)
30
+ * @param options.cart - Optional cart data from Shopify (includes discount allocations)
30
31
  */
31
32
  export declare function calculateCartTotals({ lineItems, cart, }: {
32
33
  lineItems: CartLine[];
@@ -14,7 +14,8 @@ function recalculateCartCost(cart) {
14
14
  ...cart.cost,
15
15
  subtotalAmount: subtotal,
16
16
  totalAmount: subtotal
17
- }
17
+ },
18
+ discountAllocations: []
18
19
  };
19
20
  }
20
21
  function calculateCartTotals({
@@ -26,8 +27,10 @@ function calculateCartTotals({
26
27
  return sum + getCartLinePricing(item).compareAtPrice * item.quantity;
27
28
  }, 0);
28
29
  const total = cart?.cost.totalAmount ?? lineItemSubtotal;
29
- const actualSubtotal = cart?.cost.subtotalAmount ?? lineItemSubtotal;
30
- const discountAmount = actualSubtotal - total;
30
+ const discountAmount = (cart?.discountAllocations ?? []).reduce(
31
+ (sum, allocation) => sum + allocation.discountedAmount,
32
+ 0
33
+ );
31
34
  const totalSavings = compareAtSubtotal - total;
32
35
  return {
33
36
  subtotal: lineItemSubtotal,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../cart/utils/cart-utils.ts"],
4
- "sourcesContent": ["import type { Cart, CartLine } from \"../cart-types\";\n\nimport { formatAmount } from \"schemas/money\";\n\nimport { getCartLinePricing } from \"./variant-to-cart-line\";\n\nexport interface CartTotals {\n subtotal: number;\n discountAmount: number;\n totalSavings: number;\n total: number;\n}\n\n/**\n * Sum the final (post selling-plan) price of every line item in the cart.\n * This is the authoritative subtotal for cookie-based carts, which have no\n * server-computed cost to rely on.\n */\nexport function calculateLineItemsSubtotal(lineItems: CartLine[]): number {\n return lineItems.reduce(\n (sum, item) => sum + getCartLinePricing(item).finalPrice * item.quantity,\n 0,\n );\n}\n\n/**\n * Recompute a cart's cost from its line items. Cookie carts are their own source\n * of truth (no server roundtrip) and apply no cart-level discounts, so subtotal\n * equals total. Without this the cost stays at the 0 it was created with, and\n * every consumer that trusts it (cart total, checkout analytics) reports $0 for\n * a priced cart.\n */\nexport function recalculateCartCost(cart: Cart): Cart {\n const subtotal = calculateLineItemsSubtotal(cart.lines);\n return {\n ...cart,\n cost: {\n ...cart.cost,\n subtotalAmount: subtotal,\n totalAmount: subtotal,\n },\n };\n}\n\n/**\n * Calculate cart totals including subtotal, discounts, and final total.\n * When cart cost data is provided (from Shopify), uses the actual totalAmount\n * which includes cart-level discounts from discount codes.\n *\n * @param options - Object containing lineItems and cart\n * @param options.lineItems - Array of cart line items\n * @param options.cart - Optional cart data from Shopify (includes discount codes)\n */\nexport function calculateCartTotals({\n lineItems,\n cart,\n}: {\n lineItems: CartLine[];\n cart: Cart | null;\n}): CartTotals {\n const lineItemSubtotal = calculateLineItemsSubtotal(lineItems);\n const compareAtSubtotal = lineItems.reduce((sum, item) => {\n return sum + getCartLinePricing(item).compareAtPrice * item.quantity;\n }, 0);\n\n // NOTE (Jasmine, 2026-07-22): Trust the provider's authoritative cost when\n // present. For Shopify this is the server-computed cart cost (including a\n // legitimate $0 when items aren't purchasable), which we must not\n // second-guess; cookie carts keep their cost truthful via\n // cookie-cart-persistence.ts.\n const total = cart?.cost.totalAmount ?? lineItemSubtotal;\n\n // If we have provider cost data, use subtotalAmount which doesn't include discounts\n const actualSubtotal = cart?.cost.subtotalAmount ?? lineItemSubtotal;\n const discountAmount = actualSubtotal - total;\n\n const totalSavings = compareAtSubtotal - total;\n\n return {\n subtotal: lineItemSubtotal,\n discountAmount,\n totalSavings,\n total,\n };\n}\n\n/**\n * @deprecated Back-compat shim kept for surface parity with canopy-sdk, so\n * sites migrating off `import { formatPrice } from\n * \"canopy-sdk/cart/utils/cart-utils\"` keep rendering with only the package\n * specifier renamed. It formats INTEGER MINOR UNITS \u2014 the units every cart\n * value uses (`calculateCartTotals`, `getCartLinePricing`, `CartCost`):\n * passing 1999 yields \"$19.99\", not \"$1,999.00\". New code should call\n * `formatAmount({ amount, currencyCode })` from `@replohq/sdk/money` directly.\n */\nexport function formatPrice(price: number, currency: string = \"USD\"): string {\n return formatAmount({ amount: price, currencyCode: currency });\n}\n\n/**\n * Validates and formats a Shopify selling plan ID to ensure it has the correct GID format.\n *\n * @param sellingPlanId - The selling plan ID to validate and format\n * @returns The formatted selling plan ID with GID prefix, or null if no ID provided\n */\nexport function formatSellingPlanId(\n sellingPlanId: string | null | undefined,\n): string | null {\n if (!sellingPlanId) {\n return null;\n }\n\n // Check if it already has the gid format\n if (sellingPlanId.startsWith(\"gid://shopify/SellingPlan/\")) {\n return sellingPlanId;\n }\n\n // If it doesn't have the gid format, prepend it\n return `gid://shopify/SellingPlan/${sellingPlanId}`;\n}\n"],
5
- "mappings": "AAEA,SAAS,oBAAoB;AAE7B,SAAS,0BAA0B;AAc5B,SAAS,2BAA2B,WAA+B;AACxE,SAAO,UAAU;AAAA,IACf,CAAC,KAAK,SAAS,MAAM,mBAAmB,IAAI,EAAE,aAAa,KAAK;AAAA,IAChE;AAAA,EACF;AACF;AASO,SAAS,oBAAoB,MAAkB;AACpD,QAAM,WAAW,2BAA2B,KAAK,KAAK;AACtD,SAAO;AAAA,IACL,GAAG;AAAA,IACH,MAAM;AAAA,MACJ,GAAG,KAAK;AAAA,MACR,gBAAgB;AAAA,MAChB,aAAa;AAAA,IACf;AAAA,EACF;AACF;AAWO,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AACF,GAGe;AACb,QAAM,mBAAmB,2BAA2B,SAAS;AAC7D,QAAM,oBAAoB,UAAU,OAAO,CAAC,KAAK,SAAS;AACxD,WAAO,MAAM,mBAAmB,IAAI,EAAE,iBAAiB,KAAK;AAAA,EAC9D,GAAG,CAAC;AAOJ,QAAM,QAAQ,MAAM,KAAK,eAAe;AAGxC,QAAM,iBAAiB,MAAM,KAAK,kBAAkB;AACpD,QAAM,iBAAiB,iBAAiB;AAExC,QAAM,eAAe,oBAAoB;AAEzC,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAWO,SAAS,YAAY,OAAe,WAAmB,OAAe;AAC3E,SAAO,aAAa,EAAE,QAAQ,OAAO,cAAc,SAAS,CAAC;AAC/D;AAQO,SAAS,oBACd,eACe;AACf,MAAI,CAAC,eAAe;AAClB,WAAO;AAAA,EACT;AAGA,MAAI,cAAc,WAAW,4BAA4B,GAAG;AAC1D,WAAO;AAAA,EACT;AAGA,SAAO,6BAA6B,aAAa;AACnD;",
4
+ "sourcesContent": ["import type { Cart, CartLine } from \"../cart-types\";\n\nimport { formatAmount } from \"schemas/money\";\n\nimport { getCartLinePricing } from \"./variant-to-cart-line\";\n\nexport interface CartTotals {\n subtotal: number;\n discountAmount: number;\n totalSavings: number;\n total: number;\n}\n\n/**\n * Sum the final (post selling-plan) price of every line item in the cart.\n * This is the authoritative subtotal for cookie-based carts, which have no\n * server-computed cost to rely on.\n */\nexport function calculateLineItemsSubtotal(lineItems: CartLine[]): number {\n return lineItems.reduce(\n (sum, item) => sum + getCartLinePricing(item).finalPrice * item.quantity,\n 0,\n );\n}\n\n/**\n * Recompute a cart's cost from its line items. Cookie carts are their own source\n * of truth (no server roundtrip) and apply no discounts, so subtotal equals\n * total and there are no discount allocations. Without this the cost stays at\n * the 0 it was created with, and every consumer that trusts it (cart total,\n * checkout analytics) reports $0 for a priced cart.\n */\nexport function recalculateCartCost(cart: Cart): Cart {\n const subtotal = calculateLineItemsSubtotal(cart.lines);\n return {\n ...cart,\n cost: {\n ...cart.cost,\n subtotalAmount: subtotal,\n totalAmount: subtotal,\n },\n discountAllocations: [],\n };\n}\n\n/**\n * Calculate cart totals including subtotal, discounts, and final total.\n * When cart cost data is provided (from Shopify), uses the actual totalAmount\n * and sums the provider's discount allocations (automatic, code, and custom\n * discounts, at both line and cart level) for the discount amount.\n *\n * @param options - Object containing lineItems and cart\n * @param options.lineItems - Array of cart line items\n * @param options.cart - Optional cart data from Shopify (includes discount allocations)\n */\nexport function calculateCartTotals({\n lineItems,\n cart,\n}: {\n lineItems: CartLine[];\n cart: Cart | null;\n}): CartTotals {\n const lineItemSubtotal = calculateLineItemsSubtotal(lineItems);\n const compareAtSubtotal = lineItems.reduce((sum, item) => {\n return sum + getCartLinePricing(item).compareAtPrice * item.quantity;\n }, 0);\n\n // NOTE (Jasmine, 2026-07-22): Trust the provider's authoritative cost when\n // present. For Shopify this is the server-computed cart cost (including a\n // legitimate $0 when items aren't purchasable), which we must not\n // second-guess; cookie carts keep their cost truthful via\n // cookie-cart-persistence.ts.\n const total = cart?.cost.totalAmount ?? lineItemSubtotal;\n\n // Shopify folds line-level discounts into cost.subtotalAmount too, so the\n // cost gap can't be used; the allocations carry every applied discount.\n const discountAmount = (cart?.discountAllocations ?? []).reduce(\n (sum, allocation) => sum + allocation.discountedAmount,\n 0,\n );\n\n const totalSavings = compareAtSubtotal - total;\n\n return {\n subtotal: lineItemSubtotal,\n discountAmount,\n totalSavings,\n total,\n };\n}\n\n/**\n * @deprecated Back-compat shim kept for surface parity with canopy-sdk, so\n * sites migrating off `import { formatPrice } from\n * \"canopy-sdk/cart/utils/cart-utils\"` keep rendering with only the package\n * specifier renamed. It formats INTEGER MINOR UNITS \u2014 the units every cart\n * value uses (`calculateCartTotals`, `getCartLinePricing`, `CartCost`):\n * passing 1999 yields \"$19.99\", not \"$1,999.00\". New code should call\n * `formatAmount({ amount, currencyCode })` from `@replohq/sdk/money` directly.\n */\nexport function formatPrice(price: number, currency: string = \"USD\"): string {\n return formatAmount({ amount: price, currencyCode: currency });\n}\n\n/**\n * Validates and formats a Shopify selling plan ID to ensure it has the correct GID format.\n *\n * @param sellingPlanId - The selling plan ID to validate and format\n * @returns The formatted selling plan ID with GID prefix, or null if no ID provided\n */\nexport function formatSellingPlanId(\n sellingPlanId: string | null | undefined,\n): string | null {\n if (!sellingPlanId) {\n return null;\n }\n\n // Check if it already has the gid format\n if (sellingPlanId.startsWith(\"gid://shopify/SellingPlan/\")) {\n return sellingPlanId;\n }\n\n // If it doesn't have the gid format, prepend it\n return `gid://shopify/SellingPlan/${sellingPlanId}`;\n}\n"],
5
+ "mappings": "AAEA,SAAS,oBAAoB;AAE7B,SAAS,0BAA0B;AAc5B,SAAS,2BAA2B,WAA+B;AACxE,SAAO,UAAU;AAAA,IACf,CAAC,KAAK,SAAS,MAAM,mBAAmB,IAAI,EAAE,aAAa,KAAK;AAAA,IAChE;AAAA,EACF;AACF;AASO,SAAS,oBAAoB,MAAkB;AACpD,QAAM,WAAW,2BAA2B,KAAK,KAAK;AACtD,SAAO;AAAA,IACL,GAAG;AAAA,IACH,MAAM;AAAA,MACJ,GAAG,KAAK;AAAA,MACR,gBAAgB;AAAA,MAChB,aAAa;AAAA,IACf;AAAA,IACA,qBAAqB,CAAC;AAAA,EACxB;AACF;AAYO,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AACF,GAGe;AACb,QAAM,mBAAmB,2BAA2B,SAAS;AAC7D,QAAM,oBAAoB,UAAU,OAAO,CAAC,KAAK,SAAS;AACxD,WAAO,MAAM,mBAAmB,IAAI,EAAE,iBAAiB,KAAK;AAAA,EAC9D,GAAG,CAAC;AAOJ,QAAM,QAAQ,MAAM,KAAK,eAAe;AAIxC,QAAM,kBAAkB,MAAM,uBAAuB,CAAC,GAAG;AAAA,IACvD,CAAC,KAAK,eAAe,MAAM,WAAW;AAAA,IACtC;AAAA,EACF;AAEA,QAAM,eAAe,oBAAoB;AAEzC,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAWO,SAAS,YAAY,OAAe,WAAmB,OAAe;AAC3E,SAAO,aAAa,EAAE,QAAQ,OAAO,cAAc,SAAS,CAAC;AAC/D;AAQO,SAAS,oBACd,eACe;AACf,MAAI,CAAC,eAAe;AAClB,WAAO;AAAA,EACT;AAGA,MAAI,cAAc,WAAW,4BAA4B,GAAG;AAC1D,WAAO;AAAA,EACT;AAGA,SAAO,6BAA6B,aAAa;AACnD;",
6
6
  "names": []
7
7
  }
@@ -1,9 +1,9 @@
1
1
  import type { ScriptTagDescriptor } from "../_vendor/schemas/generated/consent";
2
2
  /**
3
3
  * Creates and appends the script nodes for `descriptors`, skipping node ids
4
- * already in the document, and returns the created nodes so the caller owns
5
- * their removal. Lives outside replo-scripts.tsx so the component file only
6
- * exports components (HMR) and this stays off the published public surface.
4
+ * already in the document, and returns only the nodes created by this call.
5
+ * Lives outside replo-scripts.tsx so the component file only exports components
6
+ * (HMR) and this stays off the published public surface.
7
7
  */
8
8
  export declare function injectScriptDescriptors({ baseId, descriptors, extraAttributes, }: {
9
9
  baseId: string;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../consent/inject-script-descriptors.ts"],
4
- "sourcesContent": ["import type { ScriptTagDescriptor } from \"schemas/generated/consent\";\n\n/**\n * Creates and appends the script nodes for `descriptors`, skipping node ids\n * already in the document, and returns the created nodes so the caller owns\n * their removal. Lives outside replo-scripts.tsx so the component file only\n * exports components (HMR) and this stays off the published public surface.\n */\nexport function injectScriptDescriptors({\n baseId,\n descriptors,\n extraAttributes,\n}: {\n baseId: string;\n descriptors: ScriptTagDescriptor[];\n /** Applied last, so a consent platform's `text/plain` beats a descriptor's own type. */\n extraAttributes?: Record<string, string>;\n}): HTMLScriptElement[] {\n const createdNodes: HTMLScriptElement[] = [];\n descriptors.forEach((descriptor, index) => {\n const nodeId = `${baseId}#${index}`;\n if (document.querySelector(`script[data-replo-script-id=\"${nodeId}\"]`)) {\n return;\n }\n const scriptElement = document.createElement(\"script\");\n scriptElement.dataset.reploScriptId = nodeId;\n if (descriptor.kind === \"external\") {\n scriptElement.src = descriptor.src;\n scriptElement.async = true;\n for (const [attributeName, attributeValue] of Object.entries(\n descriptor.attributes ?? {},\n )) {\n scriptElement.setAttribute(attributeName, attributeValue);\n }\n } else if (descriptor.module && extraAttributes?.type === \"text/plain\") {\n // A consent platform restores blocked tags as CLASSIC scripts, which is a\n // SyntaxError for module source. The blocked body is a classic shim that\n // re-creates the real module tag when the platform activates it.\n scriptElement.textContent = buildModuleShim(descriptor.body);\n } else {\n // Module snippets (top-level await, import()) are a SyntaxError as\n // classic scripts, so the type must survive injection.\n if (descriptor.module) {\n scriptElement.type = \"module\";\n }\n scriptElement.textContent = descriptor.body;\n }\n for (const [attributeName, attributeValue] of Object.entries(\n extraAttributes ?? {},\n )) {\n scriptElement.setAttribute(attributeName, attributeValue);\n }\n document.head.append(scriptElement);\n createdNodes.push(scriptElement);\n });\n return createdNodes;\n}\n\nfunction buildModuleShim(body: string): string {\n return [\n 'var moduleScript = document.createElement(\"script\");',\n 'moduleScript.type = \"module\";',\n `moduleScript.textContent = ${JSON.stringify(body)};`,\n \"document.head.append(moduleScript);\",\n ].join(\"\\n\");\n}\n"],
4
+ "sourcesContent": ["import type { ScriptTagDescriptor } from \"schemas/generated/consent\";\n\n/**\n * Creates and appends the script nodes for `descriptors`, skipping node ids\n * already in the document, and returns only the nodes created by this call.\n * Lives outside replo-scripts.tsx so the component file only exports components\n * (HMR) and this stays off the published public surface.\n */\nexport function injectScriptDescriptors({\n baseId,\n descriptors,\n extraAttributes,\n}: {\n baseId: string;\n descriptors: ScriptTagDescriptor[];\n /** Applied last, so a consent platform's `text/plain` beats a descriptor's own type. */\n extraAttributes?: Record<string, string>;\n}): HTMLScriptElement[] {\n const createdNodes: HTMLScriptElement[] = [];\n descriptors.forEach((descriptor, index) => {\n const nodeId = `${baseId}#${index}`;\n if (document.querySelector(`script[data-replo-script-id=\"${nodeId}\"]`)) {\n return;\n }\n const scriptElement = document.createElement(\"script\");\n scriptElement.dataset.reploScriptId = nodeId;\n if (descriptor.kind === \"external\") {\n scriptElement.src = descriptor.src;\n scriptElement.async = true;\n for (const [attributeName, attributeValue] of Object.entries(\n descriptor.attributes ?? {},\n )) {\n scriptElement.setAttribute(attributeName, attributeValue);\n }\n } else if (descriptor.module && extraAttributes?.type === \"text/plain\") {\n // A consent platform restores blocked tags as CLASSIC scripts, which is a\n // SyntaxError for module source. The blocked body is a classic shim that\n // re-creates the real module tag when the platform activates it.\n scriptElement.textContent = buildModuleShim(descriptor.body);\n } else {\n // Module snippets (top-level await, import()) are a SyntaxError as\n // classic scripts, so the type must survive injection.\n if (descriptor.module) {\n scriptElement.type = \"module\";\n }\n scriptElement.textContent = descriptor.body;\n }\n for (const [attributeName, attributeValue] of Object.entries(\n extraAttributes ?? {},\n )) {\n scriptElement.setAttribute(attributeName, attributeValue);\n }\n document.head.append(scriptElement);\n createdNodes.push(scriptElement);\n });\n return createdNodes;\n}\n\nfunction buildModuleShim(body: string): string {\n return [\n 'var moduleScript = document.createElement(\"script\");',\n 'moduleScript.type = \"module\";',\n `moduleScript.textContent = ${JSON.stringify(body)};`,\n \"document.head.append(moduleScript);\",\n ].join(\"\\n\");\n}\n"],
5
5
  "mappings": "AAQO,SAAS,wBAAwB;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AACF,GAKwB;AACtB,QAAM,eAAoC,CAAC;AAC3C,cAAY,QAAQ,CAAC,YAAY,UAAU;AACzC,UAAM,SAAS,GAAG,MAAM,IAAI,KAAK;AACjC,QAAI,SAAS,cAAc,gCAAgC,MAAM,IAAI,GAAG;AACtE;AAAA,IACF;AACA,UAAM,gBAAgB,SAAS,cAAc,QAAQ;AACrD,kBAAc,QAAQ,gBAAgB;AACtC,QAAI,WAAW,SAAS,YAAY;AAClC,oBAAc,MAAM,WAAW;AAC/B,oBAAc,QAAQ;AACtB,iBAAW,CAAC,eAAe,cAAc,KAAK,OAAO;AAAA,QACnD,WAAW,cAAc,CAAC;AAAA,MAC5B,GAAG;AACD,sBAAc,aAAa,eAAe,cAAc;AAAA,MAC1D;AAAA,IACF,WAAW,WAAW,UAAU,iBAAiB,SAAS,cAAc;AAItE,oBAAc,cAAc,gBAAgB,WAAW,IAAI;AAAA,IAC7D,OAAO;AAGL,UAAI,WAAW,QAAQ;AACrB,sBAAc,OAAO;AAAA,MACvB;AACA,oBAAc,cAAc,WAAW;AAAA,IACzC;AACA,eAAW,CAAC,eAAe,cAAc,KAAK,OAAO;AAAA,MACnD,mBAAmB,CAAC;AAAA,IACtB,GAAG;AACD,oBAAc,aAAa,eAAe,cAAc;AAAA,IAC1D;AACA,aAAS,KAAK,OAAO,aAAa;AAClC,iBAAa,KAAK,aAAa;AAAA,EACjC,CAAC;AACD,SAAO;AACT;AAEA,SAAS,gBAAgB,MAAsB;AAC7C,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,8BAA8B,KAAK,UAAU,IAAI,CAAC;AAAA,IAClD;AAAA,EACF,EAAE,KAAK,IAAI;AACb;",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,5 @@
1
1
  import type { ConsentCategory, ReploScriptEntry, ScriptTagDescriptor } from "../_vendor/schemas/generated/consent";
2
+ import { Component } from "react";
2
3
  /**
3
4
  * Injects the resolved tags into `document.head` once a caller has decided
4
5
  * consent allows it. Reused both for author-managed `ReploScripts` entries and
@@ -9,12 +10,16 @@ import type { ConsentCategory, ReploScriptEntry, ScriptTagDescriptor } from "../
9
10
  * `data-replo-script-id` (plus a per-tag index) so re-renders and React Strict
10
11
  * Mode double-invokes do not double-inject.
11
12
  */
12
- export declare function InjectedScript({ baseId, descriptors, requiredConsent, }: {
13
+ type InjectedScriptProps = {
13
14
  baseId: string;
14
15
  descriptors: ScriptTagDescriptor[];
15
16
  /** Used under a delegated platform to derive the attributes that gate the tag. */
16
17
  requiredConsent?: ConsentCategory[];
17
- }): null;
18
+ };
19
+ export declare class InjectedScript extends Component<InjectedScriptProps> {
20
+ componentDidMount(): void;
21
+ render(): null;
22
+ }
18
23
  /**
19
24
  * The single registry component for all managed tracking scripts on the site.
20
25
  * Authored once in `app/layout.tsx`; the agent/miniapp only edit the `scripts`
@@ -28,3 +33,4 @@ export declare function InjectedScript({ baseId, descriptors, requiredConsent, }
28
33
  export declare function ReploScripts({ scripts }: {
29
34
  scripts: ReploScriptEntry[];
30
35
  }): import("react/jsx-runtime").JSX.Element;
36
+ export {};