@neowhale/storefront 0.2.28 → 0.2.30

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.
@@ -1,11 +1,11 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as react from 'react';
3
3
  import { ReactNode } from 'react';
4
- import { F as WhaleStorefrontConfig, r as Product, P as PaymentData, O as Order, a as CartItem, T as TaxBreakdown, W as WhaleClient, f as Customer, E as EventType, g as CustomerAnalytics, d as CheckoutSession, A as Address, b as Category, c as CategoryTreeNode, l as LoyaltyAccount, m as LoyaltyReward, n as LoyaltyTransaction, v as Review, w as ReviewSummary, G as WishlistItem, R as Recommendation, k as Location, x as ShippingMethod, y as ShippingRate, D as DealValidation, C as Cart, H as ReferralStatus, I as ReferralEnrollment, i as LandingSection, Q as QRLandingData, h as LandingPageRenderData } from '../client-BWUWE_7m.cjs';
4
+ import { F as WhaleStorefrontConfig, r as Product, P as PaymentData, O as Order, a as CartItem, T as TaxBreakdown, W as WhaleClient, f as Customer, E as EventType, g as CustomerAnalytics, d as CheckoutSession, A as Address, b as Category, c as CategoryTreeNode, l as LoyaltyAccount, m as LoyaltyReward, n as LoyaltyTransaction, v as Review, w as ReviewSummary, G as WishlistItem, R as Recommendation, k as Location, x as ShippingMethod, y as ShippingRate, D as DealValidation, C as Cart, H as ReferralStatus, I as ReferralEnrollment, i as LandingSection, Q as QRLandingData, h as LandingPageRenderData } from '../client-CwCSPAHp.cjs';
5
5
  import { ThemeTokens } from '@neowhale/ui';
6
6
  import * as zustand_middleware from 'zustand/middleware';
7
7
  import * as zustand from 'zustand';
8
- import { P as PixelManager } from '../pixel-manager-BRPU9JKk.cjs';
8
+ import { P as PixelManager } from '../pixel-manager-ti1xc1eC.cjs';
9
9
 
