@neowhale/storefront 0.2.57 → 0.2.59

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.
@@ -567,6 +567,8 @@ var WhaleStorefront = (function (exports) {
567
567
  onSubmit: handleSubmit,
568
568
  successHeading: c.inline_form.success_heading || "you're in.",
569
569
  successMessage: c.inline_form.success_message || "check your inbox.",
570
+ successCtaText: c.inline_form.success_cta_text,
571
+ successCtaUrl: c.inline_form.success_cta_url,
570
572
  submitText: c.inline_form.button_text || "send my code",
571
573
  theme,
572
574
  onEvent,
@@ -598,12 +600,36 @@ var WhaleStorefront = (function (exports) {
598
600
  ] })
599
601
  ] });
600
602
  }
601
- function HeroInlineForm({ ctaText, formOpen, setFormOpen, firstName, setFirstName, email, setEmail, status, errorMsg, onSubmit, successHeading, successMessage, submitText, theme, onEvent, tracking }) {
603
+ function HeroInlineForm({ ctaText, formOpen, setFormOpen, firstName, setFirstName, email, setEmail, status, errorMsg, onSubmit, successHeading, successMessage, successCtaText, successCtaUrl, submitText, theme, onEvent, tracking }) {
602
604
  const formMaxW = "min(480px, 90vw)";
603
605
  if (status === "success") {
604
606
  return /* @__PURE__ */ jsx("div", { style: { maxWidth: formMaxW, margin: "0 auto", padding: "1.5rem 2rem", background: `${theme.fg}06`, border: `1px solid ${theme.fg}10` }, children: [
605
607
  /* @__PURE__ */ jsx("p", { style: { fontSize: "clamp(1rem, 3vw, 1.25rem)", fontWeight: 300, color: theme.fg, margin: "0 0 0.375rem", fontFamily: theme.fontDisplay || "inherit" }, children: successHeading }),
606
- /* @__PURE__ */ jsx("p", { style: { fontSize: "0.85rem", color: `${theme.fg}70`, margin: 0, lineHeight: 1.5 }, children: successMessage })
608
+ /* @__PURE__ */ jsx("p", { style: { fontSize: "0.85rem", color: `${theme.fg}70`, margin: 0, lineHeight: 1.5 }, children: successMessage }),
609
+ successCtaUrl && /* @__PURE__ */ jsx(
610
+ "button",
611
+ {
612
+ onClick: () => {
613
+ window.location.href = successCtaUrl;
614
+ onEvent?.("cta_click", { label: successCtaText || "shop now", url: successCtaUrl });
615
+ },
616
+ style: {
617
+ marginTop: "1rem",
618
+ width: "100%",
619
+ padding: "0.875rem",
620
+ background: theme.accent || theme.fg,
621
+ color: "#fff",
622
+ border: "none",
623
+ fontSize: "0.85rem",
624
+ fontWeight: 500,
625
+ cursor: "pointer",
626
+ letterSpacing: "0.08em",
627
+ textTransform: "uppercase",
628
+ fontFamily: "inherit"
629
+ },
630
+ children: successCtaText || "shop now"
631
+ }
632
+ )
607
633
  ] });
608
634
  }
609
635
  if (!formOpen) {
@@ -1130,6 +1156,25 @@ var WhaleStorefront = (function (exports) {
1130
1156
  }
1131
1157
 
1132
1158
  // src/react/components/sections/lead-capture-section.tsx
1159
+ var TURNSTILE_SITE_KEY = "0x4AAAAAACwmUPgmyfw6pWfT";
1160
+ var TURNSTILE_SCRIPT_URL = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
1161
+ var turnstilePromise = null;
1162
+ function loadTurnstileScript() {
1163
+ if (turnstilePromise) return turnstilePromise;
1164
+ if (typeof window !== "undefined" && window.turnstile) {
1165
+ turnstilePromise = Promise.resolve();
1166
+ return turnstilePromise;
1167
+ }
1168
+ turnstilePromise = new Promise((resolve, reject) => {
1169
+ const script = document.createElement("script");
1170
+ script.src = TURNSTILE_SCRIPT_URL;
1171
+ script.async = true;
1172
+ script.onload = () => resolve();
1173
+ script.onerror = () => reject(new Error("Failed to load Turnstile script"));
1174
+ document.head.appendChild(script);
1175
+ });
1176
+ return turnstilePromise;
1177
+ }
1133
1178
  function LeadCaptureSection({ section, data, theme, onEvent }) {
1134
1179
  const c = section.content;
1135
1180
  const [firstName, setFirstName] = useState("");
@@ -1138,6 +1183,41 @@ var WhaleStorefront = (function (exports) {
1138
1183
  const [status, setStatus] = useState("idle");
1139
1184
  const [errorMsg, setErrorMsg] = useState("");
1140
1185
  const [serverMessage, setServerMessage] = useState(null);
1186
+ const turnstileRef = useRef(null);
1187
+ const turnstileToken = useRef(null);
1188
+ const turnstileWidgetId = useRef(null);
1189
+ const pendingSubmit = useRef(null);
1190
+ const onTurnstileToken = useCallback((token) => {
1191
+ turnstileToken.current = token;
1192
+ if (pendingSubmit.current) {
1193
+ const cb = pendingSubmit.current;
1194
+ pendingSubmit.current = null;
1195
+ cb();
1196
+ }
1197
+ }, []);
1198
+ useEffect(() => {
1199
+ if (typeof window === "undefined") return;
1200
+ let widgetId = null;
1201
+ loadTurnstileScript().then(() => {
1202
+ const el = turnstileRef.current;
1203
+ if (!el || !window.turnstile) return;
1204
+ widgetId = window.turnstile.render(el, {
1205
+ sitekey: TURNSTILE_SITE_KEY,
1206
+ callback: onTurnstileToken,
1207
+ "expired-callback": () => {
1208
+ turnstileToken.current = null;
1209
+ },
1210
+ size: "invisible"
1211
+ });
1212
+ turnstileWidgetId.current = widgetId;
1213
+ }).catch(() => {
1214
+ });
1215
+ return () => {
1216
+ if (widgetId != null && window.turnstile) {
1217
+ window.turnstile.remove(widgetId);
1218
+ }
1219
+ };
1220
+ }, [onTurnstileToken]);
1141
1221
  const gatewayUrl = c.gateway_url || data.gatewayUrl || "https://whale-gateway.fly.dev";
1142
1222
  const storeId = c.store_id || data.store?.id;
1143
1223
  const slug = c.landing_page_slug || data.landing_page?.slug;
@@ -1146,11 +1226,7 @@ var WhaleStorefront = (function (exports) {
1146
1226
  const buttonText = c.button_text || "Claim My Discount";
1147
1227
  const successHeading = c.success_heading || "You\u2019re in!";
1148
1228
  const successMessage = c.success_message || "Check your inbox for the discount code.";
1149
- async function handleSubmit(e) {
1150
- e.preventDefault();
1151
- if (!email || !storeId) return;
1152
- setStatus("loading");
1153
- setErrorMsg("");
1229
+ async function submitLead(cfToken) {
1154
1230
  const urlParams = typeof window !== "undefined" ? new URLSearchParams(window.location.search) : null;
1155
1231
  try {
1156
1232
  const res = await fetch(`${gatewayUrl}/v1/stores/${storeId}/storefront/leads`, {
@@ -1172,7 +1248,8 @@ var WhaleStorefront = (function (exports) {
1172
1248
  utm_source: urlParams?.get("utm_source") || void 0,
1173
1249
  utm_medium: urlParams?.get("utm_medium") || void 0,
1174
1250
  utm_campaign: urlParams?.get("utm_campaign") || void 0,
1175
- utm_content: urlParams?.get("utm_content") || void 0
1251
+ utm_content: urlParams?.get("utm_content") || void 0,
1252
+ cf_turnstile_response: cfToken || void 0
1176
1253
  })
1177
1254
  });
1178
1255
  if (!res.ok) {
@@ -1193,6 +1270,22 @@ var WhaleStorefront = (function (exports) {
1193
1270
  setStatus("error");
1194
1271
  }
1195
1272
  }
1273
+ async function handleSubmit(e) {
1274
+ e.preventDefault();
1275
+ if (!email || !storeId) return;
1276
+ setStatus("loading");
1277
+ setErrorMsg("");
1278
+ if (turnstileToken.current) {
1279
+ return submitLead(turnstileToken.current);
1280
+ }
1281
+ const turnstile = window.turnstile;
1282
+ if (turnstile && turnstileWidgetId.current != null) {
1283
+ pendingSubmit.current = () => submitLead(turnstileToken.current);
1284
+ turnstile.execute(turnstileWidgetId.current);
1285
+ } else {
1286
+ return submitLead(null);
1287
+ }
1288
+ }
1196
1289
  const inputStyle = {
1197
1290
  flex: 1,
1198
1291
  minWidth: 0,
@@ -1286,7 +1379,8 @@ var WhaleStorefront = (function (exports) {
1286
1379
  animation: "lc-spin 0.8s linear infinite"
1287
1380
  } }),
1288
1381
  buttonText
1289
- ] })
1382
+ ] }),
1383
+ /* @__PURE__ */ jsx("div", { ref: turnstileRef, style: { display: "none" } })
1290
1384
  ] })
1291
1385
  ] })
1292
1386
  ] });
