@neowhale/storefront 0.2.4 → 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.
- package/README.md +15 -3
- package/dist/{chunk-PVO5ZGBN.cjs → chunk-3VKRKDPL.cjs} +167 -2
- package/dist/chunk-3VKRKDPL.cjs.map +1 -0
- package/dist/{chunk-NXNLD7YI.js → chunk-M2MR6C55.js} +167 -2
- package/dist/chunk-M2MR6C55.js.map +1 -0
- package/dist/client-KHvDGThs.d.cts +567 -0
- package/dist/client-KHvDGThs.d.ts +567 -0
- package/dist/index.cjs +3 -3
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/next/index.cjs +4 -4
- package/dist/next/index.d.cts +1 -1
- package/dist/next/index.d.ts +1 -1
- package/dist/next/index.js +1 -1
- package/dist/{pixel-manager-Db6Czwr2.d.cts → pixel-manager-CQaginxZ.d.cts} +1 -1
- package/dist/{pixel-manager-CYtRIGo0.d.ts → pixel-manager-Djp4Mh9L.d.ts} +1 -1
- package/dist/react/index.cjs +1240 -12
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +207 -8
- package/dist/react/index.d.ts +207 -8
- package/dist/react/index.js +1229 -13
- package/dist/react/index.js.map +1 -1
- package/package.json +12 -2
- package/whale-logo.png +0 -0
- package/dist/chunk-NXNLD7YI.js.map +0 -1
- package/dist/chunk-PVO5ZGBN.cjs.map +0 -1
- package/dist/client-y0V1x0px.d.cts +0 -305
- package/dist/client-y0V1x0px.d.ts +0 -305
package/dist/react/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { PixelManager } from '../chunk-NLH3W6JA.js';
|
|
2
|
-
import { WhaleClient } from '../chunk-
|
|
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 {
|
|
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 (
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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
|
-
|
|
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
|