@neowhale/storefront 0.2.5 → 0.2.8

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