@@ -2306,6 +2306,8 @@ function HeroSection({ section, theme, tracking, onEvent, data }) {
2306
2306
  onSubmit: handleSubmit,
2307
2307
  successHeading: c.inline_form.success_heading || "you're in.",
2308
2308
  successMessage: c.inline_form.success_message || "check your inbox.",
2309
+ successCtaText: c.inline_form.success_cta_text,
2310
+ successCtaUrl: c.inline_form.success_cta_url,
2309
2311
  submitText: c.inline_form.button_text || "send my code",
2310
2312
  theme,
2311
2313
  onEvent,
@@ -2337,12 +2339,36 @@ function HeroSection({ section, theme, tracking, onEvent, data }) {
2337
2339
  ] })
2338
2340
  ] });
2339
2341
  }
2340
- function HeroInlineForm({ ctaText, formOpen, setFormOpen, firstName, setFirstName, email, setEmail, status, errorMsg, onSubmit, successHeading, successMessage, submitText, theme, onEvent, tracking }) {
2342
+ function HeroInlineForm({ ctaText, formOpen, setFormOpen, firstName, setFirstName, email, setEmail, status, errorMsg, onSubmit, successHeading, successMessage, successCtaText, successCtaUrl, submitText, theme, onEvent, tracking }) {
2341
2343
  const formMaxW = "min(480px, 90vw)";
2342
2344
  if (status === "success") {
2343
2345
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { maxWidth: formMaxW, margin: "0 auto", padding: "1.5rem 2rem", background: `${theme.fg}06`, border: `1px solid ${theme.fg}10` }, children: [
2344
2346
  /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "clamp(1rem, 3vw, 1.25rem)", fontWeight: 300, color: theme.fg, margin: "0 0 0.375rem", fontFamily: theme.fontDisplay || "inherit" }, children: successHeading }),
2345
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "0.85rem", color: `${theme.fg}70`, margin: 0, lineHeight: 1.5 }, children: successMessage })
2347
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "0.85rem", color: `${theme.fg}70`, margin: 0, lineHeight: 1.5 }, children: successMessage }),
2348
+ successCtaUrl && /* @__PURE__ */ jsxRuntime.jsx(
2349
+ "button",
2350
+ {
2351
+ onClick: () => {
2352
+ window.location.href = successCtaUrl;
2353
+ onEvent?.("cta_click", { label: successCtaText || "shop now", url: successCtaUrl });
2354
+ },
2355
+ style: {
2356
+ marginTop: "1rem",
2357
+ width: "100%",
2358
+ padding: "0.875rem",
2359
+ background: theme.accent || theme.fg,
2360
+ color: "#fff",
2361
+ border: "none",
2362
+ fontSize: "0.85rem",
2363
+ fontWeight: 500,
2364
+ cursor: "pointer",
2365
+ letterSpacing: "0.08em",
2366
+ textTransform: "uppercase",
2367
+ fontFamily: "inherit"
2368
+ },
2369
+ children: successCtaText || "shop now"
2370
+ }
2371
+ )
2346
2372
  ] });
