@neowhale/storefront 0.2.19 → 0.2.21

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-D1XVKpFt.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-BSO263Uv.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-DZwpn_x2.cjs';
8
+ import { P as PixelManager } from '../pixel-manager-DJ9m2FaQ.cjs';
9
9
 
10
10
  interface WhaleProviderProps extends WhaleStorefrontConfig {
11
11
  children: ReactNode;
@@ -257,6 +257,8 @@ declare function useCheckout(): {
257
257
  billing_address?: Address;
258
258
  shipping_method_id?: string;
259
259
  coupon_code?: string;
260
+ loyalty_reward_id?: string;
261
+ selected_product_id?: string;
260
262
  }) => Promise<CheckoutSession>;
261
263
  updateSession: (params: {
262
264
  customer_email?: string;
@@ -265,7 +267,10 @@ declare function useCheckout(): {
265
267
  shipping_method_id?: string;
266
268
  coupon_code?: string;
267
269
  }) => Promise<CheckoutSession>;
268
- complete: (payment?: PaymentData) => Promise<Order>;
270
+ complete: (payment?: PaymentData, opts?: {
271
+ loyalty_reward_id?: string;
272
+ selected_product_id?: string;
273
+ }) => Promise<Order>;
269
274
  reset: () => void;
270
275
  };
271
276
 
@@ -308,6 +313,7 @@ declare function useLoyalty(): {
308
313
  success: boolean;
309
314
  points_remaining: number;
310
315
  }>;
316
+ fetchProductsByCategory: (category: string, locationId: string, tier?: string) => Promise<Product[]>;
311
317
  };
312
318
 