10
10
  interface WhaleProviderProps extends WhaleStorefrontConfig {
11
11
  children: ReactNode;
@@ -429,8 +429,9 @@ interface LandingPageProps {
429
429
  renderSection?: (section: LandingSection, defaultRenderer: () => React.ReactNode) => React.ReactNode;
430
430
  onDataLoaded?: (data: LandingPageRenderData) => void;
431
431
  onError?: (error: Error) => void;
432
+ onEvent?: (event: string, data: Record<string, unknown>) => void;
432
433
  }
433
- declare function LandingPage({ slug, gatewayUrl, renderSection, onDataLoaded, onError, }: LandingPageProps): react_jsx_runtime.JSX.Element | null;
434
+ declare function LandingPage({ slug, gatewayUrl, renderSection, onDataLoaded, onError, onEvent, }: LandingPageProps): react_jsx_runtime.JSX.Element | null;
434
435
 
435
436
  interface SectionTheme {
436
437
  bg: string;
@@ -464,11 +465,12 @@ interface SectionData {
464
465
  slug?: string;
465
466
  } | null;
466
467
  }
467
- declare function SectionRenderer({ section, data, theme, tracking, }: {
468
+ declare function SectionRenderer({ section, data, theme, tracking, onEvent, }: {
468
469
  section: LandingSection;
469
470
  data: SectionData;
470
471
  theme: SectionTheme;
471
472
  tracking?: ClickTrackingContext;
473
+ onEvent?: (event: string, data: Record<string, unknown>) => void;
472
474
  }): react_jsx_runtime.JSX.Element;
473
475
 
474
476
  /**
@@ -1,11 +1,11 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as react from 'react';
3
3
  import { ReactNode } from 'react';
4
- import { F as WhaleStorefrontConfig, r as Product, P as PaymentData, O as Order, a as CartItem, T as TaxBreakdown, W as WhaleClient, f as Customer, E as EventType, g as CustomerAnalytics, d as CheckoutSession, A as Address, b as Category, c as CategoryTreeNode, l as LoyaltyAccount, m as LoyaltyReward, n as LoyaltyTransaction, v as Review, w as ReviewSummary, G as WishlistItem, R as Recommendation, k as Location, x as ShippingMethod, y as ShippingRate, D as DealValidation, C as Cart, H as ReferralStatus, I as ReferralEnrollment, i as LandingSection, Q as QRLandingData, h as LandingPageRenderData } from '../client-BWUWE_7m.js';
4
+ import { F as WhaleStorefrontConfig, r as Product, P as PaymentData, O as Order, a as CartItem, T as TaxBreakdown, W as WhaleClient, f as Customer, E as EventType, g as CustomerAnalytics, d as CheckoutSession, A as Address, b as Category, c as CategoryTreeNode, l as LoyaltyAccount, m as LoyaltyReward, n as LoyaltyTransaction, v as Review, w as ReviewSummary, G as WishlistItem, R as Recommendation, k as Location, x as ShippingMethod, y as ShippingRate, D as DealValidation, C as Cart, H as ReferralStatus, I as ReferralEnrollment, i as LandingSection, Q as QRLandingData, h as LandingPageRenderData } from '../client-CwCSPAHp.js';
5
5
  import { ThemeTokens } from '@neowhale/ui';
6
6
  import * as zustand_middleware from 'zustand/middleware';
7
7
  import * as zustand from 'zustand';
8
- import { P as PixelManager } from '../pixel-manager-CJaD49Xb.js';
8
+ import { P as PixelManager } from '../pixel-manager-LiLpBmPy.js';
9
9
 
10
10
  interface WhaleProviderProps extends WhaleStorefrontConfig {
11
11
  children: ReactNode;
@@ -429,8 +429,9 @@ interface LandingPageProps {
429
429
  renderSection?: (section: LandingSection, defaultRenderer: () => React.ReactNode) => React.ReactNode;
430
430
  onDataLoaded?: (data: LandingPageRenderData) => void;
431
431
  onError?: (error: Error) => void;
432
+ onEvent?: (event: string, data: Record<string, unknown>) => void;
432
433
  }
433
- declare function LandingPage({ slug, gatewayUrl, renderSection, onDataLoaded, onError, }: LandingPageProps): react_jsx_runtime.JSX.Element | null;
434
+ declare function LandingPage({ slug, gatewayUrl, renderSection, onDataLoaded, onError, onEvent, }: LandingPageProps): react_jsx_runtime.JSX.Element | null;
434
435
 
435
436
  interface SectionTheme {
436
437
  bg: string;
@@ -464,11 +465,12 @@ interface SectionData {
464
465
  slug?: string;
465
466
  } | null;
466
467
  }
467
- declare function SectionRenderer({ section, data, theme, tracking, }: {
468
+ declare function SectionRenderer({ section, data, theme, tracking, onEvent, }: {
468
469
  section: LandingSection;
469
470
  data: SectionData;
470
471
  theme: SectionTheme;
471
472
  tracking?: ClickTrackingContext;
473
+ onEvent?: (event: string, data: Record<string, unknown>) => void;
472
474
  }): react_jsx_runtime.JSX.Element;
473
475
 
474
476
  /**
@@ -1,4 +1,4 @@
1
- import { PixelManager } from '../chunk-PXS2DPVL.js';
1
+ import { PixelManager } from '../chunk-MZO7BCGU.js';
2
2
  import { resilientSend, WhaleClient } from '../chunk-WNZOA4FB.js';
3
3
  import { createContext, useContext, useRef, useCallback, useEffect, useState, useMemo } from 'react';
4
4
  import { usePathname } from 'next/navigation';
@@ -2452,6 +2452,68 @@ function useReferral() {
2452
2452
  referredBy: status?.referred_by ?? null
2453
2453
  };
2454
2454
  }
2455
+ var NUM_PATTERN = /(\$?[\d,]+\.?\d*[+★%]?)/g;
2456
+ function easeOutQuart(t) {
2457
+ return 1 - Math.pow(1 - t, 4);
2458
+ }
2459
+ function useCountUp(target, duration, start) {
2460
+ const [value, setValue] = useState(0);
2461
+ const raf = useRef(0);
2462
+ useEffect(() => {
2463
+ if (!start) return;
2464
+ const t0 = performance.now();
2465
+ function tick(now2) {
2466
+ const elapsed = now2 - t0;
2467
+ const progress = Math.min(elapsed / duration, 1);
2468
+ setValue(Math.round(easeOutQuart(progress) * target));
2469
+ if (progress < 1) raf.current = requestAnimationFrame(tick);
2470
+ }
2471
+ raf.current = requestAnimationFrame(tick);
2472
+ return () => cancelAnimationFrame(raf.current);
2473
+ }, [target, duration, start]);
2474
+ return value;
2475
+ }
2476
+ function AnimatedNumber({ raw }) {
2477
+ const ref = useRef(null);
2478
+ const [visible, setVisible] = useState(false);
2479
+ useEffect(() => {
2480
+ const el = ref.current;
2481
+ if (!el || typeof IntersectionObserver === "undefined") {
2482
+ setVisible(true);
2483
+ return;
2484
+ }
2485
+ const obs = new IntersectionObserver(([entry]) => {
2486
+ if (entry.isIntersecting) {
2487
+ setVisible(true);
2488
+ obs.disconnect();
2489
+ }
2490
+ }, { threshold: 0.3 });
2491
+ obs.observe(el);
2492
+ return () => obs.disconnect();
2493
+ }, []);
2494
+ const prefix = raw.startsWith("$") ? "$" : "";
2495
+ const suffix = raw.match(/[+★%]$/)?.[0] || "";
2496
+ const numeric = parseFloat(raw.replace(/[\$,+★%]/g, ""));
2497
+ const hasCommas = raw.includes(",");
2498
+ const decimals = raw.includes(".") ? raw.split(".")[1]?.replace(/[+★%]/g, "").length || 0 : 0;
2499
+ const count = useCountUp(
2500
+ decimals > 0 ? Math.round(numeric * Math.pow(10, decimals)) : numeric,
2501
+ 1400,
2502
+ visible
2503
+ );
2504
+ const display = decimals > 0 ? (count / Math.pow(10, decimals)).toFixed(decimals) : hasCommas ? count.toLocaleString() : String(count);
2505
+ return /* @__PURE__ */ jsxs("span", { ref, children: [
2506
+ prefix,
2507
+ display,
2508
+ suffix
2509
+ ] });
2510
+ }
2511
+ function AnimatedText({ text }) {
2512
+ const parts = text.split(NUM_PATTERN);
2513
+ return /* @__PURE__ */ jsx(Fragment, { children: parts.map(
2514
+ (part, i) => NUM_PATTERN.test(part) ? /* @__PURE__ */ jsx(AnimatedNumber, { raw: part }, i) : part
2515
+ ) });
2516
+ }
2455
2517
  function trackClick(tracking, label, url, position) {
2456
2518
  if (!tracking?.gatewayUrl || !tracking?.code) return;
2457
2519
  const body = JSON.stringify({ label, url, position });
@@ -2466,13 +2528,14 @@ function SectionRenderer({
2466
2528
  section,
2467
2529
  data,
2468
2530
  theme,
2469
- tracking
2531
+ tracking,
2532
+ onEvent
2470
2533
  }) {
2471
2534
  const [showCOA, setShowCOA] = useState(false);
2472
2535
  const el = (() => {
2473
2536
  switch (section.type) {
2474
2537
  case "hero":
2475
- return /* @__PURE__ */ jsx(HeroSection, { section, theme, tracking });
2538
+ return /* @__PURE__ */ jsx(HeroSection, { section, theme, tracking, onEvent });
2476
2539
  case "text":
2477
2540
  return /* @__PURE__ */ jsx(TextSection, { section, theme });
2478
2541
  case "image":
@@ -2482,7 +2545,7 @@ function SectionRenderer({
2482
2545
  case "gallery":
2483
2546
  return /* @__PURE__ */ jsx(GallerySection, { section, theme });
2484
2547
  case "cta":
2485
- return /* @__PURE__ */ jsx(CTASection, { section, theme, tracking });
2548
+ return /* @__PURE__ */ jsx(CTASection, { section, theme, tracking, onEvent });
2486
2549
  case "stats":
2487
2550
  return /* @__PURE__ */ jsx(StatsSection, { section, theme });
2488
2551
  case "product_card":
@@ -2492,7 +2555,7 @@ function SectionRenderer({
2492
2555
  case "social_links":
2493
2556
  return /* @__PURE__ */ jsx(SocialLinksSection, { section, theme });
2494
2557
  case "lead_capture":
2495
- return /* @__PURE__ */ jsx(LeadCaptureSection, { section, data, theme });
2558
+ return /* @__PURE__ */ jsx(LeadCaptureSection, { section, data, theme, onEvent });
2496
2559
  case "divider":
2497
2560
  return /* @__PURE__ */ jsx(DividerSection, { theme });
2498
2561
  default:
@@ -2504,7 +2567,7 @@ function SectionRenderer({
2504
2567
  showCOA && data?.coa && /* @__PURE__ */ jsx(COAModal, { coa: data.coa, theme, onClose: () => setShowCOA(false) })
2505
2568
  ] });
2506
2569
  }
2507
- function HeroSection({ section, theme, tracking }) {
2570
+ function HeroSection({ section, theme, tracking, onEvent }) {
2508
2571
  const { title, subtitle, background_image, cta_text, cta_url } = section.content;
2509
2572
  return /* @__PURE__ */ jsxs(
2510
2573
  "div",
@@ -2533,7 +2596,7 @@ function HeroSection({ section, theme, tracking }) {
2533
2596
  lineHeight: 1.15,
2534
2597
  letterSpacing: "-0.02em",
2535
2598
  color: theme.fg
2536
- }, children: title }),
2599
+ }, children: /* @__PURE__ */ jsx(AnimatedText, { text: title }) }),
2537
2600
  subtitle && /* @__PURE__ */ jsx("p", { style: {
2538
2601
  fontSize: "0.85rem",
2539
2602
  color: theme.accent,
@@ -2546,7 +2609,10 @@ function HeroSection({ section, theme, tracking }) {
2546
2609
  "a",
2547
2610
  {
2548
2611
  href: cta_url,
2549
- onClick: () => trackClick(tracking, cta_text, cta_url),
2612
+ onClick: () => {
2613
+ trackClick(tracking, cta_text, cta_url);
2614
+ onEvent?.("cta_click", { label: cta_text, url: cta_url });
2615
+ },
2550
2616
  style: {
2551
2617
  display: "inline-block",
2552
2618
  padding: "0.875rem 2rem",
@@ -2632,7 +2698,7 @@ function GallerySection({ section, theme }) {
2632
2698
  }
2633
2699
  ) }, i)) }) });
2634
2700
  }
2635
- function CTASection({ section, theme, tracking }) {
2701
+ function CTASection({ section, theme, tracking, onEvent }) {
2636
2702
  const { title, subtitle, buttons } = section.content;
2637
2703
  if (!buttons || buttons.length === 0) return null;
2638
2704
  return /* @__PURE__ */ jsxs("div", { style: { padding: "2rem 1.5rem", maxWidth: 480, margin: "0 auto", display: "flex", flexDirection: "column", gap: "0.75rem" }, children: [
@@ -2661,7 +2727,10 @@ function CTASection({ section, theme, tracking }) {
2661
2727
  "a",
2662
2728
  {
2663
2729
  href: btn.url,
2664
- onClick: () => trackClick(tracking, btn.text, btn.url, i),
2730
+ onClick: () => {
2731
+ trackClick(tracking, btn.text, btn.url, i);
2732
+ onEvent?.("cta_click", { label: btn.text, url: btn.url });
2733
+ },
2665
2734
  style: {
2666
2735
  display: "block",
2667
2736
  width: "100%",
@@ -2702,7 +2771,7 @@ function StatsSection({ section, theme }) {
2702
2771
  letterSpacing: "0.15em",
2703
2772
  color: `${theme.fg}66`
2704
2773
  }, children: stat.label }),
2705
- /* @__PURE__ */ jsx("span", { style: { fontSize: 14, fontWeight: 300, color: `${theme.fg}CC` }, children: stat.value })
2774
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 14, fontWeight: 300, color: `${theme.fg}CC` }, children: /* @__PURE__ */ jsx(AnimatedText, { text: stat.value }) })
2706
2775
  ] }),
2707
2776
  i < stats.length - 1 && /* @__PURE__ */ jsx("hr", { style: { border: "none", borderTop: `1px solid ${theme.fg}0A`, margin: 0 } })
2708
2777
  ] }, i)) });
