@neowhale/storefront 0.2.29 → 0.2.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/react/index.cjs +83 -9
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +7 -1
- package/dist/react/index.d.ts +7 -1
- package/dist/react/index.js +83 -9
- package/dist/react/index.js.map +1 -1
- package/package.json +1 -1
package/dist/react/index.cjs
CHANGED
|
@@ -555,7 +555,9 @@ function useAnalytics() {
|
|
|
555
555
|
/** Whether tracking is globally enabled for this storefront */
|
|
556
556
|
trackingEnabled,
|
|
557
557
|
/** Configured recording sample rate (0–1) for behavioral session replays */
|
|
558
|
-
recordingRate: config.recordingRate
|
|
558
|
+
recordingRate: config.recordingRate,
|
|
559
|
+
/** Stable visitor ID for cross-session attribution */
|
|
560
|
+
visitorId: getVisitorId(config.storagePrefix)
|
|
559
561
|
};
|
|
560
562
|
}
|
|
561
563
|
function useAuth() {
|
|
@@ -2454,6 +2456,68 @@ function useReferral() {
|
|
|
2454
2456
|
referredBy: status?.referred_by ?? null
|
|
2455
2457
|
};
|
|
2456
2458
|
}
|
|
2459
|
+
var NUM_PATTERN = /(\$?[\d,]+\.?\d*[+★%]?)/g;
|
|
2460
|
+
function easeOutQuart(t) {
|
|
2461
|
+
return 1 - Math.pow(1 - t, 4);
|
|
2462
|
+
}
|
|
2463
|
+
function useCountUp(target, duration, start) {
|
|
2464
|
+
const [value, setValue] = react.useState(0);
|
|
2465
|
+
const raf = react.useRef(0);
|
|
2466
|
+
react.useEffect(() => {
|
|
2467
|
+
if (!start) return;
|
|
2468
|
+
const t0 = performance.now();
|
|
2469
|
+
function tick(now2) {
|
|
2470
|
+
const elapsed = now2 - t0;
|
|
2471
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
2472
|
+
setValue(Math.round(easeOutQuart(progress) * target));
|
|
2473
|
+
if (progress < 1) raf.current = requestAnimationFrame(tick);
|
|
2474
|
+
}
|
|
2475
|
+
raf.current = requestAnimationFrame(tick);
|
|
2476
|
+
return () => cancelAnimationFrame(raf.current);
|
|
2477
|
+
}, [target, duration, start]);
|
|
2478
|
+
return value;
|
|
2479
|
+
}
|
|
2480
|
+
function AnimatedNumber({ raw }) {
|
|
2481
|
+
const ref = react.useRef(null);
|
|
2482
|
+
const [visible, setVisible] = react.useState(false);
|
|
2483
|
+
react.useEffect(() => {
|
|
2484
|
+
const el = ref.current;
|
|
2485
|
+
if (!el || typeof IntersectionObserver === "undefined") {
|
|
2486
|
+
setVisible(true);
|
|
2487
|
+
return;
|
|
2488
|
+
}
|
|
2489
|
+
const obs = new IntersectionObserver(([entry]) => {
|
|
2490
|
+
if (entry.isIntersecting) {
|
|
2491
|
+
setVisible(true);
|
|
2492
|
+
obs.disconnect();
|
|
2493
|
+
}
|
|
2494
|
+
}, { threshold: 0.3 });
|
|
2495
|
+
obs.observe(el);
|
|
2496
|
+
return () => obs.disconnect();
|
|
2497
|
+
}, []);
|
|
2498
|
+
const prefix = raw.startsWith("$") ? "$" : "";
|
|
2499
|
+
const suffix = raw.match(/[+★%]$/)?.[0] || "";
|
|
2500
|
+
const numeric = parseFloat(raw.replace(/[\$,+★%]/g, ""));
|
|
2501
|
+
const hasCommas = raw.includes(",");
|
|
2502
|
+
const decimals = raw.includes(".") ? raw.split(".")[1]?.replace(/[+★%]/g, "").length || 0 : 0;
|
|
2503
|
+
const count = useCountUp(
|
|
2504
|
+
decimals > 0 ? Math.round(numeric * Math.pow(10, decimals)) : numeric,
|
|
2505
|
+
1400,
|
|
2506
|
+
visible
|
|
2507
|
+
);
|
|
2508
|
+
const display = decimals > 0 ? (count / Math.pow(10, decimals)).toFixed(decimals) : hasCommas ? count.toLocaleString() : String(count);
|
|
2509
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { ref, children: [
|
|
2510
|
+
prefix,
|
|
2511
|
+
display,
|
|
2512
|
+
suffix
|
|
2513
|
+
] });
|
|
2514
|
+
}
|
|
2515
|
+
function AnimatedText({ text }) {
|
|
2516
|
+
const parts = text.split(NUM_PATTERN);
|
|
2517
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: parts.map(
|
|
2518
|
+
(part, i) => NUM_PATTERN.test(part) ? /* @__PURE__ */ jsxRuntime.jsx(AnimatedNumber, { raw: part }, i) : part
|
|
2519
|
+
) });
|
|
2520
|
+
}
|
|
2457
2521
|
function trackClick(tracking, label, url, position) {
|
|
2458
2522
|
if (!tracking?.gatewayUrl || !tracking?.code) return;
|
|
2459
2523
|
const body = JSON.stringify({ label, url, position });
|
|
@@ -2536,7 +2600,7 @@ function HeroSection({ section, theme, tracking, onEvent }) {
|
|
|
2536
2600
|
lineHeight: 1.15,
|
|
2537
2601
|
letterSpacing: "-0.02em",
|
|
2538
2602
|
color: theme.fg
|
|
2539
|
-
}, children: title }),
|
|
2603
|
+
}, children: /* @__PURE__ */ jsxRuntime.jsx(AnimatedText, { text: title }) }),
|
|
2540
2604
|
subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { style: {
|
|
2541
2605
|
fontSize: "0.85rem",
|
|
2542
2606
|
color: theme.accent,
|
|
@@ -2711,7 +2775,7 @@ function StatsSection({ section, theme }) {
|
|
|
2711
2775
|
letterSpacing: "0.15em",
|
|
2712
2776
|
color: `${theme.fg}66`
|
|
2713
2777
|
}, children: stat.label }),
|
|
2714
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 14, fontWeight: 300, color: `${theme.fg}CC` }, children: stat.value })
|
|
2778
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 14, fontWeight: 300, color: `${theme.fg}CC` }, children: /* @__PURE__ */ jsxRuntime.jsx(AnimatedText, { text: stat.value }) })
|
|
2715
2779
|
] }),
|
|
2716
2780
|
i < stats.length - 1 && /* @__PURE__ */ jsxRuntime.jsx("hr", { style: { border: "none", borderTop: `1px solid ${theme.fg}0A`, margin: 0 } })
|
|
2717
2781
|
] }, i)) });
|
|
@@ -2732,7 +2796,7 @@ function StatsSection({ section, theme }) {
|
|
|
2732
2796
|
fontWeight: 300,
|
|
2733
2797
|
lineHeight: 1,
|
|
2734
2798
|
color: theme.fg
|
|
2735
|
-
}, children: stat.value }),
|
|
2799
|
+
}, children: /* @__PURE__ */ jsxRuntime.jsx(AnimatedText, { text: stat.value }) }),
|
|
2736
2800
|
/* @__PURE__ */ jsxRuntime.jsx("div", { style: {
|
|
2737
2801
|
fontSize: 11,
|
|
2738
2802
|
fontWeight: 500,
|
|
@@ -2840,6 +2904,8 @@ function LeadCaptureSection({ section, data, theme, onEvent }) {
|
|
|
2840
2904
|
if (!email || !storeId) return;
|
|
2841
2905
|
setStatus("loading");
|
|
2842
2906
|
setErrorMsg("");
|
|
2907
|
+
const urlParams = typeof window !== "undefined" ? new URLSearchParams(window.location.search) : null;
|
|
2908
|
+
const analyticsData = data.analyticsContext;
|
|
2843
2909
|
try {
|
|
2844
2910
|
const res = await fetch(`${gatewayUrl}/v1/stores/${storeId}/storefront/leads`, {
|
|
2845
2911
|
method: "POST",
|
|
@@ -2854,7 +2920,13 @@ function LeadCaptureSection({ section, data, theme, onEvent }) {
|
|
|
2854
2920
|
const t = [...c.tags || []];
|
|
2855
2921
|
if (newsletterOptIn) t.push(c.newsletter_tag || "newsletter-subscriber");
|
|
2856
2922
|
return t.length > 0 ? t : void 0;
|
|
2857
|
-
})()
|
|
2923
|
+
})(),
|
|
2924
|
+
visitor_id: analyticsData?.visitorId || void 0,
|
|
2925
|
+
session_id: analyticsData?.sessionId || void 0,
|
|
2926
|
+
utm_source: urlParams?.get("utm_source") || void 0,
|
|
2927
|
+
utm_medium: urlParams?.get("utm_medium") || void 0,
|
|
2928
|
+
utm_campaign: urlParams?.get("utm_campaign") || void 0,
|
|
2929
|
+
utm_content: urlParams?.get("utm_content") || void 0
|
|
2858
2930
|
})
|
|
2859
2931
|
});
|
|
2860
2932
|
if (!res.ok) {
|
|
@@ -3400,7 +3472,8 @@ function LandingPage({
|
|
|
3400
3472
|
renderSection,
|
|
3401
3473
|
onDataLoaded,
|
|
3402
3474
|
onError,
|
|
3403
|
-
onEvent
|
|
3475
|
+
onEvent,
|
|
3476
|
+
analyticsContext
|
|
3404
3477
|
}) {
|
|
3405
3478
|
const [state, setState] = react.useState("loading");
|
|
3406
3479
|
const [data, setData] = react.useState(null);
|
|
@@ -3448,13 +3521,14 @@ function LandingPage({
|
|
|
3448
3521
|
if (state === "expired") return /* @__PURE__ */ jsxRuntime.jsx(DefaultExpired2, {});
|
|
3449
3522
|
if (state === "error") return /* @__PURE__ */ jsxRuntime.jsx(DefaultError2, { message: errorMsg });
|
|
3450
3523
|
if (!data) return null;
|
|
3451
|
-
return /* @__PURE__ */ jsxRuntime.jsx(PageLayout, { data, gatewayUrl, renderSection, onEvent });
|
|
3524
|
+
return /* @__PURE__ */ jsxRuntime.jsx(PageLayout, { data, gatewayUrl, renderSection, onEvent, analyticsContext });
|
|
3452
3525
|
}
|
|
3453
3526
|
function PageLayout({
|
|
3454
3527
|
data,
|
|
3455
3528
|
gatewayUrl,
|
|
3456
3529
|
renderSection,
|
|
3457
|
-
onEvent
|
|
3530
|
+
onEvent,
|
|
3531
|
+
analyticsContext
|
|
3458
3532
|
}) {
|
|
3459
3533
|
const { landing_page: lp, store } = data;
|
|
3460
3534
|
const theme = {
|
|
@@ -3469,7 +3543,7 @@ function PageLayout({
|
|
|
3469
3543
|
const fontFamily = lp.font_family || theme.fontDisplay || "system-ui, -apple-system, sans-serif";
|
|
3470
3544
|
const logoUrl = store?.logo_url;
|
|
3471
3545
|
const sorted = [...lp.sections].sort((a, b) => a.order - b.order);
|
|
3472
|
-
const sectionData = { ...data, gatewayUrl, landing_page: { slug: lp.slug } };
|
|
3546
|
+
const sectionData = { ...data, gatewayUrl, landing_page: { slug: lp.slug }, analyticsContext };
|
|
3473
3547
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { minHeight: "100dvh", background: theme.bg, color: theme.fg, fontFamily }, children: [
|
|
3474
3548
|
lp.custom_css && /* @__PURE__ */ jsxRuntime.jsx("style", { children: lp.custom_css }),
|
|
3475
3549
|
logoUrl && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "1.5rem", display: "flex", justifyContent: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: logoUrl, alt: store?.name || "Store", style: { height: 40, objectFit: "contain" } }) }),
|