@neowhale/storefront 0.2.5 → 0.2.7

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,12 +1,13 @@
1
1
  import { PixelManager } from '../chunk-NLH3W6JA.js';
2
- import { WhaleClient } from '../chunk-NXNLD7YI.js';
2
+ import { WhaleClient } from '../chunk-M2MR6C55.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';
6
6
  import { persist } from 'zustand/middleware';
7
7
  import { useStore } from 'zustand';
8
8
  import { useShallow } from 'zustand/react/shallow';
9
- import { jsxs, jsx } from 'react/jsx-runtime';
9
+ import { ThemeProvider } from '@neowhale/ui';
10
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
10
11
 
11
12
  var WhaleContext = createContext(null);
12
13
  function createCartStore(client, storagePrefix, onAddToCart, onRemoveFromCart) {
@@ -534,23 +535,26 @@ function AuthInitializer() {
534
535
  }, []);
535
536
  return null;
536
537
  }
537
- function PixelInitializer({ onReady }) {
538
+ function PixelInitializer({ onReady, onTheme }) {
538
539
  const ctx = useContext(WhaleContext);
539
540
  const initialized = useRef(false);
540
541
  useEffect(() => {
541
542
  if (!ctx || initialized.current) return;
542
543
  if (typeof window === "undefined") return;
543
- if (!ctx.config.trackingEnabled) return;
544
544
  initialized.current = true;
545
545
  const { client } = ctx;
546
546
  client.fetchStorefrontConfig().then(async (config) => {
547
- if (!config.pixels || config.pixels.length === 0) return;
548
- const manager = new PixelManager(config.pixels);
549
- await manager.initialize();
550
- onReady(manager);
547
+ if (config.theme && onTheme) {
548
+ onTheme(config.theme);
549
+ }
550
+ if (ctx.config.trackingEnabled && config.pixels && config.pixels.length > 0) {
551
+ const manager = new PixelManager(config.pixels);
552
+ await manager.initialize();
553
+ onReady(manager);
554
+ }
551
555
  }).catch(() => {
552
556
  });
553
- }, [ctx, onReady]);
557
+ }, [ctx, onReady, onTheme]);
554
558
  return null;
555
559
  }
