@replohq/sdk 1.18.0 → 1.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -25,8 +25,8 @@ export declare const localeRoutingConfigSchema: z.ZodObject<{
25
25
  language: "language";
26
26
  }>>;
27
27
  urlMode: z.ZodDefault<z.ZodEnum<{
28
- prefixed: "prefixed";
29
28
  hidden: "hidden";
29
+ prefixed: "prefixed";
30
30
  }>>;
31
31
  domains: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
32
32
  locales: z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
@@ -0,0 +1,65 @@
1
+ import React from "react";
2
+ import { z } from "zod";
3
+ import { CanopyError } from "../lib/canopy-error";
4
+ declare const mailchimpSubscribeErrorCodeSchema: z.ZodEnum<{
5
+ notConnected: "notConnected";
6
+ listNotFound: "listNotFound";
7
+ invalidEmail: "invalidEmail";
8
+ invalidRequest: "invalidRequest";
9
+ emailNotSubscribable: "emailNotSubscribable";
10
+ mergeFieldsInvalid: "mergeFieldsInvalid";
11
+ rateLimited: "rateLimited";
12
+ upstreamError: "upstreamError";
13
+ }>;
14
+ /** `pending` means Mailchimp emailed a confirmation link the visitor must click. */
15
+ export type MailchimpSubscribeStatus = "subscribed" | "pending";
16
+ export type MailchimpSubscribeErrorCode = z.infer<typeof mailchimpSubscribeErrorCodeSchema>;
17
+ export declare class MailchimpSubscribeError extends CanopyError {
18
+ readonly code: MailchimpSubscribeErrorCode;
19
+ constructor({ code, message, cause, }: {
20
+ code: MailchimpSubscribeErrorCode;
21
+ message: string;
22
+ cause: unknown;
23
+ });
24
+ }
25
+ /**
26
+ * Adds an opted-in email to a Mailchimp audience from a published Replo form.
27
+ * Call it in a client component under `ReploProvider`, render
28
+ * `<input {...honeypotFieldProps} />` inside the form, and submit with
29
+ * `subscribe.mutate({ email, mergeFields, interests })`. The project id comes
30
+ * from the site, not the caller.
31
+ *
32
+ * `tags` label everyone this form adds (e.g. `["waitlist-spring"]`), `event`
33
+ * records a custom event that can start a Mailchimp Customer Journey, and
34
+ * `interests` opts the visitor into audience groups by id. All three apply
35
+ * only to people new to the audience.
36
+ *
37
+ * `subscribe.error` is a `MailchimpSubscribeError`; branch on its `code`.
38
+ */
39
+ export declare function useMailchimpSubscribe({ listId, tags, event, }: {
40
+ listId: string;
41
+ tags: string[] | null;
42
+ event: string | null;
43
+ }): {
44
+ subscribe: import("@tanstack/react-query").UseMutationResult<MailchimpSubscribeStatus, MailchimpSubscribeError, {
45
+ email: string;
46
+ mergeFields: Record<string, string> | null;
47
+ interests: Record<string, boolean> | null;
48
+ }, unknown>;
49
+ honeypotFieldProps: {
50
+ name: string;
51
+ value: string;
52
+ onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
53
+ tabIndex: number;
54
+ autoComplete: "off";
55
+ "aria-hidden": true;
56
+ style: {
57
+ position: "absolute";
58
+ left: string;
59
+ width: string;
60
+ height: string;
61
+ overflow: "hidden";
62
+ };
63
+ };
64
+ };
65
+ export {};
@@ -0,0 +1,133 @@
1
+ "use client";
2
+ import React from "react";
3
+ import { useMutation } from "@tanstack/react-query";
4
+ import { parseJsonOrNull } from "../_vendor/replo-utils/lib/json.mjs";
5
+ import { z } from "zod";
6
+ import { CanopyError } from "../lib/canopy-error";
7
+ import { useCanopyClientConfig } from "../providers/canopy-client-config";
8
+ const mailchimpSubscribeErrorCodeSchema = z.enum([
9
+ "notConnected",
10
+ "listNotFound",
11
+ "invalidEmail",
12
+ "invalidRequest",
13
+ "emailNotSubscribable",
14
+ "mergeFieldsInvalid",
15
+ "rateLimited",
16
+ "upstreamError"
17
+ ]);
18
+ const mailchimpSubscribeResponseSchema = z.discriminatedUnion("ok", [
19
+ z.object({
20
+ ok: z.literal(true),
21
+ status: z.enum(["subscribed", "pending"])
22
+ }),
23
+ z.object({
24
+ ok: z.literal(false),
25
+ error: z.object({
26
+ code: mailchimpSubscribeErrorCodeSchema,
27
+ message: z.string()
28
+ })
29
+ })
30
+ ]);
31
+ class MailchimpSubscribeError extends CanopyError {
32
+ code;
33
+ constructor({
34
+ code,
35
+ message,
36
+ cause
37
+ }) {
38
+ super({ message, cause, additionalData: { code } });
39
+ this.code = code;
40
+ }
41
+ }
42
+ async function postSubscribe({
43
+ config,
44
+ request
45
+ }) {
46
+ if (!config) {
47
+ throw new MailchimpSubscribeError({
48
+ code: "upstreamError",
49
+ message: "This site has no canopy-api config, so it cannot reach Mailchimp",
50
+ cause: null
51
+ });
52
+ }
53
+ let responseText;
54
+ try {
55
+ const response = await fetch(
56
+ `${config.canopyApiBaseUrl}/api/v1/integrations/mailchimp/subscribe`,
57
+ {
58
+ method: "POST",
59
+ headers: { "Content-Type": "application/json" },
60
+ body: JSON.stringify({ projectId: config.projectId, ...request })
61
+ }
62
+ );
63
+ responseText = await response.text();
64
+ } catch (error) {
65
+ throw new MailchimpSubscribeError({
66
+ code: "upstreamError",
67
+ message: "Could not reach the Mailchimp subscribe endpoint",
68
+ cause: error
69
+ });
70
+ }
71
+ const parsed = mailchimpSubscribeResponseSchema.safeParse(
72
+ parseJsonOrNull(responseText)
73
+ );
74
+ if (!parsed.success) {
75
+ throw new MailchimpSubscribeError({
76
+ code: "upstreamError",
77
+ message: "The Mailchimp subscribe endpoint returned an unexpected response",
78
+ cause: parsed.error
79
+ });
80
+ }
81
+ if (!parsed.data.ok) {
82
+ throw new MailchimpSubscribeError({ ...parsed.data.error, cause: null });
83
+ }
84
+ return parsed.data.status;
85
+ }
86
+ function useMailchimpSubscribe({
87
+ listId,
88
+ tags,
89
+ event
90
+ }) {
91
+ const config = useCanopyClientConfig();
92
+ const [renderedAtMilliseconds] = React.useState(() => Date.now());
93
+ const [honeypot, setHoneypot] = React.useState("");
94
+ const subscribe = useMutation({
95
+ mutationFn: ({ email, mergeFields, interests }) => {
96
+ return postSubscribe({
97
+ config,
98
+ request: {
99
+ listId,
100
+ email,
101
+ mergeFields,
102
+ tags,
103
+ interests,
104
+ event,
105
+ honeypot,
106
+ elapsedMilliseconds: Date.now() - renderedAtMilliseconds
107
+ }
108
+ });
109
+ }
110
+ });
111
+ const honeypotFieldProps = {
112
+ name: "company_website",
113
+ value: honeypot,
114
+ onChange: (event2) => {
115
+ setHoneypot(event2.target.value);
116
+ },
117
+ tabIndex: -1,
118
+ autoComplete: "off",
119
+ "aria-hidden": true,
120
+ style: {
121
+ position: "absolute",
122
+ left: "-10000px",
123
+ width: "1px",
124
+ height: "1px",
125
+ overflow: "hidden"
126
+ }
127
+ };
128
+ return { subscribe, honeypotFieldProps };
129
+ }
130
+ export {
131
+ MailchimpSubscribeError,
132
+ useMailchimpSubscribe
133
+ };
@@ -1,7 +1,7 @@
1
1
  export const BUILD_METADATA = {
2
2
  "packageName": "@replohq/sdk",
3
- "version": "1.18.0",
3
+ "version": "1.19.0",
4
4
  "branch": "HEAD",
5
- "commit": "038932591fc729ca2f66cb29af15af8573ff9fd0",
6
- "builtAt": "2026-10-14T19:30:38.623Z"
5
+ "commit": "e3b248dbf12d7b2d1f401d36170686e094d40e3a",
6
+ "builtAt": "2026-10-14T22:19:29.297Z"
7
7
  };
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@replohq/sdk",
3
- "version": "1.18.0",
3
+ "version": "1.19.0",
4
4
  "reploBuild": {
5
5
  "packageName": "@replohq/sdk",
6
- "version": "1.18.0",
6
+ "version": "1.19.0",
7
7
  "branch": "HEAD",
8
- "commit": "038932591fc729ca2f66cb29af15af8573ff9fd0",
9
- "builtAt": "2026-10-14T19:30:38.623Z"
8
+ "commit": "e3b248dbf12d7b2d1f401d36170686e094d40e3a",
9
+ "builtAt": "2026-10-14T22:19:29.297Z"
10
10
  },