@@ -2723,7 +2792,7 @@ function StatsSection({ section, theme }) {
2723
2792
  fontWeight: 300,
2724
2793
  lineHeight: 1,
2725
2794
  color: theme.fg
2726
- }, children: stat.value }),
2795
+ }, children: /* @__PURE__ */ jsx(AnimatedText, { text: stat.value }) }),
2727
2796
  /* @__PURE__ */ jsx("div", { style: {
2728
2797
  fontSize: 11,
2729
2798
  fontWeight: 500,
@@ -2816,7 +2885,7 @@ function COAViewerSection({
2816
2885
  onShowCOA();
2817
2886
  }, style: buttonStyle, children: buttonLabel }) });
2818
2887
  }
2819
- function LeadCaptureSection({ section, data, theme }) {
2888
+ function LeadCaptureSection({ section, data, theme, onEvent }) {
2820
2889
  const c = section.content;
2821
2890
  const [firstName, setFirstName] = useState("");
2822
2891
  const [email, setEmail] = useState("");
@@ -2853,6 +2922,7 @@ function LeadCaptureSection({ section, data, theme }) {
2853
2922
  throw new Error(body?.error?.message || "Something went wrong. Please try again.");
2854
2923
  }
2855
2924
  setStatus("success");
2925
+ onEvent?.("lead", { email, first_name: firstName || void 0, source: c.source || "landing_page", landing_page_slug: slug || void 0 });
2856
2926
  } catch (err) {
2857
2927
  setErrorMsg(err instanceof Error ? err.message : "Something went wrong. Please try again.");
2858
2928
  setStatus("error");
@@ -3389,7 +3459,8 @@ function LandingPage({
3389
3459
  gatewayUrl = "https://whale-gateway.fly.dev",
3390
3460
  renderSection,
3391
3461
  onDataLoaded,
3392
- onError
3462
+ onError,
3463
+ onEvent
3393
3464
  }) {
3394
3465
  const [state, setState] = useState("loading");
3395
3466
  const [data, setData] = useState(null);
@@ -3437,12 +3508,13 @@ function LandingPage({
3437
3508
  if (state === "expired") return /* @__PURE__ */ jsx(DefaultExpired2, {});
3438
3509
  if (state === "error") return /* @__PURE__ */ jsx(DefaultError2, { message: errorMsg });
3439
3510
  if (!data) return null;
3440
- return /* @__PURE__ */ jsx(PageLayout, { data, gatewayUrl, renderSection });
3511
+ return /* @__PURE__ */ jsx(PageLayout, { data, gatewayUrl, renderSection, onEvent });
3441
3512
  }
3442
3513
  function PageLayout({
3443
3514
  data,
3444
3515
  gatewayUrl,
3445
- renderSection
3516
+ renderSection,
3517
+ onEvent
3446
3518
  }) {
3447
3519
  const { landing_page: lp, store } = data;
3448
3520
  const theme = {
@@ -3462,11 +3534,11 @@ function PageLayout({
3462
3534
  lp.custom_css && /* @__PURE__ */ jsx("style", { children: lp.custom_css }),
3463
3535
  logoUrl && /* @__PURE__ */ jsx("div", { style: { padding: "1.5rem", display: "flex", justifyContent: "center" }, children: /* @__PURE__ */ jsx("img", { src: logoUrl, alt: store?.name || "Store", style: { height: 40, objectFit: "contain" } }) }),
3464
3536
  sorted.map((section) => {
3465
- const defaultRenderer = () => /* @__PURE__ */ jsx(SectionRenderer, { section, data: sectionData, theme }, section.id);
3537
+ const defaultRenderer = () => /* @__PURE__ */ jsx(SectionRenderer, { section, data: sectionData, theme, onEvent }, section.id);
3466
3538
  if (renderSection) {
3467
3539
  return /* @__PURE__ */ jsx("div", { children: renderSection(section, defaultRenderer) }, section.id);
3468
3540
  }
3469
- return /* @__PURE__ */ jsx(SectionRenderer, { section, data: sectionData, theme }, section.id);
3541
+ return /* @__PURE__ */ jsx(SectionRenderer, { section, data: sectionData, theme, onEvent }, section.id);
3470
3542
  }),
3471
3543
  store?.name && /* @__PURE__ */ jsx("div", { style: { padding: "2rem 1.5rem", borderTop: `1px solid ${theme.surface}`, textAlign: "center" }, children: /* @__PURE__ */ jsxs("p", { style: { fontSize: "0.75rem", color: theme.muted, margin: 0 }, children: [
3472
3544
  "Powered by ",