313
319
  declare function useReviews(productId: string | null | undefined): {
@@ -431,8 +437,13 @@ interface SectionTheme {
431
437
  fontDisplay?: string;
432
438
  fontBody?: string;
433
439
  }
440
+ interface ClickTrackingContext {
441
+ gatewayUrl?: string;
442
+ code?: string;
443
+ }
434
444
  interface SectionData {
435
445
  store?: {
446
+ id?: string;
436
447
  name?: string | null;
437
448
  logo_url?: string | null;
438
449
  tagline?: string | null;
@@ -444,11 +455,16 @@ interface SectionData {
444
455
  viewer_url?: string | null;
445
456
  document_name: string;
446
457
  } | null;
458
+ gatewayUrl?: string;
459
+ landing_page?: {
460
+ slug?: string;
461
+ } | null;
447
462
  }
448
- declare function SectionRenderer({ section, data, theme, }: {
463
+ declare function SectionRenderer({ section, data, theme, tracking, }: {
449
464
  section: LandingSection;
450
465
  data: SectionData;
451
466
  theme: SectionTheme;
467
+ tracking?: ClickTrackingContext;
452
468
  }): react_jsx_runtime.JSX.Element;
453
469
 
454
470
  /**
@@ -495,4 +511,4 @@ declare function FingerprintCollector(): null;
495
511
 
496
512
  declare function SessionRecorderComponent(): null;
497
513
 
498
- export { AnalyticsTracker, type AuthActions, AuthInitializer, type AuthState, type AuthStore, BehavioralTrackerComponent, type CartActions, CartInitializer, type CartState, type CartStore, FingerprintCollector, LandingPage, type LandingPageProps, PixelInitializer, QRLandingPage, type QRLandingPageProps, type SearchParams, SectionRenderer, type SectionTheme, SessionRecorderComponent, WhaleContext, type WhaleContextValue, WhaleProvider, type WhaleProviderProps, useAnalytics, useAuth, useCart, useCartItemCount, useCartTotal, useCategories, useCheckout, useCoupons, useCustomerAnalytics, useCustomerOrders, useDeals, useLocations, useLoyalty, useProduct, useProducts, useRecommendations, useReferral, useReviews, useSearch, useShipping, useWhaleClient, useWishlist };
514
+ export { AnalyticsTracker, type AuthActions, AuthInitializer, type AuthState, type AuthStore, BehavioralTrackerComponent, type CartActions, CartInitializer, type CartState, type CartStore, type ClickTrackingContext, FingerprintCollector, LandingPage, type LandingPageProps, PixelInitializer, QRLandingPage, type QRLandingPageProps, type SearchParams, SectionRenderer, type SectionTheme, SessionRecorderComponent, WhaleContext, type WhaleContextValue, WhaleProvider, type WhaleProviderProps, useAnalytics, useAuth, useCart, useCartItemCount, useCartTotal, useCategories, useCheckout, useCoupons, useCustomerAnalytics, useCustomerOrders, useDeals, useLocations, useLoyalty, useProduct, useProducts, useRecommendations, useReferral, useReviews, useSearch, useShipping, useWhaleClient, useWishlist };
@@ -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-D1XVKpFt.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-BSO263Uv.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-C6PAp7vQ.js';
8
+ import { P as PixelManager } from '../pixel-manager-BcL95odX.js';
9
9
 
10
10
  interface WhaleProviderProps extends WhaleStorefrontConfig {
11
11
  children: ReactNode;
@@ -257,6 +257,8 @@ declare function useCheckout(): {
257
257
  billing_address?: Address;
258
258
  shipping_method_id?: string;
259
259
  coupon_code?: string;
260
+ loyalty_reward_id?: string;
261
+ selected_product_id?: string;
260
262
  }) => Promise<CheckoutSession>;
261
263
  updateSession: (params: {
262
264
  customer_email?: string;
@@ -265,7 +267,10 @@ declare function useCheckout(): {
265
267
  shipping_method_id?: string;
266
268
  coupon_code?: string;
267
269
  }) => Promise<CheckoutSession>;
268
- complete: (payment?: PaymentData) => Promise<Order>;
270
+ complete: (payment?: PaymentData, opts?: {
271
+ loyalty_reward_id?: string;
272
+ selected_product_id?: string;
273
+ }) => Promise<Order>;
269
274
  reset: () => void;
270
275
  };
271
276
 
@@ -308,6 +313,7 @@ declare function useLoyalty(): {
308
313
  success: boolean;
309
314
  points_remaining: number;
310
315
  }>;
316
+ fetchProductsByCategory: (category: string, locationId: string, tier?: string) => Promise<Product[]>;
311
317
  };
312
318
 
313
319
  declare function useReviews(productId: string | null | undefined): {
@@ -431,8 +437,13 @@ interface SectionTheme {
431
437
  fontDisplay?: string;
432
438
  fontBody?: string;
433
439
  }
440
+ interface ClickTrackingContext {
441
+ gatewayUrl?: string;
442
+ code?: string;
443
+ }
434
444
  interface SectionData {
435
445
  store?: {
446
+ id?: string;
436
447
  name?: string | null;
437
448
  logo_url?: string | null;
438
449
  tagline?: string | null;
@@ -444,11 +455,16 @@ interface SectionData {
444
455
  viewer_url?: string | null;
445
456
  document_name: string;
446
457
  } | null;
458
+ gatewayUrl?: string;
459
+ landing_page?: {
460
+ slug?: string;
461
+ } | null;
447
462
  }
448
- declare function SectionRenderer({ section, data, theme, }: {
463
+ declare function SectionRenderer({ section, data, theme, tracking, }: {
449
464
  section: LandingSection;
450
465
  data: SectionData;
451
466
  theme: SectionTheme;
467
+ tracking?: ClickTrackingContext;
452
468
  }): react_jsx_runtime.JSX.Element;
453
469
 
454
470
  /**
@@ -495,4 +511,4 @@ declare function FingerprintCollector(): null;
495
511
 
496
512
  declare function SessionRecorderComponent(): null;
497
513
 
498
- export { AnalyticsTracker, type AuthActions, AuthInitializer, type AuthState, type AuthStore, BehavioralTrackerComponent, type CartActions, CartInitializer, type CartState, type CartStore, FingerprintCollector, LandingPage, type LandingPageProps, PixelInitializer, QRLandingPage, type QRLandingPageProps, type SearchParams, SectionRenderer, type SectionTheme, SessionRecorderComponent, WhaleContext, type WhaleContextValue, WhaleProvider, type WhaleProviderProps, useAnalytics, useAuth, useCart, useCartItemCount, useCartTotal, useCategories, useCheckout, useCoupons, useCustomerAnalytics, useCustomerOrders, useDeals, useLocations, useLoyalty, useProduct, useProducts, useRecommendations, useReferral, useReviews, useSearch, useShipping, useWhaleClient, useWishlist };
514
+ export { AnalyticsTracker, type AuthActions, AuthInitializer, type AuthState, type AuthStore, BehavioralTrackerComponent, type CartActions, CartInitializer, type CartState, type CartStore, type ClickTrackingContext, FingerprintCollector, LandingPage, type LandingPageProps, PixelInitializer, QRLandingPage, type QRLandingPageProps, type SearchParams, SectionRenderer, type SectionTheme, SessionRecorderComponent, WhaleContext, type WhaleContextValue, WhaleProvider, type WhaleProviderProps, useAnalytics, useAuth, useCart, useCartItemCount, useCartTotal, useCategories, useCheckout, useCoupons, useCustomerAnalytics, useCustomerOrders, useDeals, useLocations, useLoyalty, useProduct, useProducts, useRecommendations, useReferral, useReviews, useSearch, useShipping, useWhaleClient, useWishlist };
@@ -1,5 +1,5 @@
1
1
  import { PixelManager } from '../chunk-PXS2DPVL.js';
2
- import { resilientSend, WhaleClient } from '../chunk-XHWAUMWS.js';
2
+ import { resilientSend, WhaleClient } from '../chunk-3Q7CPJBA.js';
3
3
  import { createContext, useContext, useRef, useCallback, useEffect, useState, useMemo } from 'react';
4
4
  import { usePathname } from 'next/navigation';
5
5
  import { createStore } from 'zustand/vanilla';
@@ -1859,12 +1859,12 @@ function useCheckout() {
1859
1859
  setLoading(false);
1860
1860
  }
1861
1861
  }, [ctx.client, session]);
1862
- const complete = useCallback(async (payment) => {
1862
+ const complete = useCallback(async (payment, opts) => {
1863
1863
  if (!session) throw new Error("No active checkout session");
1864
1864
  setLoading(true);
1865
1865
  setError(null);
1866
1866
  try {
1867
- const order = await ctx.client.completeCheckout(session.id, payment);
1867
+ const order = await ctx.client.completeCheckout(session.id, payment, opts);
1868
1868
  setSession(null);
1869
1869
  return order;
1870
1870
  } catch (err) {
@@ -2018,7 +2018,11 @@ function useLoyalty() {
2018
2018
  await refresh();
2019
2019
  return result;
2020
2020
  }, [customer?.id, ctx.client, refresh]);
2021
- return { account, rewards, transactions, loading, error, refresh, redeemReward };
2021
+ const fetchProductsByCategory = useCallback(async (category, locationId, tier) => {
2022
+ const res = await ctx.client.listLoyaltyProducts({ category, location_id: locationId, tier });
2023
+ return res.data;
2024
+ }, [ctx.client]);
2025
+ return { account, rewards, transactions, loading, error, refresh, redeemReward, fetchProductsByCategory };
2022
2026
  }
2023
2027
  function useReviews(productId) {
2024
2028
  const ctx = useContext(WhaleContext);
@@ -2409,16 +2413,27 @@ function useReferral() {
2409
2413
  referredBy: status?.referred_by ?? null
2410
2414
  };
2411
2415
  }
2416
+ function trackClick(tracking, label, url, position) {
2417
+ if (!tracking?.gatewayUrl || !tracking?.code) return;
2418
+ const body = JSON.stringify({ label, url, position });
2419
+ if (typeof navigator !== "undefined" && navigator.sendBeacon) {
2420
+ navigator.sendBeacon(
2421
+ `${tracking.gatewayUrl}/q/${encodeURIComponent(tracking.code)}/click`,
2422
+ new Blob([body], { type: "application/json" })
2423
+ );
2424
+ }
2425
+ }
2412
2426
  function SectionRenderer({
2413
2427
  section,
2414
2428
  data,
2415
- theme
2429
+ theme,
2430
+ tracking
2416
2431
  }) {
2417
2432
  const [showCOA, setShowCOA] = useState(false);
2418
2433
  const el = (() => {
2419
2434
  switch (section.type) {
2420
2435
  case "hero":
2421
- return /* @__PURE__ */ jsx(HeroSection, { section, theme });
2436
+ return /* @__PURE__ */ jsx(HeroSection, { section, theme, tracking });
2422
2437
  case "text":
2423
2438
  return /* @__PURE__ */ jsx(TextSection, { section, theme });
2424
2439
  case "image":
@@ -2428,15 +2443,17 @@ function SectionRenderer({
2428
2443
  case "gallery":
2429
2444
  return /* @__PURE__ */ jsx(GallerySection, { section, theme });
2430
2445
  case "cta":
2431
- return /* @__PURE__ */ jsx(CTASection, { section, theme });
2446
+ return /* @__PURE__ */ jsx(CTASection, { section, theme, tracking });
2432
2447
  case "stats":
2433
2448
  return /* @__PURE__ */ jsx(StatsSection, { section, theme });
2434
2449
  case "product_card":
2435
- return /* @__PURE__ */ jsx(ProductCardSection, { section, data, theme });
2450
+ return /* @__PURE__ */ jsx(ProductCardSection, { section, data, theme, tracking });
2436
2451
  case "coa_viewer":
2437
- return /* @__PURE__ */ jsx(COAViewerSection, { section, data, theme, onShowCOA: () => setShowCOA(true) });
2452
+ return /* @__PURE__ */ jsx(COAViewerSection, { section, data, theme, onShowCOA: () => setShowCOA(true), tracking });
2438
2453
  case "social_links":
2439
2454
  return /* @__PURE__ */ jsx(SocialLinksSection, { section, theme });
2455
+ case "lead_capture":
2456
+ return /* @__PURE__ */ jsx(LeadCaptureSection, { section, data, theme });
2440
2457
  case "divider":
2441
2458
  return /* @__PURE__ */ jsx(DividerSection, { theme });
2442
2459
  default:
@@ -2448,7 +2465,7 @@ function SectionRenderer({
2448
2465
  showCOA && data?.coa && /* @__PURE__ */ jsx(COAModal, { coa: data.coa, theme, onClose: () => setShowCOA(false) })
2449
2466
  ] });
2450
2467
  }
2451
- function HeroSection({ section, theme }) {
2468
+ function HeroSection({ section, theme, tracking }) {
2452
2469
  const { title, subtitle, background_image, cta_text, cta_url } = section.content;
2453
2470
  return /* @__PURE__ */ jsxs(
2454
2471
  "div",
@@ -2490,6 +2507,7 @@ function HeroSection({ section, theme }) {
2490
2507
  "a",
2491
2508
  {
2492
2509
  href: cta_url,
2510
+ onClick: () => trackClick(tracking, cta_text, cta_url),
2493
2511
  style: {
2494
2512
  display: "inline-block",
2495
2513
  padding: "0.875rem 2rem",
@@ -2575,35 +2593,57 @@ function GallerySection({ section, theme }) {
2575
2593
  }
2576
2594
  ) }, i)) }) });
2577
2595
  }
2578
- function CTASection({ section, theme }) {
2579
- const { buttons } = section.content;
2596
+ function CTASection({ section, theme, tracking }) {
2597
+ const { title, subtitle, buttons } = section.content;
2580
2598
  if (!buttons || buttons.length === 0) return null;
2581
- return /* @__PURE__ */ jsx("div", { style: { padding: "2rem 1.5rem", maxWidth: 480, margin: "0 auto", display: "flex", flexDirection: "column", gap: "0.75rem" }, children: buttons.map((btn, i) => {
2582
- const isPrimary = btn.style !== "outline";
2583
- return /* @__PURE__ */ jsx(
2584
- "a",
2585
- {
2586
- href: btn.url,
2587
- style: {
2588
- display: "block",
2589
- width: "100%",
2590
- padding: "0.875rem",
2591
- background: isPrimary ? theme.fg : "transparent",
2592
- color: isPrimary ? theme.bg : theme.fg,
2593
- border: isPrimary ? "none" : `1px solid ${theme.fg}20`,
2594
- fontSize: "0.85rem",
2595
- fontWeight: 500,
2596
- textAlign: "center",
2597
- textDecoration: "none",
2598
- boxSizing: "border-box",
2599
- letterSpacing: "0.08em",
2600
- textTransform: "uppercase"
2599
+ return /* @__PURE__ */ jsxs("div", { style: { padding: "2rem 1.5rem", maxWidth: 480, margin: "0 auto", display: "flex", flexDirection: "column", gap: "0.75rem" }, children: [
2600
+ title && /* @__PURE__ */ jsx("h2", { style: {
2601
+ fontSize: "clamp(1.25rem, 4vw, 1.5rem)",
2602
+ fontWeight: 300,
2603
+ fontFamily: theme.fontDisplay || "inherit",
2604
+ margin: "0 0 0.25rem",
2605
+ lineHeight: 1.2,
2606
+ letterSpacing: "-0.02em",
2607
+ color: theme.fg,
2608
+ textAlign: "center"
2609
+ }, children: title }),
2610
+ subtitle && /* @__PURE__ */ jsx("p", { style: {
2611
+ fontSize: "0.8rem",
2612
+ color: theme.accent,
2613
+ margin: "0 0 0.75rem",
2614
+ lineHeight: 1.6,
2615
+ textTransform: "uppercase",
2616
+ letterSpacing: "0.15em",
2617
+ textAlign: "center"
2618
+ }, children: subtitle }),
2619
+ buttons.map((btn, i) => {
2620
+ const isPrimary = btn.style !== "outline";
2621
+ return /* @__PURE__ */ jsx(
2622
+ "a",
2623
+ {
2624
+ href: btn.url,
2625
+ onClick: () => trackClick(tracking, btn.text, btn.url, i),
2626
+ style: {
2627
+ display: "block",
2628
+ width: "100%",
2629
+ padding: "0.875rem",
2630
+ background: isPrimary ? theme.fg : "transparent",
2631
+ color: isPrimary ? theme.bg : theme.fg,
2632
+ border: isPrimary ? "none" : `1px solid ${theme.fg}20`,
2633
+ fontSize: "0.85rem",
2634
+ fontWeight: 500,
2635
+ textAlign: "center",
2636
+ textDecoration: "none",
2637
+ boxSizing: "border-box",
2638
+ letterSpacing: "0.08em",
2639
+ textTransform: "uppercase"
2640
+ },
2641
+ children: btn.text
2601
2642
  },
2602
- children: btn.text
2603
- },
2604
- i
2605
- );
2606
- }) });
2643
+ i
2644
+ );
2645
+ })
2646
+ ] });
2607
2647
  }
2608
2648
  function StatsSection({ section, theme }) {
2609
2649
  const { stats } = section.content;
@@ -2655,7 +2695,7 @@ function StatsSection({ section, theme }) {
2655
2695
  }, children: stat.label })
2656
2696
  ] }, i)) }) });