556
560
  function envBool(name) {
@@ -569,6 +573,7 @@ function envNumber(name) {
569
573
  function WhaleProvider({
570
574
  children,
571
575
  products = [],
576
+ theme: themeProp,
572
577
  storeId,
573
578
  apiKey,
574
579
  gatewayUrl,
@@ -583,9 +588,13 @@ function WhaleProvider({
583
588
  }) {
584
589
  const pathname = usePathname();
585
590
  const [pixelManager, setPixelManager] = useState(null);
591
+ const [storeTheme, setStoreTheme] = useState(void 0);
586
592
  const handlePixelReady = useCallback((manager) => {
587
593
  setPixelManager(manager);
588
594
  }, []);
595
+ const handleTheme = useCallback((theme) => {
596
+ setStoreTheme(theme);
597
+ }, []);
589
598
  const ctx = useMemo(() => {
590
599
  const resolvedConfig = {
591
600
  storeId,
@@ -621,13 +630,17 @@ function WhaleProvider({
621
630
  () => ({ ...ctx, products, pixelManager }),
622
631
  [ctx, products, pixelManager]
623
632
  );
624
- return /* @__PURE__ */ jsxs(WhaleContext.Provider, { value, children: [
633
+ const mergedTheme = useMemo(() => {
634
+ if (!storeTheme && !themeProp) return void 0;
635
+ return { ...storeTheme, ...themeProp };
636
+ }, [storeTheme, themeProp]);
637
+ return /* @__PURE__ */ jsx(WhaleContext.Provider, { value, children: /* @__PURE__ */ jsxs(ThemeProvider, { theme: mergedTheme, children: [
625
638
  /* @__PURE__ */ jsx(AuthInitializer, {}),
626
639
  /* @__PURE__ */ jsx(CartInitializer, {}),
627
640
  /* @__PURE__ */ jsx(AnalyticsTracker, { pathname }),
628
- /* @__PURE__ */ jsx(PixelInitializer, { onReady: handlePixelReady }),
641
+ /* @__PURE__ */ jsx(PixelInitializer, { onReady: handlePixelReady, onTheme: handleTheme }),
629
642
  children
630
- ] });
643
+ ] }) });
631
644
  }
632
645
  function useProducts(opts) {
633
646
  const ctx = useContext(WhaleContext);
@@ -712,7 +725,1210 @@ function useCustomerAnalytics() {
712
725
  }, [customer?.id, customer?.first_name, customer?.last_name, ctx.client]);
713
726
  return { analytics, loading };
714
727
  }
728
+ function useCheckout() {
729
+ const ctx = useContext(WhaleContext);
730
+ if (!ctx) throw new Error("useCheckout must be used within <WhaleProvider>");
731
+ const [session, setSession] = useState(null);
732
+ const [loading, setLoading] = useState(false);
733
+ const [error, setError] = useState(null);
734
+ const createSession = useCallback(async (params) => {
735
+ setLoading(true);
736
+ setError(null);
737
+ try {
738
+ const data = await ctx.client.createCheckoutSession(params);
739
+ setSession(data);
740
+ return data;
741
+ } catch (err) {
742
+ const e = err instanceof Error ? err : new Error(String(err));
743
+ setError(e);
744
+ throw e;
745
+ } finally {
746
+ setLoading(false);
747
+ }
748
+ }, [ctx.client]);
749
+ const updateSession = useCallback(async (params) => {
750
+ if (!session) throw new Error("No active checkout session");
751
+ setLoading(true);
752
+ setError(null);
753
+ try {
754
+ const data = await ctx.client.updateCheckoutSession(session.id, params);
755
+ setSession(data);
756
+ return data;
757
+ } catch (err) {
758
+ const e = err instanceof Error ? err : new Error(String(err));
759
+ setError(e);
760
+ throw e;
761
+ } finally {
762
+ setLoading(false);
763
+ }
764
+ }, [ctx.client, session]);
765
+ const complete = useCallback(async (payment) => {
766
+ if (!session) throw new Error("No active checkout session");
767
+ setLoading(true);
768
+ setError(null);
769
+ try {
770
+ const order = await ctx.client.completeCheckout(session.id, payment);
771
+ setSession(null);
772
+ return order;
773
+ } catch (err) {
774
+ const e = err instanceof Error ? err : new Error(String(err));
775
+ setError(e);
776
+ throw e;
777
+ } finally {
778
+ setLoading(false);
779
+ }
780
+ }, [ctx.client, session]);
781
+ const reset = useCallback(() => {
782
+ setSession(null);
783
+ setError(null);
784
+ }, []);
785
+ return { session, loading, error, createSession, updateSession, complete, reset };
786
+ }
787
+ function useSearch() {
788
+ const ctx = useContext(WhaleContext);
789
+ if (!ctx) throw new Error("useSearch must be used within <WhaleProvider>");
790
+ const [results, setResults] = useState([]);
791
+ const [hasMore, setHasMore] = useState(false);
792
+ const [loading, setLoading] = useState(false);
793
+ const [error, setError] = useState(null);
794
+ const [cursor, setCursor] = useState(void 0);
795
+ const search = useCallback(async (params) => {
796
+ setLoading(true);
797
+ setError(null);
798
+ try {
799
+ const data = await ctx.client.searchProducts(params);
800
+ setResults(data.data);
801
+ setHasMore(data.has_more);
802
+ setCursor(data.data.length > 0 ? data.data[data.data.length - 1].id : void 0);
803
+ } catch (err) {
804
+ const e = err instanceof Error ? err : new Error(String(err));
805
+ setError(e);
806
+ setResults([]);
807
+ setHasMore(false);
808
+ } finally {
809
+ setLoading(false);
810
+ }
811
+ }, [ctx.client]);
812
+ const loadMore = useCallback(async (params) => {
813
+ if (!cursor || !hasMore) return;
814
+ setLoading(true);
815
+ setError(null);
816
+ try {
817
+ const data = await ctx.client.searchProducts({ ...params, starting_after: cursor });
818
+ setResults((prev) => [...prev, ...data.data]);
819
+ setHasMore(data.has_more);
820
+ setCursor(data.data.length > 0 ? data.data[data.data.length - 1].id : void 0);
821
+ } catch (err) {
822
+ const e = err instanceof Error ? err : new Error(String(err));
823
+ setError(e);
824
+ } finally {
825
+ setLoading(false);
826
+ }
827
+ }, [ctx.client, cursor, hasMore]);
828
+ const clear = useCallback(() => {
829
+ setResults([]);
830
+ setHasMore(false);
831
+ setCursor(void 0);
832
+ setError(null);
833
+ }, []);
834
+ return { results, hasMore, loading, error, search, loadMore, clear };
835
+ }
836
+ function buildTree(categories) {
837
+ const map = /* @__PURE__ */ new Map();
838
+ const roots = [];
839
+ for (const cat of categories) {
840
+ map.set(cat.id, { ...cat, children: [] });
841
+ }
842
+ for (const cat of categories) {
843
+ const node = map.get(cat.id);
844
+ if (cat.parent_id && map.has(cat.parent_id)) {
845
+ map.get(cat.parent_id).children.push(node);
846
+ } else {
847
+ roots.push(node);
848
+ }
849
+ }
850
+ return roots;
851
+ }
852
+ function useCategories() {
853
+ const ctx = useContext(WhaleContext);
854
+ if (!ctx) throw new Error("useCategories must be used within <WhaleProvider>");
855
+ const [categories, setCategories] = useState([]);
856
+ const [loading, setLoading] = useState(true);
857
+ const [error, setError] = useState(null);
858
+ const refresh = useCallback(async () => {
859
+ setLoading(true);
860
+ setError(null);
861
+ try {
862
+ const data = await ctx.client.listCategories();
863
+ setCategories(data.data);
864
+ } catch (err) {
865
+ const e = err instanceof Error ? err : new Error(String(err));
866
+ setError(e);
867
+ setCategories([]);
868
+ } finally {
869
+ setLoading(false);
870
+ }
871
+ }, [ctx.client]);
872
+ useEffect(() => {
873
+ refresh();
874
+ }, [refresh]);
875
+ const tree = useMemo(() => buildTree(categories), [categories]);
876
+ const getCategory = useCallback(async (id) => {
877
+ return ctx.client.getCategory(id);
878
+ }, [ctx.client]);
879
+ return { categories, tree, loading, error, refresh, getCategory };
880
+ }
881
+ function useLoyalty() {
882
+ const ctx = useContext(WhaleContext);
883
+ if (!ctx) throw new Error("useLoyalty must be used within <WhaleProvider>");
884
+ const customer = useStore(ctx.authStore, (s) => s.customer);
885
+ const [account, setAccount] = useState(null);
886
+ const [rewards, setRewards] = useState([]);
887
+ const [transactions, setTransactions] = useState([]);
888
+ const [loading, setLoading] = useState(false);
889
+ const [error, setError] = useState(null);
890
+ const refresh = useCallback(async () => {
891
+ if (!customer?.id) {
892
+ setAccount(null);
893
+ setRewards([]);
894
+ setTransactions([]);
895
+ return;
896
+ }
897
+ setLoading(true);
898
+ setError(null);
899
+ try {
900
+ const [acct, rwds, txns] = await Promise.all([
901
+ ctx.client.getLoyaltyAccount(customer.id),
902
+ ctx.client.listLoyaltyRewards(),
903
+ ctx.client.listLoyaltyTransactions(customer.id)
904
+ ]);
905
+ setAccount(acct);
906
+ setRewards(rwds.data);
907
+ setTransactions(txns.data);
908
+ } catch (err) {
909
+ const e = err instanceof Error ? err : new Error(String(err));
910
+ setError(e);
911
+ } finally {
912
+ setLoading(false);
913
+ }
914
+ }, [customer?.id, ctx.client]);
915
+ useEffect(() => {
916
+ refresh();
917
+ }, [refresh]);
918
+ const redeemReward = useCallback(async (rewardId) => {
919
+ if (!customer?.id) throw new Error("Not authenticated");
920
+ const result = await ctx.client.redeemLoyaltyReward(customer.id, rewardId);
921
+ await refresh();
922
+ return result;
923
+ }, [customer?.id, ctx.client, refresh]);
924
+ return { account, rewards, transactions, loading, error, refresh, redeemReward };
925
+ }
926
+ function useReviews(productId) {
927
+ const ctx = useContext(WhaleContext);
928
+ if (!ctx) throw new Error("useReviews must be used within <WhaleProvider>");
929
+ const [reviews, setReviews] = useState([]);
930
+ const [summary, setSummary] = useState(null);
931
+ const [hasMore, setHasMore] = useState(false);
932
+ const [loading, setLoading] = useState(false);
933
+ const [error, setError] = useState(null);
934
+ const refresh = useCallback(async () => {
935
+ if (!productId) {
936
+ setReviews([]);
937
+ setSummary(null);
938
+ return;
939
+ }
940
+ setLoading(true);
941
+ setError(null);
942
+ try {
943
+ const data = await ctx.client.listProductReviews(productId);
944
+ setReviews(data.data);
945
+ setSummary(data.summary ?? null);
946
+ setHasMore(data.has_more);
947
+ } catch (err) {
948
+ const e = err instanceof Error ? err : new Error(String(err));
949
+ setError(e);
950
+ setReviews([]);
951
+ setSummary(null);
952
+ } finally {
953
+ setLoading(false);
954
+ }
955
+ }, [productId, ctx.client]);
956
+ useEffect(() => {
957
+ refresh();
958
+ }, [refresh]);
959
+ const loadMore = useCallback(async () => {
960
+ if (!productId || !hasMore || reviews.length === 0) return;
961
+ setLoading(true);
962
+ setError(null);
963
+ try {
964
+ const cursor = reviews[reviews.length - 1].id;
965
+ const data = await ctx.client.listProductReviews(productId, { starting_after: cursor });
966
+ setReviews((prev) => [...prev, ...data.data]);
967
+ setHasMore(data.has_more);
968
+ } catch (err) {
969
+ const e = err instanceof Error ? err : new Error(String(err));
970
+ setError(e);
971
+ } finally {
972
+ setLoading(false);
973
+ }
974
+ }, [productId, ctx.client, hasMore, reviews]);
975
+ const submit = useCallback(async (data) => {
976
+ if (!productId) throw new Error("No product ID provided");
977
+ const review = await ctx.client.submitReview(productId, data);
978
+ await refresh();
979
+ return review;
980
+ }, [productId, ctx.client, refresh]);
981
+ return { reviews, summary, hasMore, loading, error, refresh, loadMore, submit };
982
+ }
983
+ function useWishlist() {
984
+ const ctx = useContext(WhaleContext);
985
+ if (!ctx) throw new Error("useWishlist must be used within <WhaleProvider>");
986
+ const customer = useStore(ctx.authStore, (s) => s.customer);
987
+ const [items, setItems] = useState([]);
988
+ const [loading, setLoading] = useState(false);
989
+ const [error, setError] = useState(null);
990
+ const refresh = useCallback(async () => {
991
+ if (!customer?.id) {
992
+ setItems([]);
993
+ return;
994
+ }
995
+ setLoading(true);
996
+ setError(null);
997
+ try {
998
+ const data = await ctx.client.listWishlistItems(customer.id);
999
+ setItems(data.data);
1000
+ } catch (err) {
1001
+ const e = err instanceof Error ? err : new Error(String(err));
1002
+ setError(e);
1003
+ setItems([]);
1004
+ } finally {
1005
+ setLoading(false);
1006
+ }
1007
+ }, [customer?.id, ctx.client]);
1008
+ useEffect(() => {
1009
+ refresh();
1010
+ }, [refresh]);
1011
+ const add = useCallback(async (productId) => {
1012
+ if (!customer?.id) throw new Error("Not authenticated");
1013
+ setError(null);
1014
+ try {
1015
+ const item = await ctx.client.addWishlistItem(customer.id, productId);
1016
+ setItems((prev) => [...prev, item]);
1017
+ return item;
1018
+ } catch (err) {
1019
+ const e = err instanceof Error ? err : new Error(String(err));
1020
+ setError(e);
1021
+ throw e;
1022
+ }
1023
+ }, [customer?.id, ctx.client]);
1024
+ const remove = useCallback(async (productId) => {
1025
+ if (!customer?.id) throw new Error("Not authenticated");
1026
+ setError(null);
1027
+ try {
1028
+ await ctx.client.removeWishlistItem(customer.id, productId);
1029
+ setItems((prev) => prev.filter((item) => item.product_id !== productId));
1030
+ } catch (err) {
1031
+ const e = err instanceof Error ? err : new Error(String(err));
1032
+ setError(e);
1033
+ throw e;
1034
+ }
1035
+ }, [customer?.id, ctx.client]);
1036
+ const has = useCallback((productId) => {
1037
+ return items.some((item) => item.product_id === productId);
1038
+ }, [items]);
1039
+ const toggle = useCallback(async (productId) => {
1040
+ if (has(productId)) {
1041
+ await remove(productId);
1042
+ } else {
1043
+ await add(productId);
1044
+ }
1045
+ }, [has, add, remove]);
1046
+ return { items, loading, error, refresh, add, remove, has, toggle };
1047
+ }
1048
+ function useRecommendations(params) {
1049
+ const ctx = useContext(WhaleContext);
1050
+ if (!ctx) throw new Error("useRecommendations must be used within <WhaleProvider>");
1051
+ const [recommendations, setRecommendations] = useState([]);
1052
+ const [loading, setLoading] = useState(false);
1053
+ const [error, setError] = useState(null);
1054
+ const productId = params?.product_id;
1055
+ const customerId = params?.customer_id;
1056
+ const limit = params?.limit;
1057
+ const type = params?.type;
1058
+ const refresh = useCallback(async () => {
1059
+ setLoading(true);
1060
+ setError(null);
1061
+ try {
1062
+ const data = await ctx.client.getRecommendations({
1063
+ product_id: productId,
1064
+ customer_id: customerId,
1065
+ limit,
1066
+ type
1067
+ });
1068
+ setRecommendations(data.data);
1069
+ } catch (err) {
1070
+ const e = err instanceof Error ? err : new Error(String(err));
1071
+ setError(e);
1072
+ setRecommendations([]);
1073
+ } finally {
1074
+ setLoading(false);
1075
+ }
1076
+ }, [ctx.client, productId, customerId, limit, type]);
1077
+ useEffect(() => {
1078
+ refresh();
1079
+ }, [refresh]);
1080
+ return { recommendations, loading, error, refresh };
1081
+ }
1082
+ function useLocations() {
1083
+ const ctx = useContext(WhaleContext);
1084
+ if (!ctx) throw new Error("useLocations must be used within <WhaleProvider>");
1085
+ const [locations, setLocations] = useState([]);
1086
+ const [loading, setLoading] = useState(true);
1087
+ const [error, setError] = useState(null);
1088
+ const refresh = useCallback(async () => {
1089
+ setLoading(true);
1090
+ setError(null);
1091
+ try {
1092
+ const data = await ctx.client.listLocations();
1093
+ setLocations(data.data);
1094
+ } catch (err) {
1095
+ const e = err instanceof Error ? err : new Error(String(err));
1096
+ setError(e);
1097
+ setLocations([]);
1098
+ } finally {
1099
+ setLoading(false);
1100
+ }
1101
+ }, [ctx.client]);
1102
+ useEffect(() => {
1103
+ refresh();
1104
+ }, [refresh]);
1105
+ const getLocation = useCallback(async (id) => {
1106
+ return ctx.client.getLocation(id);
1107
+ }, [ctx.client]);
1108
+ return { locations, loading, error, refresh, getLocation };
1109
+ }
1110
+ function useShipping() {
1111
+ const ctx = useContext(WhaleContext);
1112
+ if (!ctx) throw new Error("useShipping must be used within <WhaleProvider>");
1113
+ const [methods, setMethods] = useState([]);
1114
+ const [rates, setRates] = useState([]);
1115
+ const [loading, setLoading] = useState(false);
1116
+ const [error, setError] = useState(null);
1117
+ const refreshMethods = useCallback(async () => {
1118
+ setLoading(true);
1119
+ setError(null);
1120
+ try {
1121
+ const data = await ctx.client.listShippingMethods();
1122
+ setMethods(data.data);
1123
+ } catch (err) {
1124
+ const e = err instanceof Error ? err : new Error(String(err));
1125
+ setError(e);
1126
+ setMethods([]);
1127
+ } finally {
1128
+ setLoading(false);
1129
+ }
1130
+ }, [ctx.client]);
1131
+ useEffect(() => {
1132
+ refreshMethods();
1133
+ }, [refreshMethods]);
1134
+ const calculateRates = useCallback(async (params) => {
1135
+ setLoading(true);
1136
+ setError(null);
1137
+ try {
1138
+ const data = await ctx.client.calculateShippingRates(params);
1139
+ setRates(data.data);
1140
+ return data.data;
1141
+ } catch (err) {
1142
+ const e = err instanceof Error ? err : new Error(String(err));
1143
+ setError(e);
1144
+ setRates([]);
1145
+ throw e;
1146
+ } finally {
1147
+ setLoading(false);
1148
+ }
1149
+ }, [ctx.client]);
1150
+ return { methods, rates, loading, error, refreshMethods, calculateRates };
1151
+ }
1152
+ function useCoupons() {
1153
+ const ctx = useContext(WhaleContext);
1154
+ if (!ctx) throw new Error("useCoupons must be used within <WhaleProvider>");
1155
+ const [validation, setValidation] = useState(null);
1156
+ const [loading, setLoading] = useState(false);
1157
+ const [error, setError] = useState(null);
1158
+ const validate = useCallback(async (code, cartId) => {
1159
+ setLoading(true);
1160
+ setError(null);
1161
+ try {
1162
+ const result = await ctx.client.validateCoupon(code, cartId ? { cart_id: cartId } : void 0);
1163
+ setValidation(result);
1164
+ return result;
1165
+ } catch (err) {
1166
+ const e = err instanceof Error ? err : new Error(String(err));
1167
+ setError(e);
1168
+ setValidation(null);
1169
+ throw e;
1170
+ } finally {
1171
+ setLoading(false);
1172
+ }
1173
+ }, [ctx.client]);
1174
+ const apply = useCallback(async (cartId, code) => {
1175
+ setLoading(true);
1176
+ setError(null);
1177
+ try {
1178
+ const cart = await ctx.client.applyCoupon(cartId, code);
1179
+ return cart;
1180
+ } catch (err) {
1181
+ const e = err instanceof Error ? err : new Error(String(err));
1182
+ setError(e);
1183
+ throw e;
1184
+ } finally {
1185
+ setLoading(false);
1186
+ }
1187
+ }, [ctx.client]);
1188
+ const remove = useCallback(async (cartId) => {
1189
+ setLoading(true);
1190
+ setError(null);
1191
+ try {
1192
+ const cart = await ctx.client.removeCoupon(cartId);
1193
+ setValidation(null);
1194
+ return cart;
1195
+ } catch (err) {
1196
+ const e = err instanceof Error ? err : new Error(String(err));
1197
+ setError(e);
1198
+ throw e;
1199
+ } finally {
1200
+ setLoading(false);
1201
+ }
1202
+ }, [ctx.client]);
1203
+ const clear = useCallback(() => {
1204
+ setValidation(null);
1205
+ setError(null);
1206
+ }, []);
1207
+ return { validation, loading, error, validate, apply, remove, clear };
1208
+ }
1209
+ function QRLandingPage({
1210
+ code,
1211
+ gatewayUrl = "https://whale-gateway.fly.dev",
1212
+ renderProduct,
1213
+ renderCOA,
1214
+ renderPage,
1215
+ onDataLoaded,
1216
+ onError
1217
+ }) {
1218
+ const [state, setState] = useState("loading");
1219
+ const [data, setData] = useState(null);
1220
+ const [errorMsg, setErrorMsg] = useState("");
1221
+ useEffect(() => {
1222
+ if (!code) return;
1223
+ let cancelled = false;
1224
+ async function load() {
1225
+ try {
1226
+ const res = await fetch(`${gatewayUrl}/q/${encodeURIComponent(code)}/page`);
1227
+ if (!cancelled) {
1228
+ if (res.status === 404) {
1229
+ setState("not_found");
1230
+ return;
1231
+ }
1232
+ if (res.status === 410) {
1233
+ setState("expired");
1234
+ return;
1235
+ }
1236
+ if (!res.ok) {
1237
+ const body = await res.json().catch(() => ({}));
1238
+ throw new Error(body?.error?.message ?? `Failed to load: ${res.status}`);
1239
+ }
1240
+ const json = await res.json();
1241
+ setData(json);
1242
+ setState("ready");
1243
+ onDataLoaded?.(json);
1244
+ }
1245
+ } catch (err) {
1246
+ if (!cancelled) {
1247
+ const e = err instanceof Error ? err : new Error(String(err));
1248
+ setErrorMsg(e.message);
1249
+ setState("error");
1250
+ onError?.(e);
1251
+ }
1252
+ }
1253
+ }
1254
+ load();
1255
+ return () => {
1256
+ cancelled = true;
1257
+ };
1258
+ }, [code, gatewayUrl]);
1259
+ if (state === "loading") return /* @__PURE__ */ jsx(DefaultLoading, {});
1260
+ if (state === "not_found") return /* @__PURE__ */ jsx(DefaultNotFound, {});
1261
+ if (state === "expired") return /* @__PURE__ */ jsx(DefaultExpired, {});
1262
+ if (state === "error") return /* @__PURE__ */ jsx(DefaultError, { message: errorMsg });
1263
+ if (!data) return null;
1264
+ if (renderPage) return /* @__PURE__ */ jsx(Fragment, { children: renderPage(data) });
1265
+ return /* @__PURE__ */ jsx(DefaultLayout, { data, renderProduct, renderCOA });
1266
+ }
1267
+ function DefaultLayout({
1268
+ data,
1269
+ renderProduct,
1270
+ renderCOA
1271
+ }) {
1272
+ const { qr_code: qr, store, product, coa } = data;
1273
+ const lp = qr.landing_page;
1274
+ const [showCOA, setShowCOA] = useState(false);
1275
+ const bg = lp.background_color || store?.theme?.background || "#050505";
1276
+ const fg = lp.text_color || store?.theme?.foreground || "#fafafa";
1277
+ const accent = store?.theme?.accent || qr.brand_color || "#E8E2D9";
1278
+ const surface = store?.theme?.surface || "#111";
1279
+ const muted = store?.theme?.muted || "#888";
1280
+ const logoUrl = qr.logo_url || store?.logo_url;
1281
+ const productImage = lp.image_url || product?.featured_image || product?.image_gallery?.[0] || null;
1282
+ const productName = lp.title || product?.name || qr.name;
1283
+ const description = lp.description || product?.description || "";
1284
+ const ctaText = lp.cta_text || (coa ? "View Lab Results" : "Learn More");
1285
+ const ctaUrl = lp.cta_url || qr.destination_url;
1286
+ const cf = product?.custom_fields;
1287
+ const thca = cf?.thca_percentage ?? null;
1288
+ const thc = cf?.d9_percentage ?? null;
1289
+ const cbd = cf?.cbd_total ?? null;
1290
+ const strainType = cf?.strain_type ?? null;
1291
+ const categoryName = product?.category_name ?? null;
1292
+ const handleCOAClick = useCallback(() => {
1293
+ if (coa) setShowCOA(true);
1294
+ }, [coa]);
1295
+ return /* @__PURE__ */ jsxs("div", { style: { minHeight: "100dvh", background: bg, color: fg, fontFamily: "system-ui, -apple-system, sans-serif" }, children: [
1296
+ /* @__PURE__ */ jsx("div", { style: { padding: "1.5rem", display: "flex", justifyContent: "center" }, children: logoUrl && /* @__PURE__ */ jsx(
1297
+ "img",
1298
+ {
1299
+ src: logoUrl,
1300
+ alt: store?.name || "Store",
1301
+ style: { height: 40, objectFit: "contain" }
1302
+ }
1303
+ ) }),
1304
+ productImage && /* @__PURE__ */ jsx("div", { style: { width: "100%", aspectRatio: "1", overflow: "hidden", background: surface }, children: /* @__PURE__ */ jsx(
1305
+ "img",
1306
+ {
1307
+ src: productImage,
1308
+ alt: productName,
1309
+ style: { width: "100%", height: "100%", objectFit: "cover" }
1310
+ }
1311
+ ) }),
1312
+ /* @__PURE__ */ jsxs("div", { style: { padding: "1.5rem", maxWidth: 480, margin: "0 auto" }, children: [
1313
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "0.5rem", marginBottom: "0.75rem", flexWrap: "wrap" }, children: [
1314
+ strainType && /* @__PURE__ */ jsx("span", { style: {
1315
+ padding: "0.25rem 0.75rem",
1316
+ borderRadius: 999,
1317
+ fontSize: "0.75rem",
1318
+ fontWeight: 600,
1319
+ textTransform: "uppercase",
1320
+ letterSpacing: "0.05em",
1321
+ background: strainBadgeColor(strainType),
1322
+ color: "#fff"
1323
+ }, children: strainType }),
1324
+ categoryName && /* @__PURE__ */ jsx("span", { style: {
1325
+ padding: "0.25rem 0.75rem",
1326
+ borderRadius: 999,
1327
+ fontSize: "0.75rem",
1328
+ background: surface,
1329
+ color: muted
1330
+ }, children: categoryName })
1331
+ ] }),
1332
+ /* @__PURE__ */ jsx("h1", { style: { fontSize: "1.75rem", fontWeight: 600, margin: "0 0 0.5rem", lineHeight: 1.2 }, children: productName }),
1333
+ description && /* @__PURE__ */ jsx("p", { style: { color: muted, lineHeight: 1.6, margin: "0 0 1.5rem", fontSize: "0.95rem" }, children: description }),
1334
+ renderProduct ? renderProduct(data) : null,
1335
+ (thca || thc || cbd) && /* @__PURE__ */ jsxs("div", { style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: "0.75rem", margin: "1.5rem 0" }, children: [
1336
+ thca != null && /* @__PURE__ */ jsx(StatCard, { label: "THCa", value: `${thca.toFixed(1)}%`, bg: surface, fg, accent }),
1337
+ thc != null && /* @__PURE__ */ jsx(StatCard, { label: "D9 THC", value: `${thc.toFixed(1)}%`, bg: surface, fg, accent }),
1338
+ cbd != null && /* @__PURE__ */ jsx(StatCard, { label: "CBD", value: `${cbd.toFixed(1)}%`, bg: surface, fg, accent })
1339
+ ] }),
1340
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.75rem", marginTop: "1.5rem" }, children: [
1341
+ coa && /* @__PURE__ */ jsx(
1342
+ "button",
1343
+ {
1344
+ onClick: handleCOAClick,
1345
+ style: {
1346
+ width: "100%",
1347
+ padding: "0.875rem",
1348
+ background: accent,
1349
+ color: bg,
1350
+ border: "none",
1351
+ fontSize: "0.95rem",
1352
+ fontWeight: 600,
1353
+ cursor: "pointer",
1354
+ borderRadius: 0
1355
+ },
1356
+ children: "View Lab Results"
1357
+ }
1358
+ ),
1359
+ renderCOA ? renderCOA(data) : null,
1360
+ /* @__PURE__ */ jsx(
1361
+ "a",
1362
+ {
1363
+ href: ctaUrl,
1364
+ style: {
1365
+ display: "block",
1366
+ width: "100%",
1367
+ padding: "0.875rem",
1368
+ background: "transparent",
1369
+ color: fg,
1370
+ border: `1px solid ${muted}`,
1371
+ fontSize: "0.95rem",
1372
+ fontWeight: 500,
1373
+ textAlign: "center",
1374
+ textDecoration: "none",
1375
+ boxSizing: "border-box",
1376
+ borderRadius: 0
1377
+ },
1378
+ children: ctaText === "View Lab Results" ? "Shop Online" : ctaText
1379
+ }
1380
+ )
1381
+ ] }),
1382
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: "2rem", paddingTop: "1.5rem", borderTop: `1px solid ${surface}`, textAlign: "center" }, children: [
1383
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "center", gap: "1.5rem", marginBottom: "0.75rem" }, children: [
1384
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "0.75rem", color: muted, textTransform: "uppercase", letterSpacing: "0.05em" }, children: "Lab Tested" }),
1385
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "0.75rem", color: muted, textTransform: "uppercase", letterSpacing: "0.05em" }, children: "Authentic" })
1386
+ ] }),
1387
+ store?.name && /* @__PURE__ */ jsxs("p", { style: { fontSize: "0.75rem", color: muted, margin: 0 }, children: [
1388
+ "Verified by ",
1389
+ store.name
1390
+ ] })
1391
+ ] })
1392
+ ] }),
1393
+ showCOA && coa && /* @__PURE__ */ jsxs(
1394
+ "div",
1395
+ {
1396
+ style: {
1397
+ position: "fixed",
1398
+ inset: 0,
1399
+ zIndex: 9999,
1400
+ background: "rgba(0,0,0,0.9)",
1401
+ display: "flex",
1402
+ flexDirection: "column"
1403
+ },
1404
+ children: [
1405
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", padding: "1rem" }, children: [
1406
+ /* @__PURE__ */ jsx("span", { style: { color: "#fff", fontWeight: 600 }, children: coa.document_name || "Lab Results" }),
1407
+ /* @__PURE__ */ jsx(
1408
+ "button",
1409
+ {
1410
+ onClick: () => setShowCOA(false),
1411
+ style: { background: "none", border: "none", color: "#fff", fontSize: "1.5rem", cursor: "pointer", padding: "0.5rem" },
1412
+ children: "\u2715"
1413
+ }
1414
+ )
1415
+ ] }),
1416
+ /* @__PURE__ */ jsx(
1417
+ "iframe",
1418
+ {
1419
+ src: coa.url,
1420
+ style: { flex: 1, border: "none", background: "#fff" },
1421
+ title: "Lab Results"
1422
+ }
1423
+ )
1424
+ ]
1425
+ }
1426
+ )
1427
+ ] });
1428
+ }
1429
+ function StatCard({ label, value, bg, fg, accent }) {
1430
+ return /* @__PURE__ */ jsxs("div", { style: { background: bg, padding: "1rem", textAlign: "center" }, children: [
1431
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "1.25rem", fontWeight: 700, color: fg }, children: value }),
1432
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "0.7rem", color: accent, textTransform: "uppercase", letterSpacing: "0.05em", marginTop: "0.25rem" }, children: label })
1433
+ ] });
1434
+ }
1435
+ function strainBadgeColor(strain) {
1436
+ const s = strain.toLowerCase();
1437
+ if (s === "sativa") return "#22c55e";
1438
+ if (s === "indica") return "#8b5cf6";
1439
+ if (s === "hybrid") return "#f59e0b";
1440
+ return "#6b7280";
1441
+ }
1442
+ var containerStyle = {
1443
+ minHeight: "100dvh",
1444
+ display: "flex",
1445
+ justifyContent: "center",
1446
+ alignItems: "center",
1447
+ fontFamily: "system-ui, -apple-system, sans-serif",
1448
+ background: "#050505",
1449
+ color: "#fafafa",
1450
+ textAlign: "center",
1451
+ padding: "2rem"
1452
+ };
1453
+ function DefaultLoading() {
1454
+ return /* @__PURE__ */ jsx("div", { style: containerStyle, children: /* @__PURE__ */ jsxs("div", { children: [
1455
+ /* @__PURE__ */ jsx("div", { style: { width: 32, height: 32, border: "2px solid #333", borderTopColor: "#fafafa", borderRadius: "50%", animation: "spin 0.8s linear infinite", margin: "0 auto 1rem" } }),
1456
+ /* @__PURE__ */ jsx("style", { children: `@keyframes spin { to { transform: rotate(360deg) } }` })
1457
+ ] }) });
1458
+ }
1459
+ function DefaultNotFound() {
1460
+ return /* @__PURE__ */ jsx("div", { style: containerStyle, children: /* @__PURE__ */ jsxs("div", { children: [
1461
+ /* @__PURE__ */ jsx("h1", { style: { fontSize: "1.5rem", marginBottom: "0.5rem" }, children: "QR Code Not Found" }),
1462
+ /* @__PURE__ */ jsx("p", { style: { color: "#888" }, children: "This QR code does not exist or has been deactivated." })
1463
+ ] }) });
1464
+ }
1465
+ function DefaultExpired() {
1466
+ return /* @__PURE__ */ jsx("div", { style: containerStyle, children: /* @__PURE__ */ jsxs("div", { children: [
1467
+ /* @__PURE__ */ jsx("h1", { style: { fontSize: "1.5rem", marginBottom: "0.5rem" }, children: "QR Code Expired" }),
1468
+ /* @__PURE__ */ jsx("p", { style: { color: "#888" }, children: "This QR code is no longer active." })
1469
+ ] }) });
1470
+ }
1471
+ function DefaultError({ message }) {
1472
+ return /* @__PURE__ */ jsx("div", { style: containerStyle, children: /* @__PURE__ */ jsxs("div", { children: [
1473
+ /* @__PURE__ */ jsx("h1", { style: { fontSize: "1.5rem", marginBottom: "0.5rem" }, children: "Something Went Wrong" }),
1474
+ /* @__PURE__ */ jsx("p", { style: { color: "#888" }, children: message || "Please try scanning again." })
1475
+ ] }) });
1476
+ }
1477
+ function LandingPage({
1478
+ slug,
1479
+ gatewayUrl = "https://whale-gateway.fly.dev",
1480
+ renderSection,
1481
+ onDataLoaded,
1482
+ onError
1483
+ }) {
1484
+ const [state, setState] = useState("loading");
1485
+ const [data, setData] = useState(null);
1486
+ const [errorMsg, setErrorMsg] = useState("");
1487
+ useEffect(() => {
1488
+ if (!slug) return;
1489
+ let cancelled = false;
1490
+ async function load() {
1491
+ try {
1492
+ const res = await fetch(`${gatewayUrl}/l/${encodeURIComponent(slug)}`);
1493
+ if (!cancelled) {
1494
+ if (res.status === 404) {
1495
+ setState("not_found");
1496
+ return;
1497
+ }
1498
+ if (res.status === 410) {
1499
+ setState("expired");
1500
+ return;
1501
+ }
1502
+ if (!res.ok) {
1503
+ const body = await res.json().catch(() => ({}));
1504
+ throw new Error(body?.error?.message ?? `Failed to load: ${res.status}`);
1505
+ }
1506
+ const json = await res.json();
1507
+ setData(json);
1508
+ setState("ready");
1509
+ onDataLoaded?.(json);
1510
+ }
1511
+ } catch (err) {
1512
+ if (!cancelled) {
1513
+ const e = err instanceof Error ? err : new Error(String(err));
1514
+ setErrorMsg(e.message);
1515
+ setState("error");
1516
+ onError?.(e);
1517
+ }
1518
+ }
1519
+ }
1520
+ load();
1521
+ return () => {
1522
+ cancelled = true;
1523
+ };
1524
+ }, [slug, gatewayUrl]);
1525
+ if (state === "loading") return /* @__PURE__ */ jsx(DefaultLoading2, {});
1526
+ if (state === "not_found") return /* @__PURE__ */ jsx(DefaultNotFound2, {});
1527
+ if (state === "expired") return /* @__PURE__ */ jsx(DefaultExpired2, {});
1528
+ if (state === "error") return /* @__PURE__ */ jsx(DefaultError2, { message: errorMsg });
1529
+ if (!data) return null;
1530
+ return /* @__PURE__ */ jsx(PageLayout, { data, renderSection });
1531
+ }
1532
+ function PageLayout({
1533
+ data,
1534
+ renderSection
1535
+ }) {
1536
+ const { landing_page: lp, store } = data;
1537
+ const [showCOA, setShowCOA] = useState(false);
1538
+ const theme = {
1539
+ bg: lp.background_color || store?.theme?.background || "#050505",
1540
+ fg: lp.text_color || store?.theme?.foreground || "#fafafa",
1541
+ accent: lp.accent_color || store?.theme?.accent || "#E8E2D9",
1542
+ surface: store?.theme?.surface || "#111",
1543
+ muted: store?.theme?.muted || "#888"
1544
+ };
1545
+ const fontFamily = lp.font_family || "system-ui, -apple-system, sans-serif";
1546
+ const logoUrl = store?.logo_url;
1547
+ const sorted = [...lp.sections].sort((a, b) => a.order - b.order);
1548
+ return /* @__PURE__ */ jsxs("div", { style: { minHeight: "100dvh", background: theme.bg, color: theme.fg, fontFamily }, children: [
1549
+ lp.custom_css && /* @__PURE__ */ jsx("style", { children: lp.custom_css }),
1550
+ logoUrl && /* @__PURE__ */ jsx("div", { style: { padding: "1.5rem", display: "flex", justifyContent: "center" }, children: /* @__PURE__ */ jsx(
1551
+ "img",
1552
+ {
1553
+ src: logoUrl,
1554
+ alt: store?.name || "Store",
1555
+ style: { height: 40, objectFit: "contain" }
1556
+ }
1557
+ ) }),
1558
+ sorted.map((section) => {
1559
+ const defaultRenderer = () => /* @__PURE__ */ jsx(
1560
+ DefaultSectionRenderer,
1561
+ {
1562
+ section,
1563
+ data,
1564
+ theme,
1565
+ onShowCOA: () => setShowCOA(true)
1566
+ },
1567
+ section.id
1568
+ );
1569
+ if (renderSection) {
1570
+ return /* @__PURE__ */ jsx("div", { children: renderSection(section, defaultRenderer) }, section.id);
1571
+ }
1572
+ return /* @__PURE__ */ jsx(
1573
+ DefaultSectionRenderer,
1574
+ {
1575
+ section,
1576
+ data,
1577
+ theme,
1578
+ onShowCOA: () => setShowCOA(true)
1579
+ },
1580
+ section.id
1581
+ );
1582
+ }),
1583
+ 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: [
1584
+ "Powered by ",
1585
+ store.name
1586
+ ] }) }),
1587
+ showCOA && data.coa && /* @__PURE__ */ jsxs(
1588
+ "div",
1589
+ {
1590
+ style: {
1591
+ position: "fixed",
1592
+ inset: 0,
1593
+ zIndex: 9999,
1594
+ background: "rgba(0,0,0,0.9)",
1595
+ display: "flex",
1596
+ flexDirection: "column"
1597
+ },
1598
+ children: [
1599
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", padding: "1rem" }, children: [
1600
+ /* @__PURE__ */ jsx("span", { style: { color: "#fff", fontWeight: 600 }, children: data.coa.document_name || "Lab Results" }),
1601
+ /* @__PURE__ */ jsx(
1602
+ "button",
1603
+ {
1604
+ onClick: () => setShowCOA(false),
1605
+ style: { background: "none", border: "none", color: "#fff", fontSize: "1.5rem", cursor: "pointer", padding: "0.5rem" },
1606
+ children: "\u2715"
1607
+ }
1608
+ )
1609
+ ] }),
1610
+ /* @__PURE__ */ jsx(
1611
+ "iframe",
1612
+ {
1613
+ src: data.coa.url,
1614
+ style: { flex: 1, border: "none", background: "#fff" },
1615
+ title: "Lab Results"
1616
+ }
1617
+ )
1618
+ ]
1619
+ }
1620
+ )
1621
+ ] });
1622
+ }
1623
+ function DefaultSectionRenderer({
1624
+ section,
1625
+ data,
1626
+ theme,
1627
+ onShowCOA
1628
+ }) {
1629
+ switch (section.type) {
1630
+ case "hero":
1631
+ return /* @__PURE__ */ jsx(HeroSection, { section, theme });
1632
+ case "text":
1633
+ return /* @__PURE__ */ jsx(TextSection, { section, theme });
1634
+ case "image":
1635
+ return /* @__PURE__ */ jsx(ImageSection, { section, theme });
1636
+ case "video":
1637
+ return /* @__PURE__ */ jsx(VideoSection, { section, theme });
1638
+ case "gallery":
1639
+ return /* @__PURE__ */ jsx(GallerySection, { section, theme });
1640
+ case "cta":
1641
+ return /* @__PURE__ */ jsx(CTASection, { section, theme });
1642
+ case "stats":
1643
+ return /* @__PURE__ */ jsx(StatsSection, { section, theme });
1644
+ case "product_card":
1645
+ return /* @__PURE__ */ jsx(ProductCardSection, { section, data, theme });
1646
+ case "coa_viewer":
1647
+ return /* @__PURE__ */ jsx(COAViewerSection, { section, data, theme, onShowCOA });
1648
+ case "social_links":
1649
+ return /* @__PURE__ */ jsx(SocialLinksSection, { section, theme });
1650
+ case "divider":
1651
+ return /* @__PURE__ */ jsx(DividerSection, { theme });
1652
+ default:
1653
+ return null;
1654
+ }
1655
+ }
1656
+ function HeroSection({ section, theme }) {
1657
+ const { title, subtitle, background_image, cta_text, cta_url } = section.content;
1658
+ return /* @__PURE__ */ jsxs(
1659
+ "div",
1660
+ {
1661
+ style: {
1662
+ position: "relative",
1663
+ minHeight: "60vh",
1664
+ display: "flex",
1665
+ flexDirection: "column",
1666
+ justifyContent: "center",
1667
+ alignItems: "center",
1668
+ textAlign: "center",
1669
+ padding: "3rem 1.5rem",
1670
+ backgroundImage: background_image ? `url(${background_image})` : void 0,
1671
+ backgroundSize: "cover",
1672
+ backgroundPosition: "center"
1673
+ },
1674
+ children: [
1675
+ background_image && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: "rgba(0,0,0,0.5)" } }),
1676
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative", zIndex: 1, maxWidth: 640 }, children: [
1677
+ title && /* @__PURE__ */ jsx("h1", { style: { fontSize: "2.5rem", fontWeight: 700, margin: "0 0 1rem", lineHeight: 1.15, color: theme.fg }, children: title }),
1678
+ subtitle && /* @__PURE__ */ jsx("p", { style: { fontSize: "1.125rem", color: theme.muted, margin: "0 0 2rem", lineHeight: 1.6 }, children: subtitle }),
1679
+ cta_text && cta_url && /* @__PURE__ */ jsx(
1680
+ "a",
1681
+ {
1682
+ href: cta_url,
1683
+ style: {
1684
+ display: "inline-block",
1685
+ padding: "0.875rem 2rem",
1686
+ background: theme.accent,
1687
+ color: theme.bg,
1688
+ textDecoration: "none",
1689
+ fontSize: "0.95rem",
1690
+ fontWeight: 600,
1691
+ borderRadius: 0
1692
+ },
1693
+ children: cta_text
1694
+ }
1695
+ )
1696
+ ] })
1697
+ ]
1698
+ }
1699
+ );
1700
+ }
1701
+ function TextSection({ section, theme }) {
1702
+ const { heading, body } = section.content;
1703
+ const align = section.config?.align || "left";
1704
+ return /* @__PURE__ */ jsxs("div", { style: { padding: "2rem 1.5rem", maxWidth: 640, margin: "0 auto", textAlign: align }, children: [
1705
+ heading && /* @__PURE__ */ jsx("h2", { style: { fontSize: "1.5rem", fontWeight: 600, margin: "0 0 1rem", color: theme.fg }, children: heading }),
1706
+ body && /* @__PURE__ */ jsx("div", { style: { color: theme.muted, lineHeight: 1.7, fontSize: "0.95rem", whiteSpace: "pre-wrap" }, children: body })
1707
+ ] });
1708
+ }
1709
+ function ImageSection({ section, theme }) {
1710
+ const { url, alt, caption } = section.content;
1711
+ const contained = section.config?.contained !== false;
1712
+ if (!url) return null;
1713
+ return /* @__PURE__ */ jsxs("div", { style: { padding: contained ? "1.5rem" : 0, maxWidth: contained ? 640 : void 0, margin: contained ? "0 auto" : void 0 }, children: [
1714
+ /* @__PURE__ */ jsx(
1715
+ "img",
1716
+ {
1717
+ src: url,
1718
+ alt: alt || "",
1719
+ style: { width: "100%", display: "block", objectFit: "cover" }
1720
+ }
1721
+ ),
1722
+ caption && /* @__PURE__ */ jsx("p", { style: { fontSize: "0.8rem", color: theme.muted, textAlign: "center", marginTop: "0.75rem" }, children: caption })
1723
+ ] });
1724
+ }
1725
+ function VideoSection({ section, theme }) {
1726
+ const { url, poster } = section.content;
1727
+ if (!url) return null;
1728
+ const isEmbed = url.includes("youtube") || url.includes("youtu.be") || url.includes("vimeo");
1729
+ return /* @__PURE__ */ jsx("div", { style: { padding: "1.5rem", maxWidth: 640, margin: "0 auto" }, children: isEmbed ? /* @__PURE__ */ jsx("div", { style: { position: "relative", paddingBottom: "56.25%", height: 0 }, children: /* @__PURE__ */ jsx(
1730
+ "iframe",
1731
+ {
1732
+ src: toEmbedUrl(url),
1733
+ style: { position: "absolute", top: 0, left: 0, width: "100%", height: "100%", border: "none" },
1734
+ allow: "autoplay; fullscreen",
1735
+ title: "Video"
1736
+ }
1737
+ ) }) : /* @__PURE__ */ jsx(
1738
+ "video",
1739
+ {
1740
+ src: url,
1741
+ poster,
1742
+ controls: true,
1743
+ style: { width: "100%", display: "block", background: theme.surface }
1744
+ }
1745
+ ) });
1746
+ }
1747
+ function GallerySection({ section, theme }) {
1748
+ const { images } = section.content;
1749
+ const columns = section.config?.columns || 3;
1750
+ if (!images || images.length === 0) return null;
1751
+ return /* @__PURE__ */ jsx("div", { style: { padding: "1.5rem", maxWidth: 800, margin: "0 auto" }, children: /* @__PURE__ */ jsx("div", { style: { display: "grid", gridTemplateColumns: `repeat(${columns}, 1fr)`, gap: "0.5rem" }, children: images.map((img, i) => /* @__PURE__ */ jsx("div", { style: { aspectRatio: "1", overflow: "hidden", background: theme.surface }, children: /* @__PURE__ */ jsx(
1752
+ "img",
1753
+ {
1754
+ src: img.url,
1755
+ alt: img.alt || "",
1756
+ style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
1757
+ }
1758
+ ) }, i)) }) });
1759
+ }
1760
+ function CTASection({ section, theme }) {
1761
+ const { buttons } = section.content;
1762
+ if (!buttons || buttons.length === 0) return null;
1763
+ 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) => {
1764
+ const isPrimary = btn.style !== "outline";
1765
+ return /* @__PURE__ */ jsx(
1766
+ "a",
1767
+ {
1768
+ href: btn.url,
1769
+ style: {
1770
+ display: "block",
1771
+ width: "100%",
1772
+ padding: "0.875rem",
1773
+ background: isPrimary ? theme.accent : "transparent",
1774
+ color: isPrimary ? theme.bg : theme.fg,
1775
+ border: isPrimary ? "none" : `1px solid ${theme.muted}`,
1776
+ fontSize: "0.95rem",
1777
+ fontWeight: 600,
1778
+ textAlign: "center",
1779
+ textDecoration: "none",
1780
+ boxSizing: "border-box",
1781
+ borderRadius: 0
1782
+ },
1783
+ children: btn.text
1784
+ },
1785
+ i
1786
+ );
1787
+ }) });
1788
+ }
1789
+ function StatsSection({ section, theme }) {
1790
+ const { stats } = section.content;
1791
+ if (!stats || stats.length === 0) return null;
1792
+ const columns = Math.min(stats.length, 4);
1793
+ return /* @__PURE__ */ jsx("div", { style: { padding: "1.5rem", maxWidth: 640, margin: "0 auto" }, children: /* @__PURE__ */ jsx("div", { style: { display: "grid", gridTemplateColumns: `repeat(${columns}, 1fr)`, gap: "0.75rem" }, children: stats.map((stat, i) => /* @__PURE__ */ jsxs("div", { style: { background: theme.surface, padding: "1rem", textAlign: "center" }, children: [
1794
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "1.25rem", fontWeight: 700, color: theme.fg }, children: stat.value }),
1795
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "0.7rem", color: theme.accent, textTransform: "uppercase", letterSpacing: "0.05em", marginTop: "0.25rem" }, children: stat.label })
1796
+ ] }, i)) }) });
1797
+ }
1798
+ function ProductCardSection({ section, data, theme }) {
1799
+ const product = data.product;
1800
+ const c = section.content;
1801
+ const name = c.name || product?.name || "";
1802
+ const description = c.description || product?.description || "";
1803
+ const price = c.price || null;
1804
+ const imageUrl = c.image_url || product?.featured_image || null;
1805
+ const url = c.url || null;
1806
+ return /* @__PURE__ */ jsx("div", { style: { padding: "1.5rem", maxWidth: 480, margin: "0 auto" }, children: /* @__PURE__ */ jsxs("div", { style: { background: theme.surface, overflow: "hidden" }, children: [
1807
+ imageUrl && /* @__PURE__ */ jsx("div", { style: { width: "100%", aspectRatio: "1", overflow: "hidden" }, children: /* @__PURE__ */ jsx("img", { src: imageUrl, alt: name, style: { width: "100%", height: "100%", objectFit: "cover", display: "block" } }) }),
1808
+ /* @__PURE__ */ jsxs("div", { style: { padding: "1.25rem" }, children: [
1809
+ /* @__PURE__ */ jsx("h3", { style: { fontSize: "1.25rem", fontWeight: 600, margin: "0 0 0.5rem", color: theme.fg }, children: name }),
1810
+ description && /* @__PURE__ */ jsx("p", { style: { fontSize: "0.9rem", color: theme.muted, margin: "0 0 1rem", lineHeight: 1.5 }, children: description }),
1811
+ price && /* @__PURE__ */ jsx("p", { style: { fontSize: "1.125rem", fontWeight: 700, color: theme.accent, margin: "0 0 1rem" }, children: price }),
1812
+ url && /* @__PURE__ */ jsx(
1813
+ "a",
1814
+ {
1815
+ href: url,
1816
+ style: {
1817
+ display: "block",
1818
+ width: "100%",
1819
+ padding: "0.75rem",
1820
+ background: theme.accent,
1821
+ color: theme.bg,
1822
+ textAlign: "center",
1823
+ textDecoration: "none",
1824
+ fontSize: "0.9rem",
1825
+ fontWeight: 600,
1826
+ boxSizing: "border-box",
1827
+ borderRadius: 0
1828
+ },
1829
+ children: "View Product"
1830
+ }
1831
+ )
1832
+ ] })
1833
+ ] }) });
1834
+ }
1835
+ function COAViewerSection({
1836
+ section,
1837
+ data,
1838
+ theme,
1839
+ onShowCOA
1840
+ }) {
1841
+ const coa = data.coa;
1842
+ const c = section.content;
1843
+ if (!coa) return null;
1844
+ return /* @__PURE__ */ jsx("div", { style: { padding: "1.5rem", maxWidth: 480, margin: "0 auto" }, children: /* @__PURE__ */ jsx(
1845
+ "button",
1846
+ {
1847
+ onClick: onShowCOA,
1848
+ style: {
1849
+ width: "100%",
1850
+ padding: "0.875rem",
1851
+ background: theme.accent,
1852
+ color: theme.bg,
1853
+ border: "none",
1854
+ fontSize: "0.95rem",
1855
+ fontWeight: 600,
1856
+ cursor: "pointer",
1857
+ borderRadius: 0
1858
+ },
1859
+ children: c.button_text || "View Lab Results"
1860
+ }
1861
+ ) });
1862
+ }
1863
+ function SocialLinksSection({ section, theme }) {
1864
+ const { links } = section.content;
1865
+ if (!links || links.length === 0) return null;
1866
+ return /* @__PURE__ */ jsx("div", { style: { padding: "1.5rem", display: "flex", justifyContent: "center", gap: "1.5rem", flexWrap: "wrap" }, children: links.map((link, i) => /* @__PURE__ */ jsx(
1867
+ "a",
1868
+ {
1869
+ href: link.url,
1870
+ target: "_blank",
1871
+ rel: "noopener noreferrer",
1872
+ style: {
1873
+ color: theme.muted,
1874
+ textDecoration: "none",
1875
+ fontSize: "0.85rem",
1876
+ fontWeight: 500,
1877
+ textTransform: "capitalize",
1878
+ letterSpacing: "0.03em",
1879
+ transition: "color 0.15s"
1880
+ },
1881
+ children: link.platform
1882
+ },
1883
+ i
1884
+ )) });
1885
+ }
1886
+ function DividerSection({ theme }) {
1887
+ return /* @__PURE__ */ jsx("div", { style: { padding: "1rem 1.5rem", maxWidth: 640, margin: "0 auto" }, children: /* @__PURE__ */ jsx("hr", { style: { border: "none", borderTop: `1px solid ${theme.surface}`, margin: 0 } }) });
1888
+ }
1889
+ function toEmbedUrl(url) {
1890
+ const ytMatch = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([\w-]+)/);
1891
+ if (ytMatch) return `https://www.youtube.com/embed/${ytMatch[1]}`;
1892
+ const vimeoMatch = url.match(/vimeo\.com\/(\d+)/);
1893
+ if (vimeoMatch) return `https://player.vimeo.com/video/${vimeoMatch[1]}`;
1894
+ return url;
1895
+ }
1896
+ var containerStyle2 = {
1897
+ minHeight: "100dvh",
1898
+ display: "flex",
1899
+ justifyContent: "center",
1900
+ alignItems: "center",
1901
+ fontFamily: "system-ui, -apple-system, sans-serif",
1902
+ background: "#050505",
1903
+ color: "#fafafa",
1904
+ textAlign: "center",
1905
+ padding: "2rem"
1906
+ };
1907
+ function DefaultLoading2() {
1908
+ return /* @__PURE__ */ jsx("div", { style: containerStyle2, children: /* @__PURE__ */ jsxs("div", { children: [
1909
+ /* @__PURE__ */ jsx("div", { style: { width: 32, height: 32, border: "2px solid #333", borderTopColor: "#fafafa", borderRadius: "50%", animation: "spin 0.8s linear infinite", margin: "0 auto 1rem" } }),
1910
+ /* @__PURE__ */ jsx("style", { children: `@keyframes spin { to { transform: rotate(360deg) } }` })
1911
+ ] }) });
1912
+ }
1913
+ function DefaultNotFound2() {
1914
+ return /* @__PURE__ */ jsx("div", { style: containerStyle2, children: /* @__PURE__ */ jsxs("div", { children: [
1915
+ /* @__PURE__ */ jsx("h1", { style: { fontSize: "1.5rem", marginBottom: "0.5rem" }, children: "Page Not Found" }),
1916
+ /* @__PURE__ */ jsx("p", { style: { color: "#888" }, children: "This page does not exist or has been removed." })
1917
+ ] }) });
1918
+ }
1919
+ function DefaultExpired2() {
1920
+ return /* @__PURE__ */ jsx("div", { style: containerStyle2, children: /* @__PURE__ */ jsxs("div", { children: [
1921
+ /* @__PURE__ */ jsx("h1", { style: { fontSize: "1.5rem", marginBottom: "0.5rem" }, children: "Page Expired" }),
1922
+ /* @__PURE__ */ jsx("p", { style: { color: "#888" }, children: "This page is no longer active." })
1923
+ ] }) });
1924
+ }
1925
+ function DefaultError2({ message }) {
1926
+ return /* @__PURE__ */ jsx("div", { style: containerStyle2, children: /* @__PURE__ */ jsxs("div", { children: [
1927
+ /* @__PURE__ */ jsx("h1", { style: { fontSize: "1.5rem", marginBottom: "0.5rem" }, children: "Something Went Wrong" }),
1928
+ /* @__PURE__ */ jsx("p", { style: { color: "#888" }, children: message || "Please try again later." })
1929
+ ] }) });
1930
+ }
715
1931
 
716
- export { AnalyticsTracker, AuthInitializer, CartInitializer, PixelInitializer, WhaleContext, WhaleProvider, useAnalytics, useAuth, useCart, useCartItemCount, useCartTotal, useCustomerAnalytics, useCustomerOrders, useProduct, useProducts, useWhaleClient };
1932
+ export { AnalyticsTracker, AuthInitializer, CartInitializer, LandingPage, PixelInitializer, QRLandingPage, WhaleContext, WhaleProvider, useAnalytics, useAuth, useCart, useCartItemCount, useCartTotal, useCategories, useCheckout, useCoupons, useCustomerAnalytics, useCustomerOrders, useLocations, useLoyalty, useProduct, useProducts, useRecommendations, useReviews, useSearch, useShipping, useWhaleClient, useWishlist };
717
1933
  //# sourceMappingURL=index.js.map
718
1934
  //# sourceMappingURL=index.js.map