2347
2373
  }
2348
2374
  if (!formOpen) {
@@ -2865,6 +2891,25 @@ function COAModal({ coa, theme, onClose }) {
2865
2891
  /* @__PURE__ */ jsxRuntime.jsx("iframe", { src: coa.url, style: { flex: 1, border: "none", background: "#fff" }, title: "Lab Results" })
2866
2892
  ] });
2867
2893
  }
2894
+ var TURNSTILE_SITE_KEY = "0x4AAAAAACwmUPgmyfw6pWfT";
2895
+ var TURNSTILE_SCRIPT_URL = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
2896
+ var turnstilePromise = null;
2897
+ function loadTurnstileScript() {
2898
+ if (turnstilePromise) return turnstilePromise;
2899
+ if (typeof window !== "undefined" && window.turnstile) {
2900
+ turnstilePromise = Promise.resolve();
2901
+ return turnstilePromise;
2902
+ }
2903
+ turnstilePromise = new Promise((resolve, reject) => {
2904
+ const script = document.createElement("script");
2905
+ script.src = TURNSTILE_SCRIPT_URL;
2906
+ script.async = true;
2907
+ script.onload = () => resolve();
2908
+ script.onerror = () => reject(new Error("Failed to load Turnstile script"));
2909
+ document.head.appendChild(script);
2910
+ });
2911
+ return turnstilePromise;
2912
+ }
2868
2913
  function LeadCaptureSection({ section, data, theme, onEvent }) {
2869
2914
  const c = section.content;
2870
2915
  const [firstName, setFirstName] = react.useState("");
@@ -2873,6 +2918,41 @@ function LeadCaptureSection({ section, data, theme, onEvent }) {
2873
2918
  const [status, setStatus] = react.useState("idle");
2874
2919
  const [errorMsg, setErrorMsg] = react.useState("");
2875
2920
  const [serverMessage, setServerMessage] = react.useState(null);
2921
+ const turnstileRef = react.useRef(null);
2922
+ const turnstileToken = react.useRef(null);
2923
+ const turnstileWidgetId = react.useRef(null);
2924
+ const pendingSubmit = react.useRef(null);
2925
+ const onTurnstileToken = react.useCallback((token) => {
2926
+ turnstileToken.current = token;
2927
+ if (pendingSubmit.current) {
2928
+ const cb = pendingSubmit.current;
2929
+ pendingSubmit.current = null;
2930
+ cb();
2931
+ }
2932
+ }, []);
2933
+ react.useEffect(() => {
2934
+ if (typeof window === "undefined") return;
2935
+ let widgetId = null;
2936
+ loadTurnstileScript().then(() => {
2937
+ const el = turnstileRef.current;
2938
+ if (!el || !window.turnstile) return;
2939
+ widgetId = window.turnstile.render(el, {
2940
+ sitekey: TURNSTILE_SITE_KEY,
2941
+ callback: onTurnstileToken,
2942
+ "expired-callback": () => {
2943
+ turnstileToken.current = null;
2944
+ },
2945
+ size: "invisible"
2946
+ });
2947
+ turnstileWidgetId.current = widgetId;
2948
+ }).catch(() => {
2949
+ });
2950
+ return () => {
2951
+ if (widgetId != null && window.turnstile) {
2952
+ window.turnstile.remove(widgetId);
2953
+ }
2954
+ };
2955
+ }, [onTurnstileToken]);
2876
2956
  const gatewayUrl = c.gateway_url || data.gatewayUrl || "https://whale-gateway.fly.dev";
2877
2957
  const storeId = c.store_id || data.store?.id;
2878
2958
  const slug = c.landing_page_slug || data.landing_page?.slug;
@@ -2881,11 +2961,7 @@ function LeadCaptureSection({ section, data, theme, onEvent }) {
2881
2961
  const buttonText = c.button_text || "Claim My Discount";
2882
2962
  const successHeading = c.success_heading || "You\u2019re in!";
2883
2963
  const successMessage = c.success_message || "Check your inbox for the discount code.";
2884
- async function handleSubmit(e) {
2885
- e.preventDefault();
2886
- if (!email || !storeId) return;
2887
- setStatus("loading");
2888
- setErrorMsg("");
2964
+ async function submitLead(cfToken) {
2889
2965
  const urlParams = typeof window !== "undefined" ? new URLSearchParams(window.location.search) : null;
2890
2966
  try {
2891
2967
  const res = await fetch(`${gatewayUrl}/v1/stores/${storeId}/storefront/leads`, {
@@ -2907,7 +2983,8 @@ function LeadCaptureSection({ section, data, theme, onEvent }) {
2907
2983
  utm_source: urlParams?.get("utm_source") || void 0,
2908
2984
  utm_medium: urlParams?.get("utm_medium") || void 0,
2909
2985
  utm_campaign: urlParams?.get("utm_campaign") || void 0,
2910
- utm_content: urlParams?.get("utm_content") || void 0
2986
+ utm_content: urlParams?.get("utm_content") || void 0,
2987
+ cf_turnstile_response: cfToken || void 0
2911
2988
  })
2912
2989
  });
2913
2990
  if (!res.ok) {
@@ -2928,6 +3005,22 @@ function LeadCaptureSection({ section, data, theme, onEvent }) {
2928
3005
  setStatus("error");
2929
3006
  }
2930
3007
  }
3008
+ async function handleSubmit(e) {
3009
+ e.preventDefault();
3010
+ if (!email || !storeId) return;
3011
+ setStatus("loading");
3012
+ setErrorMsg("");
3013
+ if (turnstileToken.current) {
3014
+ return submitLead(turnstileToken.current);
3015
+ }
3016
+ const turnstile = window.turnstile;
3017
+ if (turnstile && turnstileWidgetId.current != null) {
3018
+ pendingSubmit.current = () => submitLead(turnstileToken.current);
3019
+ turnstile.execute(turnstileWidgetId.current);
3020
+ } else {
3021
+ return submitLead(null);
3022
+ }
3023
+ }
2931
3024
  const inputStyle = {
2932
3025
  flex: 1,
2933
3026
  minWidth: 0,
@@ -3021,7 +3114,8 @@ function LeadCaptureSection({ section, data, theme, onEvent }) {
3021
3114
  animation: "lc-spin 0.8s linear infinite"
3022
3115
  } }),
3023
3116
  buttonText
3024
- ] })
3117
+ ] }),
3118
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: turnstileRef, style: { display: "none" } })
3025
3119
  ] })
3026
3120
  ] })
3027
3121
  ] });