2657
2697
  }
2658
- function ProductCardSection({ section, data, theme }) {
2698
+ function ProductCardSection({ section, data, theme, tracking }) {
2659
2699
  const product = data?.product;
2660
2700
  const c = section.content;
2661
2701
  const name = c.name || product?.name || "";
@@ -2671,6 +2711,7 @@ function ProductCardSection({ section, data, theme }) {
2671
2711
  "a",
2672
2712
  {
2673
2713
  href: url,
2714
+ onClick: () => trackClick(tracking, "View Product", url),
2674
2715
  style: {
2675
2716
  display: "block",
2676
2717
  width: "100%",
@@ -2695,7 +2736,8 @@ function COAViewerSection({
2695
2736
  section,
2696
2737
  data,
2697
2738
  theme,
2698
- onShowCOA
2739
+ onShowCOA,
2740
+ tracking
2699
2741
  }) {
2700
2742
  const coa = data?.coa;
2701
2743
  const c = section.content;
@@ -2716,10 +2758,200 @@ function COAViewerSection({
2716
2758
  display: "block",
2717
2759
  boxSizing: "border-box"
2718
2760
  };
2761
+ const buttonLabel = c.button_text || "View Lab Results";
2719
2762
  if (coa.viewer_url) {
2720
- return /* @__PURE__ */ jsx("div", { style: { padding: "1.5rem", maxWidth: 480, margin: "0 auto" }, children: /* @__PURE__ */ jsx("a", { href: coa.viewer_url, target: "_blank", rel: "noopener noreferrer", style: buttonStyle, children: c.button_text || "View Lab Results" }) });
2763
+ return /* @__PURE__ */ jsx("div", { style: { padding: "1.5rem", maxWidth: 480, margin: "0 auto" }, children: /* @__PURE__ */ jsx(
2764
+ "a",
2765
+ {
2766
+ href: coa.viewer_url,
2767
+ target: "_blank",
2768
+ rel: "noopener noreferrer",
2769
+ onClick: () => trackClick(tracking, buttonLabel, coa.viewer_url),
2770
+ style: buttonStyle,
2771
+ children: buttonLabel
2772
+ }
2773
+ ) });
2721
2774
  }
2722
- return /* @__PURE__ */ jsx("div", { style: { padding: "1.5rem", maxWidth: 480, margin: "0 auto" }, children: /* @__PURE__ */ jsx("button", { onClick: onShowCOA, style: buttonStyle, children: c.button_text || "View Lab Results" }) });
2775
+ return /* @__PURE__ */ jsx("div", { style: { padding: "1.5rem", maxWidth: 480, margin: "0 auto" }, children: /* @__PURE__ */ jsx("button", { onClick: () => {
2776
+ trackClick(tracking, buttonLabel, coa.url);
2777
+ onShowCOA();
2778
+ }, style: buttonStyle, children: buttonLabel }) });
2779
+ }
2780
+ function LeadCaptureSection({ section, data, theme }) {
2781
+ const c = section.content;
2782
+ const [firstName, setFirstName] = useState("");
2783
+ const [email, setEmail] = useState("");
2784
+ const [status, setStatus] = useState("idle");
2785
+ const [errorMsg, setErrorMsg] = useState("");
2786
+ const gatewayUrl = c.gateway_url || data.gatewayUrl || "https://whale-gateway.fly.dev";
2787
+ const storeId = c.store_id || data.store?.id;
2788
+ const slug = c.landing_page_slug || data.landing_page?.slug;
2789
+ async function handleSubmit(e) {
2790
+ e.preventDefault();
2791
+ if (!email || !storeId) return;
2792
+ setStatus("loading");
2793
+ setErrorMsg("");
2794
+ try {
2795
+ const res = await fetch(`${gatewayUrl}/v1/stores/${storeId}/storefront/leads`, {
2796
+ method: "POST",
2797
+ headers: { "Content-Type": "application/json" },
2798
+ body: JSON.stringify({
2799
+ email,
2800
+ first_name: firstName || void 0,
2801
+ source: c.source || "landing_page",
2802
+ landing_page_slug: slug || void 0,
2803
+ tags: c.tags || void 0
2804
+ })
2805
+ });
2806
+ if (!res.ok) {
2807
+ const body = await res.json().catch(() => ({}));
2808
+ throw new Error(body?.error?.message || "Something went wrong. Please try again.");
2809
+ }
2810
+ setStatus("success");
2811
+ } catch (err) {
2812
+ setErrorMsg(err instanceof Error ? err.message : "Something went wrong. Please try again.");
2813
+ setStatus("error");
2814
+ }
2815
+ }
2816
+ const heading = c.heading || "get 10% off your first visit.";
2817
+ const subtitle = c.subtitle || "drop your email and we will send you the code.";
2818
+ const buttonText = c.button_text || "Claim My Discount";
2819
+ const successHeading = c.success_heading || "You\u2019re in!";
2820
+ const successMessage = c.success_message || "Check your inbox for the discount code.";
2821
+ const inputStyle = {
2822
+ flex: 1,
2823
+ minWidth: 0,
2824
+ padding: "0.875rem 1rem",
2825
+ background: theme.surface,
2826
+ border: `1px solid ${theme.fg}15`,
2827
+ color: theme.fg,
2828
+ fontSize: "0.95rem",
2829
+ fontWeight: 300,
2830
+ outline: "none",
2831
+ boxSizing: "border-box",
2832
+ fontFamily: "inherit",
2833
+ transition: "border-color 0.2s"
2834
+ };
2835
+ return /* @__PURE__ */ jsxs("div", { style: { padding: "3.5rem 1.5rem", maxWidth: 560, margin: "0 auto" }, children: [
2836
+ /* @__PURE__ */ jsx("style", { children: `@keyframes lc-spin { to { transform: rotate(360deg) } }` }),
2837
+ /* @__PURE__ */ jsx("div", { style: {
2838
+ background: theme.surface,
2839
+ border: `1px solid ${theme.fg}12`,
2840
+ padding: "clamp(2rem, 6vw, 3rem)"
2841
+ }, children: status === "success" ? /* @__PURE__ */ jsxs("div", { style: { textAlign: "center" }, children: [
2842
+ /* @__PURE__ */ jsx("h2", { style: {
2843
+ fontSize: "clamp(1.5rem, 5vw, 2rem)",
2844
+ fontWeight: 300,
2845
+ fontFamily: theme.fontDisplay || "inherit",
2846
+ margin: "0 0 0.75rem",
2847
+ lineHeight: 1.2,
2848
+ letterSpacing: "-0.02em",
2849
+ color: theme.fg
2850
+ }, children: successHeading }),
2851
+ /* @__PURE__ */ jsx("p", { style: {
2852
+ fontSize: "0.9rem",
2853
+ color: `${theme.fg}99`,
2854
+ margin: "0 0 1.5rem",
2855
+ lineHeight: 1.6,
2856
+ fontWeight: 300
2857
+ }, children: successMessage }),
2858
+ c.coupon_code && /* @__PURE__ */ jsx("div", { style: {
2859
+ display: "inline-block",
2860
+ padding: "0.75rem 2rem",
2861
+ background: `${theme.fg}08`,
2862
+ border: `1px dashed ${theme.fg}30`,
2863
+ fontSize: "clamp(1.25rem, 4vw, 1.75rem)",
2864
+ fontWeight: 500,
2865
+ fontFamily: "monospace",
2866
+ letterSpacing: "0.12em",
2867
+ color: theme.accent
2868
+ }, children: c.coupon_code })
2869
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2870
+ /* @__PURE__ */ jsxs("div", { style: { textAlign: "center", marginBottom: "clamp(1.5rem, 4vw, 2rem)" }, children: [
2871
+ /* @__PURE__ */ jsx("h2", { style: {
2872
+ fontSize: "clamp(1.5rem, 5vw, 2.25rem)",
2873
+ fontWeight: 300,
2874
+ fontFamily: theme.fontDisplay || "inherit",
2875
+ margin: "0 0 0.5rem",
2876
+ lineHeight: 1.15,
2877
+ letterSpacing: "-0.02em",
2878
+ color: theme.fg
2879
+ }, children: heading }),
2880
+ /* @__PURE__ */ jsx("p", { style: {
2881
+ fontSize: "0.85rem",
2882
+ color: theme.accent,
2883
+ margin: 0,
2884
+ lineHeight: 1.6,
2885
+ textTransform: "uppercase",
2886
+ letterSpacing: "0.15em"
2887
+ }, children: subtitle })
2888
+ ] }),
2889
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, style: { display: "flex", flexDirection: "column", gap: "0.75rem" }, children: [
2890
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "0.75rem", flexWrap: "wrap" }, children: [
2891
+ /* @__PURE__ */ jsx(
2892
+ "input",
2893
+ {
2894
+ type: "text",
2895
+ placeholder: "First name",
2896
+ value: firstName,
2897
+ onChange: (e) => setFirstName(e.target.value),
2898
+ style: inputStyle
2899
+ }
2900
+ ),
2901
+ /* @__PURE__ */ jsx(
2902
+ "input",
2903
+ {
2904
+ type: "email",
2905
+ placeholder: "Email address",
2906
+ value: email,
2907
+ onChange: (e) => setEmail(e.target.value),
2908
+ required: true,
2909
+ style: inputStyle
2910
+ }
2911
+ )
2912
+ ] }),
2913
+ status === "error" && errorMsg && /* @__PURE__ */ jsx("p", { style: { fontSize: "0.8rem", color: "#e55", margin: 0, fontWeight: 400 }, children: errorMsg }),
2914
+ /* @__PURE__ */ jsxs(
2915
+ "button",
2916
+ {
2917
+ type: "submit",
2918
+ disabled: status === "loading",
2919
+ style: {
2920
+ width: "100%",
2921
+ padding: "0.875rem",
2922
+ background: theme.fg,
2923
+ color: theme.bg,
2924
+ border: "none",
2925
+ fontSize: "0.85rem",
2926
+ fontWeight: 500,
2927
+ cursor: status === "loading" ? "wait" : "pointer",
2928
+ letterSpacing: "0.08em",
2929
+ textTransform: "uppercase",
2930
+ fontFamily: "inherit",
2931
+ display: "flex",
2932
+ alignItems: "center",
2933
+ justifyContent: "center",
2934
+ gap: "0.5rem",
2935
+ opacity: status === "loading" ? 0.7 : 1,
2936
+ transition: "opacity 0.2s"
2937
+ },
2938
+ children: [
2939
+ status === "loading" && /* @__PURE__ */ jsx("span", { style: {
2940
+ display: "inline-block",
2941
+ width: 16,
2942
+ height: 16,
2943
+ border: `2px solid ${theme.bg}40`,
2944
+ borderTopColor: theme.bg,
2945
+ borderRadius: "50%",
2946
+ animation: "lc-spin 0.8s linear infinite"
2947
+ } }),
2948
+ buttonText
2949
+ ]
2950
+ }
2951
+ )
2952
+ ] })
2953
+ ] }) })
2954
+ ] });
2723
2955
  }
2724
2956
  function SocialLinksSection({ section, theme }) {
2725
2957
  const { links } = section.content;
@@ -2842,15 +3074,16 @@ function QRLandingPage({
2842
3074
  const logoUrl = data.qr_code.logo_url || data.store?.logo_url;
2843
3075
  const storeName = data.store?.name;
2844
3076
  const sorted = [...sections].sort((a, b) => a.order - b.order);
3077
+ const tracking = { gatewayUrl, code };
2845
3078
  return /* @__PURE__ */ jsxs("div", { style: { minHeight: "100dvh", background: theme.bg, color: theme.fg, fontFamily }, children: [
2846
3079
  lp?.custom_css && /* @__PURE__ */ jsx("style", { children: lp.custom_css }),
2847
3080
  logoUrl && /* @__PURE__ */ jsx("div", { style: { padding: "1.5rem", display: "flex", justifyContent: "center" }, children: /* @__PURE__ */ jsx("img", { src: logoUrl, alt: storeName || "Store", style: { height: 40, objectFit: "contain" } }) }),
2848
3081
  sorted.map((section) => {
2849
- const defaultRenderer = () => /* @__PURE__ */ jsx(SectionRenderer, { section, data, theme }, section.id);
3082
+ const defaultRenderer = () => /* @__PURE__ */ jsx(SectionRenderer, { section, data, theme, tracking }, section.id);
2850
3083
  if (renderSection) {
2851
3084
  return /* @__PURE__ */ jsx("div", { children: renderSection(section, defaultRenderer) }, section.id);
2852
3085
  }
2853
- return /* @__PURE__ */ jsx(SectionRenderer, { section, data, theme }, section.id);
3086
+ return /* @__PURE__ */ jsx(SectionRenderer, { section, data, theme, tracking }, section.id);
2854
3087
  }),
2855
3088
  storeName && /* @__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: [
2856
3089
  storeName,
@@ -3100,10 +3333,11 @@ function LandingPage({
3100
3333
  if (state === "expired") return /* @__PURE__ */ jsx(DefaultExpired2, {});
3101
3334
  if (state === "error") return /* @__PURE__ */ jsx(DefaultError2, { message: errorMsg });
3102
3335
  if (!data) return null;
3103
- return /* @__PURE__ */ jsx(PageLayout, { data, renderSection });
3336
+ return /* @__PURE__ */ jsx(PageLayout, { data, gatewayUrl, renderSection });
3104
3337
  }
3105
3338
  function PageLayout({
3106
3339
  data,
3340
+ gatewayUrl,
3107
3341
  renderSection
3108
3342
  }) {
3109
3343
  const { landing_page: lp, store } = data;
@@ -3119,15 +3353,16 @@ function PageLayout({
3119
3353
  const fontFamily = lp.font_family || theme.fontDisplay || "system-ui, -apple-system, sans-serif";
3120
3354
  const logoUrl = store?.logo_url;
3121
3355
  const sorted = [...lp.sections].sort((a, b) => a.order - b.order);
3356
+ const sectionData = { ...data, gatewayUrl, landing_page: { slug: lp.slug } };
3122
3357
  return /* @__PURE__ */ jsxs("div", { style: { minHeight: "100dvh", background: theme.bg, color: theme.fg, fontFamily }, children: [
3123
3358
  lp.custom_css && /* @__PURE__ */ jsx("style", { children: lp.custom_css }),
3124
3359
  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" } }) }),
3125
3360
  sorted.map((section) => {
3126
- const defaultRenderer = () => /* @__PURE__ */ jsx(SectionRenderer, { section, data, theme }, section.id);
3361
+ const defaultRenderer = () => /* @__PURE__ */ jsx(SectionRenderer, { section, data: sectionData, theme }, section.id);
3127
3362
  if (renderSection) {
3128
3363
  return /* @__PURE__ */ jsx("div", { children: renderSection(section, defaultRenderer) }, section.id);
3129
3364
  }
3130
- return /* @__PURE__ */ jsx(SectionRenderer, { section, data, theme }, section.id);
3365
+ return /* @__PURE__ */ jsx(SectionRenderer, { section, data: sectionData, theme }, section.id);
3131
3366
  }),
3132
3367
  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: [
3133
3368
  "Powered by ",