@neowhale/storefront 0.2.56 → 0.2.58

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.
@@ -1130,6 +1130,25 @@ var WhaleStorefront = (function (exports) {
1130
1130
  }
1131
1131
 
1132
1132
  // src/react/components/sections/lead-capture-section.tsx
1133
+ var TURNSTILE_SITE_KEY = "0x4AAAAAACwmUPgmyfw6pWfT";
1134
+ var TURNSTILE_SCRIPT_URL = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
1135
+ var turnstilePromise = null;
1136
+ function loadTurnstileScript() {
1137
+ if (turnstilePromise) return turnstilePromise;
1138
+ if (typeof window !== "undefined" && window.turnstile) {
1139
+ turnstilePromise = Promise.resolve();
1140
+ return turnstilePromise;
1141
+ }
1142
+ turnstilePromise = new Promise((resolve, reject) => {
1143
+ const script = document.createElement("script");
1144
+ script.src = TURNSTILE_SCRIPT_URL;
1145
+ script.async = true;
1146
+ script.onload = () => resolve();
1147
+ script.onerror = () => reject(new Error("Failed to load Turnstile script"));
1148
+ document.head.appendChild(script);
1149
+ });
1150
+ return turnstilePromise;
1151
+ }
1133
1152
  function LeadCaptureSection({ section, data, theme, onEvent }) {
1134
1153
  const c = section.content;
1135
1154
  const [firstName, setFirstName] = useState("");
@@ -1138,6 +1157,41 @@ var WhaleStorefront = (function (exports) {
1138
1157
  const [status, setStatus] = useState("idle");
1139
1158
  const [errorMsg, setErrorMsg] = useState("");
1140
1159
  const [serverMessage, setServerMessage] = useState(null);
1160
+ const turnstileRef = useRef(null);
1161
+ const turnstileToken = useRef(null);
1162
+ const turnstileWidgetId = useRef(null);
1163
+ const pendingSubmit = useRef(null);
1164
+ const onTurnstileToken = useCallback((token) => {
1165
+ turnstileToken.current = token;
1166
+ if (pendingSubmit.current) {
1167
+ const cb = pendingSubmit.current;
1168
+ pendingSubmit.current = null;
1169
+ cb();
1170
+ }
1171
+ }, []);
1172
+ useEffect(() => {
1173
+ if (typeof window === "undefined") return;
1174
+ let widgetId = null;
1175
+ loadTurnstileScript().then(() => {
1176
+ const el = turnstileRef.current;
1177
+ if (!el || !window.turnstile) return;
1178
+ widgetId = window.turnstile.render(el, {
1179
+ sitekey: TURNSTILE_SITE_KEY,
1180
+ callback: onTurnstileToken,
1181
+ "expired-callback": () => {
1182
+ turnstileToken.current = null;
1183
+ },
1184
+ size: "invisible"
1185
+ });
1186
+ turnstileWidgetId.current = widgetId;
1187
+ }).catch(() => {
1188
+ });
1189
+ return () => {
1190
+ if (widgetId != null && window.turnstile) {
1191
+ window.turnstile.remove(widgetId);
1192
+ }
1193
+ };
1194
+ }, [onTurnstileToken]);
1141
1195
  const gatewayUrl = c.gateway_url || data.gatewayUrl || "https://whale-gateway.fly.dev";
1142
1196
  const storeId = c.store_id || data.store?.id;
1143
1197
  const slug = c.landing_page_slug || data.landing_page?.slug;
@@ -1146,11 +1200,7 @@ var WhaleStorefront = (function (exports) {
1146
1200
  const buttonText = c.button_text || "Claim My Discount";
1147
1201
  const successHeading = c.success_heading || "You\u2019re in!";
1148
1202
  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("");
1203
+ async function submitLead(cfToken) {
1154
1204
  const urlParams = typeof window !== "undefined" ? new URLSearchParams(window.location.search) : null;
1155
1205
  try {
1156
1206
  const res = await fetch(`${gatewayUrl}/v1/stores/${storeId}/storefront/leads`, {
@@ -1172,7 +1222,8 @@ var WhaleStorefront = (function (exports) {
1172
1222
  utm_source: urlParams?.get("utm_source") || void 0,
1173
1223
  utm_medium: urlParams?.get("utm_medium") || void 0,
1174
1224
  utm_campaign: urlParams?.get("utm_campaign") || void 0,
1175
- utm_content: urlParams?.get("utm_content") || void 0
1225
+ utm_content: urlParams?.get("utm_content") || void 0,
1226
+ cf_turnstile_response: cfToken || void 0
1176
1227
  })
1177
1228
  });
1178
1229
  if (!res.ok) {
@@ -1193,6 +1244,22 @@ var WhaleStorefront = (function (exports) {
1193
1244
  setStatus("error");
1194
1245
  }
1195
1246
  }
1247
+ async function handleSubmit(e) {
1248
+ e.preventDefault();
1249
+ if (!email || !storeId) return;
1250
+ setStatus("loading");
1251
+ setErrorMsg("");
1252
+ if (turnstileToken.current) {
1253
+ return submitLead(turnstileToken.current);
1254
+ }
1255
+ const turnstile = window.turnstile;
1256
+ if (turnstile && turnstileWidgetId.current != null) {
1257
+ pendingSubmit.current = () => submitLead(turnstileToken.current);
1258
+ turnstile.execute(turnstileWidgetId.current);
1259
+ } else {
1260
+ return submitLead(null);
1261
+ }
1262
+ }
1196
1263
  const inputStyle = {
1197
1264
  flex: 1,
1198
1265
  minWidth: 0,
@@ -1207,7 +1274,7 @@ var WhaleStorefront = (function (exports) {
1207
1274
  fontFamily: "inherit",
1208
1275
  transition: "border-color 0.2s"
1209
1276
  };
1210
- if (status === "success") return /* @__PURE__ */ jsx(SuccessState, { theme, heading: serverMessage?.heading || successHeading, message: serverMessage?.message || successMessage, couponCode: c.coupon_code });
1277
+ if (status === "success") return /* @__PURE__ */ jsx(SuccessState, { theme, heading: serverMessage?.heading || successHeading, message: serverMessage?.message || successMessage, couponCode: c.coupon_code, ctaText: c.success_cta_text, ctaUrl: c.success_cta_url });
1211
1278
  return /* @__PURE__ */ jsx("div", { style: { padding: "3.5rem 1.5rem", maxWidth: 560, margin: "0 auto" }, children: [
1212
1279
  /* @__PURE__ */ jsx("style", { children: `@keyframes lc-spin { to { transform: rotate(360deg) } }` }),
1213
1280
  /* @__PURE__ */ jsx("div", { style: { background: theme.surface, border: `1px solid ${theme.fg}12`, padding: "clamp(2rem, 6vw, 3rem)" }, children: [
@@ -1286,12 +1353,13 @@ var WhaleStorefront = (function (exports) {
1286
1353
  animation: "lc-spin 0.8s linear infinite"
1287
1354
  } }),
1288
1355
  buttonText
1289
- ] })
1356
+ ] }),
1357
+ /* @__PURE__ */ jsx("div", { ref: turnstileRef, style: { display: "none" } })
1290
1358
  ] })
1291
1359
  ] })
1292
1360
  ] });
1293
1361
  }