11
11
  "description": "Replo SDK — cart, analytics, and data loaders for agent-built Next.js sites.",
12
12
  "license": "SEE LICENSE IN LICENSE",
@@ -77,6 +77,10 @@
77
77
  "types": "./hooks/use-formatted-price.d.ts",
78
78
  "default": "./hooks/use-formatted-price.js"
79
79
  },
80
+ "./integrations/mailchimp": {
81
+ "types": "./integrations/mailchimp.d.ts",
82
+ "default": "./integrations/mailchimp.js"
83
+ },
80
84
  "./lib/buildMetadata": {
81
85
  "types": "./lib/buildMetadata.d.ts",
82
86
  "default": "./lib/buildMetadata.js"
@@ -0,0 +1,14 @@
1
+ import React from "react";
2
+ /**
3
+ * What browser code needs to call canopy-api directly. Resolved from the worker
4
+ * env by `ReploProvider`; null when the env can't be read (e.g. outside the
5
+ * worker), in which case browser calls fail with a typed error.
6
+ */
7
+ export type CanopyClientConfig = {
8
+ canopyApiBaseUrl: string;
9
+ projectId: string;
10
+ };
11
+ export declare function CanopyClientConfigProvider({ config, children, }: React.PropsWithChildren<{
12
+ config: CanopyClientConfig | null;
13
+ }>): import("react/jsx-runtime").JSX.Element;
14
+ export declare function useCanopyClientConfig(): CanopyClientConfig | null;
@@ -0,0 +1,17 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import React from "react";
4
+ const CanopyClientConfigContext = React.createContext(null);
5
+ function CanopyClientConfigProvider({
6
+ config,
7
+ children
8
+ }) {
9
+ return /* @__PURE__ */ jsx(CanopyClientConfigContext.Provider, { value: config, children });
10
+ }
11
+ function useCanopyClientConfig() {
12
+ return React.useContext(CanopyClientConfigContext);
13
+ }
14
+ export {
15
+ CanopyClientConfigProvider,
16
+ useCanopyClientConfig
17
+ };
@@ -2,21 +2,29 @@ import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { AnalyticsProvider } from "../analytics/analytics-provider";
3
3
  import { ReploFirstPartyPixel } from "../analytics/replo-first-party-pixel";
4
4
  import { CartLoader } from "../cart/cart-loader";
5
- import { getCheckoutProvider } from "../lib/integration-utils";
5
+ import { getEnv } from "../env";
6
+ import { buildBaseUrl } from "../lib/url-utils";
6
7
  import { LoaderErrorDetailsProvider } from "../loaders/loader-error-boundary";
7
8
  import { shouldShowLoaderErrorDetails } from "../loaders/loader-error-details";
9
+ import { CanopyClientConfigProvider } from "./canopy-client-config";
8
10
  import { QueryProvider } from "./query-provider";
9
11
  async function ReploProvider({
10
12
  children,
11
13
  markets
12
14
  }) {
13
15
  let checkoutProvider = "none";
16
+ let canopyClientConfig = null;
14
17
  try {
15
- checkoutProvider = await getCheckoutProvider();
18
+ const env = await getEnv();
19
+ checkoutProvider = env.CHECKOUT_PROVIDER ?? "none";
20
+ canopyClientConfig = {
21
+ canopyApiBaseUrl: buildBaseUrl(env.CANOPY_API_HOST),
22
+ projectId: env.PROJECT_ID
23
+ };
16
24
  } catch {
17
25
  checkoutProvider = "none";
18
26
  }
19
- return /* @__PURE__ */ jsx(QueryProvider, { children: /* @__PURE__ */ jsx(LoaderErrorDetailsProvider, { enabled: shouldShowLoaderErrorDetails(), children: /* @__PURE__ */ jsxs(AnalyticsProvider, { checkoutProvider, children: [
27
+ return /* @__PURE__ */ jsx(QueryProvider, { children: /* @__PURE__ */ jsx(CanopyClientConfigProvider, { config: canopyClientConfig, children: /* @__PURE__ */ jsx(LoaderErrorDetailsProvider, { enabled: shouldShowLoaderErrorDetails(), children: /* @__PURE__ */ jsxs(AnalyticsProvider, { checkoutProvider, children: [
20
28
  /* @__PURE__ */ jsx(ReploFirstPartyPixel, {}),
21
29
  /* @__PURE__ */ jsx(
22
30
  CartLoader,
@@ -26,7 +34,7 @@ async function ReploProvider({
26
34
  children
27
35
  }
28
36
  )
29
- ] }) }) });
37
+ ] }) }) }) });
30
38
  }
31
39
  export {
32
40
  ReploProvider