1294
- function SuccessState({ theme, heading, message, couponCode }) {
1362
+ function SuccessState({ theme, heading, message, couponCode, ctaText, ctaUrl }) {
1295
1363
  return /* @__PURE__ */ jsx("div", { style: { padding: "3.5rem 1.5rem", maxWidth: 560, margin: "0 auto" }, children: /* @__PURE__ */ jsx("div", { style: { background: theme.surface, border: `1px solid ${theme.fg}12`, padding: "clamp(2rem, 6vw, 3rem)", textAlign: "center" }, children: [
1296
1364
  /* @__PURE__ */ jsx("h2", { style: {
1297
1365
  fontSize: "clamp(1.5rem, 5vw, 2rem)",
@@ -1313,7 +1381,30 @@ var WhaleStorefront = (function (exports) {
1313
1381
  fontFamily: "monospace",
1314
1382
  letterSpacing: "0.12em",
1315
1383
  color: theme.accent
1316
- }, children: couponCode })
1384
+ }, children: couponCode }),
1385
+ /* @__PURE__ */ jsx(
1386
+ "button",
1387
+ {
1388
+ onClick: () => {
1389
+ window.location.href = ctaUrl || "/shop";
1390
+ },
1391
+ style: {
1392
+ marginTop: "1.5rem",
1393
+ width: "100%",
1394
+ padding: "0.875rem",
1395
+ background: theme.accent,
1396
+ color: "#fff",
1397
+ border: "none",
1398
+ fontSize: "0.85rem",
1399
+ fontWeight: 500,
1400
+ cursor: "pointer",
1401
+ letterSpacing: "0.08em",
1402
+ textTransform: "uppercase",
1403
+ fontFamily: "inherit"
1404
+ },
1405
+ children: ctaText || "shop now"
1406
+ }
1407
+ )
1317
1408
  ] }) });
1318
1409
  }
1319
1410
 
@@ -2865,6 +2865,25 @@ function COAModal({ coa, theme, onClose }) {
2865
2865
  /* @__PURE__ */ jsxRuntime.jsx("iframe", { src: coa.url, style: { flex: 1, border: "none", background: "#fff" }, title: "Lab Results" })
2866
2866
  ] });
2867
2867
  }
2868
+ var TURNSTILE_SITE_KEY = "0x4AAAAAACwmUPgmyfw6pWfT";
2869
+ var TURNSTILE_SCRIPT_URL = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
2870
+ var turnstilePromise = null;
2871
+ function loadTurnstileScript() {
2872
+ if (turnstilePromise) return turnstilePromise;
2873
+ if (typeof window !== "undefined" && window.turnstile) {
2874
+ turnstilePromise = Promise.resolve();
2875
+ return turnstilePromise;
2876
+ }
2877
+ turnstilePromise = new Promise((resolve, reject) => {
2878
+ const script = document.createElement("script");
2879
+ script.src = TURNSTILE_SCRIPT_URL;
2880
+ script.async = true;
2881
+ script.onload = () => resolve();
2882
+ script.onerror = () => reject(new Error("Failed to load Turnstile script"));
2883
+ document.head.appendChild(script);
2884
+ });
2885
+ return turnstilePromise;
2886
+ }
2868
2887
  function LeadCaptureSection({ section, data, theme, onEvent }) {
2869
2888
  const c = section.content;
2870
2889
  const [firstName, setFirstName] = react.useState("");
@@ -2873,6 +2892,41 @@ function LeadCaptureSection({ section, data, theme, onEvent }) {
2873
2892
  const [status, setStatus] = react.useState("idle");
2874
2893
  const [errorMsg, setErrorMsg] = react.useState("");
2875
2894
  const [serverMessage, setServerMessage] = react.useState(null);
2895
+ const turnstileRef = react.useRef(null);
2896
+ const turnstileToken = react.useRef(null);
2897
+ const turnstileWidgetId = react.useRef(null);
2898
+ const pendingSubmit = react.useRef(null);
2899
+ const onTurnstileToken = react.useCallback((token) => {
2900
+ turnstileToken.current = token;
2901
+ if (pendingSubmit.current) {
2902
+ const cb = pendingSubmit.current;
2903
+ pendingSubmit.current = null;
2904
+ cb();
2905
+ }
2906
+ }, []);
2907
+ react.useEffect(() => {
2908
+ if (typeof window === "undefined") return;
2909
+ let widgetId = null;
2910
+ loadTurnstileScript().then(() => {
2911
+ const el = turnstileRef.current;
2912
+ if (!el || !window.turnstile) return;
2913
+ widgetId = window.turnstile.render(el, {
2914
+ sitekey: TURNSTILE_SITE_KEY,
2915
+ callback: onTurnstileToken,
2916
+ "expired-callback": () => {
2917
+ turnstileToken.current = null;
2918
+ },
2919
+ size: "invisible"
2920
+ });
2921
+ turnstileWidgetId.current = widgetId;
2922
+ }).catch(() => {
2923
+ });
2924
+ return () => {
2925
+ if (widgetId != null && window.turnstile) {
2926
+ window.turnstile.remove(widgetId);
2927
+ }
2928
+ };
2929
+ }, [onTurnstileToken]);
2876
2930
  const gatewayUrl = c.gateway_url || data.gatewayUrl || "https://whale-gateway.fly.dev";
2877
2931
  const storeId = c.store_id || data.store?.id;
2878
2932
  const slug = c.landing_page_slug || data.landing_page?.slug;
@@ -2881,11 +2935,7 @@ function LeadCaptureSection({ section, data, theme, onEvent }) {
2881
2935
  const buttonText = c.button_text || "Claim My Discount";
2882
2936
  const successHeading = c.success_heading || "You\u2019re in!";
2883
2937
  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("");
2938
+ async function submitLead(cfToken) {
2889
2939
  const urlParams = typeof window !== "undefined" ? new URLSearchParams(window.location.search) : null;
2890
2940
  try {
2891
2941
  const res = await fetch(`${gatewayUrl}/v1/stores/${storeId}/storefront/leads`, {
@@ -2907,7 +2957,8 @@ function LeadCaptureSection({ section, data, theme, onEvent }) {
2907
2957
  utm_source: urlParams?.get("utm_source") || void 0,
2908
2958
  utm_medium: urlParams?.get("utm_medium") || void 0,
2909
2959
  utm_campaign: urlParams?.get("utm_campaign") || void 0,
2910
- utm_content: urlParams?.get("utm_content") || void 0
2960
+ utm_content: urlParams?.get("utm_content") || void 0,
2961
+ cf_turnstile_response: cfToken || void 0
2911
2962
  })
2912
2963
  });
2913
2964
  if (!res.ok) {
@@ -2928,6 +2979,22 @@ function LeadCaptureSection({ section, data, theme, onEvent }) {
2928
2979
  setStatus("error");
2929
2980
  }
2930
2981
  }
2982
+ async function handleSubmit(e) {
2983
+ e.preventDefault();
2984
+ if (!email || !storeId) return;
2985
+ setStatus("loading");
2986
+ setErrorMsg("");
2987
+ if (turnstileToken.current) {
2988
+ return submitLead(turnstileToken.current);
2989
+ }
2990
+ const turnstile = window.turnstile;
2991
+ if (turnstile && turnstileWidgetId.current != null) {
2992
+ pendingSubmit.current = () => submitLead(turnstileToken.current);
2993
+ turnstile.execute(turnstileWidgetId.current);
2994
+ } else {
2995
+ return submitLead(null);
2996
+ }
2997
+ }
2931
2998
  const inputStyle = {
2932
2999
  flex: 1,
2933
3000
  minWidth: 0,
@@ -2942,7 +3009,7 @@ function LeadCaptureSection({ section, data, theme, onEvent }) {
2942
3009
  fontFamily: "inherit",
2943
3010
  transition: "border-color 0.2s"
2944
3011
  };
2945
- if (status === "success") return /* @__PURE__ */ jsxRuntime.jsx(SuccessState, { theme, heading: serverMessage?.heading || successHeading, message: serverMessage?.message || successMessage, couponCode: c.coupon_code });
3012
+ if (status === "success") return /* @__PURE__ */ jsxRuntime.jsx(SuccessState, { theme, heading: serverMessage?.heading || successHeading, message: serverMessage?.message || successMessage, couponCode: c.coupon_code, ctaText: c.success_cta_text, ctaUrl: c.success_cta_url });
2946
3013
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "3.5rem 1.5rem", maxWidth: 560, margin: "0 auto" }, children: [
2947
3014
  /* @__PURE__ */ jsxRuntime.jsx("style", { children: `@keyframes lc-spin { to { transform: rotate(360deg) } }` }),
2948
3015
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { background: theme.surface, border: `1px solid ${theme.fg}12`, padding: "clamp(2rem, 6vw, 3rem)" }, children: [
@@ -3021,12 +3088,13 @@ function LeadCaptureSection({ section, data, theme, onEvent }) {
3021
3088
  animation: "lc-spin 0.8s linear infinite"
3022
3089
  } }),
3023
3090
  buttonText
3024
- ] })
3091
+ ] }),
3092
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: turnstileRef, style: { display: "none" } })
3025
3093
  ] })
3026
3094
  ] })
3027
3095
  ] });
3028
3096
  }
3029
- function SuccessState({ theme, heading, message, couponCode }) {
3097
+ function SuccessState({ theme, heading, message, couponCode, ctaText, ctaUrl }) {
3030
3098
  return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "3.5rem 1.5rem", maxWidth: 560, margin: "0 auto" }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { background: theme.surface, border: `1px solid ${theme.fg}12`, padding: "clamp(2rem, 6vw, 3rem)", textAlign: "center" }, children: [
3031
3099
  /* @__PURE__ */ jsxRuntime.jsx("h2", { style: {
3032
3100
  fontSize: "clamp(1.5rem, 5vw, 2rem)",
@@ -3048,7 +3116,30 @@ function SuccessState({ theme, heading, message, couponCode }) {
3048
3116
  fontFamily: "monospace",
3049
3117
  letterSpacing: "0.12em",
3050
3118
  color: theme.accent
3051
- }, children: couponCode })
3119
+ }, children: couponCode }),
3120
+ /* @__PURE__ */ jsxRuntime.jsx(
3121
+ "button",
3122
+ {
3123
+ onClick: () => {
3124
+ window.location.href = ctaUrl || "/shop";
3125
+ },
3126
+ style: {
3127
+ marginTop: "1.5rem",
3128
+ width: "100%",
3129
+ padding: "0.875rem",
3130
+ background: theme.accent,
3131
+ color: "#fff",
3132
+ border: "none",
3133
+ fontSize: "0.85rem",
3134
+ fontWeight: 500,
3135
+ cursor: "pointer",
3136
+ letterSpacing: "0.08em",
3137
+ textTransform: "uppercase",
3138
+ fontFamily: "inherit"
3139
+ },
3140
+ children: ctaText || "shop now"
3141
+ }
3142
+ )
3052
3143
  ] }) });
3053
3144
  }
3054
3145
  var GOOGLE_G_LG = '<svg width="28" height="28" viewBox="0 0 48 48"><path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"/><path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"/><path fill="#FBBC05" d="M10.53 28.59a14.5 14.5 0 010-9.18l-7.98-6.19a24.03 24.03 0 000 21.56l7.98-6.19z"/><path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"/></svg>';