shared-features 0.0.4 → 0.0.5
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/dist/{AdBanner-CZz8trdz.cjs → AnnouncementModal-Bqy0pn3V.cjs} +428 -3
- package/dist/AnnouncementModal-Bqy0pn3V.cjs.map +1 -0
- package/dist/{AdBanner-DF1QuMxR.js → AnnouncementModal-sxH4K5gy.js} +430 -5
- package/dist/AnnouncementModal-sxH4K5gy.js.map +1 -0
- package/dist/admin-notifications-D9n9h-eY.cjs +362 -0
- package/dist/admin-notifications-D9n9h-eY.cjs.map +1 -0
- package/dist/admin-notifications-p1dy3zIP.js +363 -0
- package/dist/admin-notifications-p1dy3zIP.js.map +1 -0
- package/dist/{analytics-CdpCtTpu.js → analytics-40-S_fHC.js} +3 -2
- package/dist/{analytics-CdpCtTpu.js.map → analytics-40-S_fHC.js.map} +1 -1
- package/dist/{analytics--ZSO9ova.cjs → analytics-lEzOx2vl.cjs} +2 -1
- package/dist/{analytics--ZSO9ova.cjs.map → analytics-lEzOx2vl.cjs.map} +1 -1
- package/dist/broadcasts-3_WfQMNL.cjs +278 -0
- package/dist/broadcasts-3_WfQMNL.cjs.map +1 -0
- package/dist/broadcasts-DgZUzqMf.js +257 -0
- package/dist/broadcasts-DgZUzqMf.js.map +1 -0
- package/dist/components/index.cjs +23 -20
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +4 -1
- package/dist/components/notifications/AnnouncementModal.d.ts +21 -0
- package/dist/components/notifications/AnnouncementModal.d.ts.map +1 -0
- package/dist/components/notifications/BroadcastBanner.d.ts +55 -0
- package/dist/components/notifications/BroadcastBanner.d.ts.map +1 -0
- package/dist/components/notifications/index.d.ts +11 -0
- package/dist/components/notifications/index.d.ts.map +1 -0
- package/dist/hooks/index.cjs +9 -1
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +9 -1
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/useBroadcasts.d.ts +122 -0
- package/dist/hooks/useBroadcasts.d.ts.map +1 -0
- package/dist/index.cjs +99 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +94 -17
- package/dist/index.js.map +1 -1
- package/dist/notifications/events/index.d.ts +14 -0
- package/dist/notifications/events/index.d.ts.map +1 -0
- package/dist/notifications/events/registry.d.ts +75 -0
- package/dist/notifications/events/registry.d.ts.map +1 -0
- package/dist/notifications/events/templates/engine.d.ts +57 -0
- package/dist/notifications/events/templates/engine.d.ts.map +1 -0
- package/dist/notifications/events/templates/standard.d.ts +20 -0
- package/dist/notifications/events/templates/standard.d.ts.map +1 -0
- package/dist/notifications/events/useNotificationEvents.d.ts +36 -0
- package/dist/notifications/events/useNotificationEvents.d.ts.map +1 -0
- package/dist/notifications/index.cjs +25 -0
- package/dist/notifications/index.cjs.map +1 -0
- package/dist/notifications/index.d.ts +9 -0
- package/dist/notifications/index.d.ts.map +1 -0
- package/dist/notifications/index.js +25 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/services/admin-notifications.d.ts +95 -0
- package/dist/services/admin-notifications.d.ts.map +1 -0
- package/dist/services/broadcasts.d.ts +55 -0
- package/dist/services/broadcasts.d.ts.map +1 -0
- package/dist/services/index.cjs +34 -1
- package/dist/services/index.cjs.map +1 -1
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +35 -2
- package/dist/services/index.js.map +1 -1
- package/dist/types/index.cjs +108 -0
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +109 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/notifications.d.ts +634 -0
- package/dist/types/notifications.d.ts.map +1 -0
- package/dist/useBroadcasts-DzpCcbC8.js +161 -0
- package/dist/useBroadcasts-DzpCcbC8.js.map +1 -0
- package/dist/useBroadcasts-FP6ZrcY_.cjs +160 -0
- package/dist/useBroadcasts-FP6ZrcY_.cjs.map +1 -0
- package/dist/{useCampaigns-Dltisb9N.cjs → useCampaigns-BOZ9dDsG.cjs} +2 -2
- package/dist/{useCampaigns-Dltisb9N.cjs.map → useCampaigns-BOZ9dDsG.cjs.map} +1 -1
- package/dist/{useCampaigns-nwfsALsN.js → useCampaigns-D46b9zuf.js} +2 -2
- package/dist/{useCampaigns-nwfsALsN.js.map → useCampaigns-D46b9zuf.js.map} +1 -1
- package/dist/useNotificationEvents-BXeMqdak.cjs +954 -0
- package/dist/useNotificationEvents-BXeMqdak.cjs.map +1 -0
- package/dist/useNotificationEvents-D8DVxah1.js +955 -0
- package/dist/useNotificationEvents-D8DVxah1.js.map +1 -0
- package/package.json +11 -3
- package/dist/AdBanner-CZz8trdz.cjs.map +0 -1
- package/dist/AdBanner-DF1QuMxR.js.map +0 -1
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { useState, useRef, useCallback, useEffect } from "react";
|
|
2
|
+
import { a as fetchActiveBroadcasts, s as subscribeToBroadcasts, d as trackBroadcastDismiss, t as trackBroadcastImpression, c as trackBroadcastClick, j as clearBroadcastsCache } from "./broadcasts-DgZUzqMf.js";
|
|
3
|
+
import { e as isInitialized } from "./analytics-40-S_fHC.js";
|
|
4
|
+
function useBroadcasts(options = {}) {
|
|
5
|
+
const {
|
|
6
|
+
variant,
|
|
7
|
+
platform,
|
|
8
|
+
maxBroadcasts = 10,
|
|
9
|
+
autoFetch = true,
|
|
10
|
+
realtime = false
|
|
11
|
+
} = options;
|
|
12
|
+
const [broadcasts, setBroadcasts] = useState([]);
|
|
13
|
+
const [isLoading, setIsLoading] = useState(autoFetch);
|
|
14
|
+
const [error, setError] = useState(null);
|
|
15
|
+
const dismissedIdsRef = useRef(/* @__PURE__ */ new Set());
|
|
16
|
+
const impressionTrackedRef = useRef(/* @__PURE__ */ new Set());
|
|
17
|
+
const fetchBroadcastsData = useCallback(async () => {
|
|
18
|
+
if (!isInitialized()) {
|
|
19
|
+
setError(new Error("shared-features not initialized"));
|
|
20
|
+
setIsLoading(false);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
setIsLoading(true);
|
|
24
|
+
setError(null);
|
|
25
|
+
try {
|
|
26
|
+
const allBroadcasts = await fetchActiveBroadcasts({
|
|
27
|
+
variant,
|
|
28
|
+
platform
|
|
29
|
+
});
|
|
30
|
+
const filteredBroadcasts = allBroadcasts.filter((b) => !dismissedIdsRef.current.has(b.id)).slice(0, maxBroadcasts);
|
|
31
|
+
setBroadcasts(filteredBroadcasts);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
const errorObj = err instanceof Error ? err : new Error("Failed to fetch broadcasts");
|
|
34
|
+
setError(errorObj);
|
|
35
|
+
console.error("[shared-features] Error fetching broadcasts:", err);
|
|
36
|
+
} finally {
|
|
37
|
+
setIsLoading(false);
|
|
38
|
+
}
|
|
39
|
+
}, [variant, platform, maxBroadcasts]);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (autoFetch) {
|
|
42
|
+
fetchBroadcastsData();
|
|
43
|
+
}
|
|
44
|
+
}, [autoFetch, fetchBroadcastsData]);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (!realtime || !isInitialized()) return;
|
|
47
|
+
const unsubscribe = subscribeToBroadcasts(
|
|
48
|
+
(newBroadcasts) => {
|
|
49
|
+
const filtered = newBroadcasts.filter((b) => !dismissedIdsRef.current.has(b.id)).filter((b) => !variant || b.variant === variant).slice(0, maxBroadcasts);
|
|
50
|
+
setBroadcasts(filtered);
|
|
51
|
+
setIsLoading(false);
|
|
52
|
+
},
|
|
53
|
+
{ variant, platform }
|
|
54
|
+
);
|
|
55
|
+
return () => {
|
|
56
|
+
unsubscribe();
|
|
57
|
+
};
|
|
58
|
+
}, [realtime, variant, platform, maxBroadcasts]);
|
|
59
|
+
const handleDismissBroadcast = useCallback((broadcastId) => {
|
|
60
|
+
dismissedIdsRef.current.add(broadcastId);
|
|
61
|
+
setBroadcasts((prev) => prev.filter((b) => b.id !== broadcastId));
|
|
62
|
+
trackBroadcastDismiss(broadcastId).catch((err) => {
|
|
63
|
+
console.error("[shared-features] Failed to track dismiss:", err);
|
|
64
|
+
});
|
|
65
|
+
}, []);
|
|
66
|
+
const handleTrackImpression = useCallback((broadcastId) => {
|
|
67
|
+
if (impressionTrackedRef.current.has(broadcastId)) return;
|
|
68
|
+
impressionTrackedRef.current.add(broadcastId);
|
|
69
|
+
trackBroadcastImpression(broadcastId).catch((err) => {
|
|
70
|
+
console.error("[shared-features] Failed to track impression:", err);
|
|
71
|
+
});
|
|
72
|
+
}, []);
|
|
73
|
+
const handleTrackClick = useCallback((broadcastId) => {
|
|
74
|
+
trackBroadcastClick(broadcastId).catch((err) => {
|
|
75
|
+
console.error("[shared-features] Failed to track click:", err);
|
|
76
|
+
});
|
|
77
|
+
}, []);
|
|
78
|
+
const checkIsDismissed = useCallback((broadcastId) => {
|
|
79
|
+
return dismissedIdsRef.current.has(broadcastId);
|
|
80
|
+
}, []);
|
|
81
|
+
const handleRefresh = useCallback(async () => {
|
|
82
|
+
clearBroadcastsCache();
|
|
83
|
+
await fetchBroadcastsData();
|
|
84
|
+
}, [fetchBroadcastsData]);
|
|
85
|
+
return {
|
|
86
|
+
broadcasts,
|
|
87
|
+
isLoading,
|
|
88
|
+
error,
|
|
89
|
+
dismissBroadcast: handleDismissBroadcast,
|
|
90
|
+
trackImpression: handleTrackImpression,
|
|
91
|
+
trackClick: handleTrackClick,
|
|
92
|
+
isDismissed: checkIsDismissed,
|
|
93
|
+
refresh: handleRefresh
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function useBannerBroadcasts(options = {}) {
|
|
97
|
+
return useBroadcasts({ ...options, variant: "banner" });
|
|
98
|
+
}
|
|
99
|
+
function useModalBroadcasts(options = {}) {
|
|
100
|
+
return useBroadcasts({ ...options, variant: "modal", maxBroadcasts: 1 });
|
|
101
|
+
}
|
|
102
|
+
function useToastBroadcasts(options = {}) {
|
|
103
|
+
return useBroadcasts({ ...options, variant: "toast" });
|
|
104
|
+
}
|
|
105
|
+
function useBellBroadcasts(options = {}) {
|
|
106
|
+
return useBroadcasts({ ...options, variant: "bell" });
|
|
107
|
+
}
|
|
108
|
+
function useSingleBroadcast(options = {}) {
|
|
109
|
+
const result = useBroadcasts({ ...options, maxBroadcasts: 1 });
|
|
110
|
+
const broadcast = result.broadcasts[0] || null;
|
|
111
|
+
return {
|
|
112
|
+
broadcast,
|
|
113
|
+
isLoading: result.isLoading,
|
|
114
|
+
error: result.error,
|
|
115
|
+
dismiss: () => broadcast && result.dismissBroadcast(broadcast.id),
|
|
116
|
+
trackImpression: () => broadcast && result.trackImpression(broadcast.id),
|
|
117
|
+
trackClick: () => broadcast && result.trackClick(broadcast.id),
|
|
118
|
+
isDismissed: broadcast ? result.isDismissed(broadcast.id) : false
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function useAnnouncementModal() {
|
|
122
|
+
const { broadcast, dismiss, trackImpression, trackClick } = useSingleBroadcast({
|
|
123
|
+
variant: "modal"
|
|
124
|
+
});
|
|
125
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
126
|
+
const impressionTrackedRef = useRef(false);
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
if (broadcast && !impressionTrackedRef.current) {
|
|
129
|
+
setIsOpen(true);
|
|
130
|
+
trackImpression();
|
|
131
|
+
impressionTrackedRef.current = true;
|
|
132
|
+
}
|
|
133
|
+
}, [broadcast, trackImpression]);
|
|
134
|
+
const handleClose = useCallback(() => {
|
|
135
|
+
setIsOpen(false);
|
|
136
|
+
dismiss();
|
|
137
|
+
}, [dismiss]);
|
|
138
|
+
const handleAction = useCallback(() => {
|
|
139
|
+
trackClick();
|
|
140
|
+
if (broadcast?.actionUrl) {
|
|
141
|
+
window.open(broadcast.actionUrl, "_blank");
|
|
142
|
+
}
|
|
143
|
+
handleClose();
|
|
144
|
+
}, [broadcast, trackClick, handleClose]);
|
|
145
|
+
return {
|
|
146
|
+
broadcast,
|
|
147
|
+
isOpen,
|
|
148
|
+
close: handleClose,
|
|
149
|
+
handleAction
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
export {
|
|
153
|
+
useBannerBroadcasts as a,
|
|
154
|
+
useModalBroadcasts as b,
|
|
155
|
+
useToastBroadcasts as c,
|
|
156
|
+
useBellBroadcasts as d,
|
|
157
|
+
useSingleBroadcast as e,
|
|
158
|
+
useAnnouncementModal as f,
|
|
159
|
+
useBroadcasts as u
|
|
160
|
+
};
|
|
161
|
+
//# sourceMappingURL=useBroadcasts-DzpCcbC8.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useBroadcasts-DzpCcbC8.js","sources":["../src/hooks/useBroadcasts.ts"],"sourcesContent":["/**\n * useBroadcasts Hook\n *\n * React hook for fetching and displaying broadcast notifications in consumer projects.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport { useState, useEffect, useCallback, useRef } from 'react';\nimport {\n fetchActiveBroadcasts,\n subscribeToBroadcasts,\n trackBroadcastImpression,\n trackBroadcastClick,\n trackBroadcastDismiss,\n clearBroadcastsCache,\n} from '../services/broadcasts';\nimport { isInitialized } from '../firebase/config';\nimport type {\n BroadcastNotification,\n BroadcastVariant,\n NotificationPlatform,\n UseBroadcastsReturn,\n} from '../types/notifications';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\nexport interface UseBroadcastsOptions {\n /** Filter by variant type */\n variant?: BroadcastVariant;\n /** Override platform detection */\n platform?: NotificationPlatform;\n /** Maximum number of broadcasts to return */\n maxBroadcasts?: number;\n /** Whether to auto-fetch on mount (default: true) */\n autoFetch?: boolean;\n /** Whether to subscribe to real-time updates (default: false) */\n realtime?: boolean;\n}\n\n// ============================================================================\n// MAIN HOOK\n// ============================================================================\n\n/**\n * Hook to fetch and manage broadcast notifications\n *\n * @example\n * ```tsx\n * const { broadcasts, isLoading, dismissBroadcast, trackClick } = useBroadcasts({\n * variant: 'banner',\n * maxBroadcasts: 3,\n * });\n *\n * return (\n * <div>\n * {broadcasts.map(broadcast => (\n * <BroadcastBanner\n * key={broadcast.id}\n * broadcast={broadcast}\n * onDismiss={() => dismissBroadcast(broadcast.id)}\n * onActionClick={() => trackClick(broadcast.id)}\n * />\n * ))}\n * </div>\n * );\n * ```\n */\nexport function useBroadcasts(\n options: UseBroadcastsOptions = {}\n): UseBroadcastsReturn {\n const {\n variant,\n platform,\n maxBroadcasts = 10,\n autoFetch = true,\n realtime = false,\n } = options;\n\n const [broadcasts, setBroadcasts] = useState<BroadcastNotification[]>([]);\n const [isLoading, setIsLoading] = useState(autoFetch);\n const [error, setError] = useState<Error | null>(null);\n const dismissedIdsRef = useRef<Set<string>>(new Set());\n const impressionTrackedRef = useRef<Set<string>>(new Set());\n\n // Fetch broadcasts\n const fetchBroadcastsData = useCallback(async () => {\n if (!isInitialized()) {\n setError(new Error('shared-features not initialized'));\n setIsLoading(false);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const allBroadcasts = await fetchActiveBroadcasts({\n variant,\n platform,\n });\n\n // Filter out locally dismissed and limit count\n const filteredBroadcasts = allBroadcasts\n .filter((b) => !dismissedIdsRef.current.has(b.id))\n .slice(0, maxBroadcasts);\n\n setBroadcasts(filteredBroadcasts);\n } catch (err) {\n const errorObj =\n err instanceof Error ? err : new Error('Failed to fetch broadcasts');\n setError(errorObj);\n console.error('[shared-features] Error fetching broadcasts:', err);\n } finally {\n setIsLoading(false);\n }\n }, [variant, platform, maxBroadcasts]);\n\n // Auto-fetch on mount\n useEffect(() => {\n if (autoFetch) {\n fetchBroadcastsData();\n }\n }, [autoFetch, fetchBroadcastsData]);\n\n // Real-time subscription\n useEffect(() => {\n if (!realtime || !isInitialized()) return;\n\n const unsubscribe = subscribeToBroadcasts(\n (newBroadcasts) => {\n // Filter and limit\n const filtered = newBroadcasts\n .filter((b) => !dismissedIdsRef.current.has(b.id))\n .filter((b) => !variant || b.variant === variant)\n .slice(0, maxBroadcasts);\n\n setBroadcasts(filtered);\n setIsLoading(false);\n },\n { variant, platform }\n );\n\n return () => {\n unsubscribe();\n };\n }, [realtime, variant, platform, maxBroadcasts]);\n\n // Dismiss broadcast\n const handleDismissBroadcast = useCallback((broadcastId: string) => {\n // Update local state immediately\n dismissedIdsRef.current.add(broadcastId);\n setBroadcasts((prev) => prev.filter((b) => b.id !== broadcastId));\n\n // Track dismiss and persist\n trackBroadcastDismiss(broadcastId).catch((err) => {\n console.error('[shared-features] Failed to track dismiss:', err);\n });\n }, []);\n\n // Track impression\n const handleTrackImpression = useCallback((broadcastId: string) => {\n // Only track once per session\n if (impressionTrackedRef.current.has(broadcastId)) return;\n impressionTrackedRef.current.add(broadcastId);\n\n trackBroadcastImpression(broadcastId).catch((err) => {\n console.error('[shared-features] Failed to track impression:', err);\n });\n }, []);\n\n // Track click\n const handleTrackClick = useCallback((broadcastId: string) => {\n trackBroadcastClick(broadcastId).catch((err) => {\n console.error('[shared-features] Failed to track click:', err);\n });\n }, []);\n\n // Check if dismissed\n const checkIsDismissed = useCallback((broadcastId: string) => {\n return dismissedIdsRef.current.has(broadcastId);\n }, []);\n\n // Refresh broadcasts\n const handleRefresh = useCallback(async () => {\n clearBroadcastsCache();\n await fetchBroadcastsData();\n }, [fetchBroadcastsData]);\n\n return {\n broadcasts,\n isLoading,\n error,\n dismissBroadcast: handleDismissBroadcast,\n trackImpression: handleTrackImpression,\n trackClick: handleTrackClick,\n isDismissed: checkIsDismissed,\n refresh: handleRefresh,\n };\n}\n\n// ============================================================================\n// SPECIALIZED HOOKS\n// ============================================================================\n\n/**\n * Hook specifically for banner broadcasts\n */\nexport function useBannerBroadcasts(\n options: Omit<UseBroadcastsOptions, 'variant'> = {}\n) {\n return useBroadcasts({ ...options, variant: 'banner' });\n}\n\n/**\n * Hook specifically for modal broadcasts\n */\nexport function useModalBroadcasts(\n options: Omit<UseBroadcastsOptions, 'variant'> = {}\n) {\n return useBroadcasts({ ...options, variant: 'modal', maxBroadcasts: 1 });\n}\n\n/**\n * Hook specifically for toast broadcasts\n */\nexport function useToastBroadcasts(\n options: Omit<UseBroadcastsOptions, 'variant'> = {}\n) {\n return useBroadcasts({ ...options, variant: 'toast' });\n}\n\n/**\n * Hook specifically for bell/notification center broadcasts\n */\nexport function useBellBroadcasts(\n options: Omit<UseBroadcastsOptions, 'variant'> = {}\n) {\n return useBroadcasts({ ...options, variant: 'bell' });\n}\n\n// ============================================================================\n// SINGLE BROADCAST HOOK\n// ============================================================================\n\nexport interface UseSingleBroadcastReturn {\n /** The broadcast (first matching one) */\n broadcast: BroadcastNotification | null;\n /** Whether loading */\n isLoading: boolean;\n /** Error if any */\n error: Error | null;\n /** Dismiss the broadcast */\n dismiss: () => void;\n /** Track impression */\n trackImpression: () => void;\n /** Track click */\n trackClick: () => void;\n /** Whether broadcast was dismissed */\n isDismissed: boolean;\n}\n\n/**\n * Hook to get a single broadcast (convenience wrapper)\n *\n * @example\n * ```tsx\n * const { broadcast, dismiss, trackClick } = useSingleBroadcast({ variant: 'modal' });\n *\n * if (!broadcast) return null;\n *\n * return (\n * <AnnouncementModal\n * broadcast={broadcast}\n * onClose={dismiss}\n * onActionClick={trackClick}\n * />\n * );\n * ```\n */\nexport function useSingleBroadcast(\n options: Omit<UseBroadcastsOptions, 'maxBroadcasts'> = {}\n): UseSingleBroadcastReturn {\n const result = useBroadcasts({ ...options, maxBroadcasts: 1 });\n const broadcast = result.broadcasts[0] || null;\n\n return {\n broadcast,\n isLoading: result.isLoading,\n error: result.error,\n dismiss: () => broadcast && result.dismissBroadcast(broadcast.id),\n trackImpression: () => broadcast && result.trackImpression(broadcast.id),\n trackClick: () => broadcast && result.trackClick(broadcast.id),\n isDismissed: broadcast ? result.isDismissed(broadcast.id) : false,\n };\n}\n\n// ============================================================================\n// ANNOUNCEMENT MODAL HOOK\n// ============================================================================\n\nexport interface UseAnnouncementModalReturn {\n /** The modal broadcast to display */\n broadcast: BroadcastNotification | null;\n /** Whether the modal should be shown */\n isOpen: boolean;\n /** Close the modal */\n close: () => void;\n /** Handle action button click */\n handleAction: () => void;\n}\n\n/**\n * Hook for managing announcement modal display\n * Automatically tracks impressions and handles dismissal\n *\n * @example\n * ```tsx\n * const { broadcast, isOpen, close, handleAction } = useAnnouncementModal();\n *\n * return (\n * <Dialog open={isOpen} onOpenChange={(open) => !open && close()}>\n * <DialogContent>\n * <h2>{broadcast?.title}</h2>\n * <p>{broadcast?.message}</p>\n * {broadcast?.actionUrl && (\n * <Button onClick={handleAction}>{broadcast.actionText || 'Learn More'}</Button>\n * )}\n * </DialogContent>\n * </Dialog>\n * );\n * ```\n */\nexport function useAnnouncementModal(): UseAnnouncementModalReturn {\n const { broadcast, dismiss, trackImpression, trackClick } = useSingleBroadcast({\n variant: 'modal',\n });\n\n const [isOpen, setIsOpen] = useState(false);\n const impressionTrackedRef = useRef(false);\n\n // Show modal when broadcast is available\n useEffect(() => {\n if (broadcast && !impressionTrackedRef.current) {\n setIsOpen(true);\n trackImpression();\n impressionTrackedRef.current = true;\n }\n }, [broadcast, trackImpression]);\n\n const handleClose = useCallback(() => {\n setIsOpen(false);\n dismiss();\n }, [dismiss]);\n\n const handleAction = useCallback(() => {\n trackClick();\n if (broadcast?.actionUrl) {\n window.open(broadcast.actionUrl, '_blank');\n }\n handleClose();\n }, [broadcast, trackClick, handleClose]);\n\n return {\n broadcast,\n isOpen,\n close: handleClose,\n handleAction,\n };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const react = require("react");
|
|
3
|
+
const broadcasts = require("./broadcasts-3_WfQMNL.cjs");
|
|
4
|
+
const analytics = require("./analytics-lEzOx2vl.cjs");
|
|
5
|
+
function useBroadcasts(options = {}) {
|
|
6
|
+
const {
|
|
7
|
+
variant,
|
|
8
|
+
platform,
|
|
9
|
+
maxBroadcasts = 10,
|
|
10
|
+
autoFetch = true,
|
|
11
|
+
realtime = false
|
|
12
|
+
} = options;
|
|
13
|
+
const [broadcasts$1, setBroadcasts] = react.useState([]);
|
|
14
|
+
const [isLoading, setIsLoading] = react.useState(autoFetch);
|
|
15
|
+
const [error, setError] = react.useState(null);
|
|
16
|
+
const dismissedIdsRef = react.useRef(/* @__PURE__ */ new Set());
|
|
17
|
+
const impressionTrackedRef = react.useRef(/* @__PURE__ */ new Set());
|
|
18
|
+
const fetchBroadcastsData = react.useCallback(async () => {
|
|
19
|
+
if (!analytics.isInitialized()) {
|
|
20
|
+
setError(new Error("shared-features not initialized"));
|
|
21
|
+
setIsLoading(false);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
setIsLoading(true);
|
|
25
|
+
setError(null);
|
|
26
|
+
try {
|
|
27
|
+
const allBroadcasts = await broadcasts.fetchActiveBroadcasts({
|
|
28
|
+
variant,
|
|
29
|
+
platform
|
|
30
|
+
});
|
|
31
|
+
const filteredBroadcasts = allBroadcasts.filter((b) => !dismissedIdsRef.current.has(b.id)).slice(0, maxBroadcasts);
|
|
32
|
+
setBroadcasts(filteredBroadcasts);
|
|
33
|
+
} catch (err) {
|
|
34
|
+
const errorObj = err instanceof Error ? err : new Error("Failed to fetch broadcasts");
|
|
35
|
+
setError(errorObj);
|
|
36
|
+
console.error("[shared-features] Error fetching broadcasts:", err);
|
|
37
|
+
} finally {
|
|
38
|
+
setIsLoading(false);
|
|
39
|
+
}
|
|
40
|
+
}, [variant, platform, maxBroadcasts]);
|
|
41
|
+
react.useEffect(() => {
|
|
42
|
+
if (autoFetch) {
|
|
43
|
+
fetchBroadcastsData();
|
|
44
|
+
}
|
|
45
|
+
}, [autoFetch, fetchBroadcastsData]);
|
|
46
|
+
react.useEffect(() => {
|
|
47
|
+
if (!realtime || !analytics.isInitialized()) return;
|
|
48
|
+
const unsubscribe = broadcasts.subscribeToBroadcasts(
|
|
49
|
+
(newBroadcasts) => {
|
|
50
|
+
const filtered = newBroadcasts.filter((b) => !dismissedIdsRef.current.has(b.id)).filter((b) => !variant || b.variant === variant).slice(0, maxBroadcasts);
|
|
51
|
+
setBroadcasts(filtered);
|
|
52
|
+
setIsLoading(false);
|
|
53
|
+
},
|
|
54
|
+
{ variant, platform }
|
|
55
|
+
);
|
|
56
|
+
return () => {
|
|
57
|
+
unsubscribe();
|
|
58
|
+
};
|
|
59
|
+
}, [realtime, variant, platform, maxBroadcasts]);
|
|
60
|
+
const handleDismissBroadcast = react.useCallback((broadcastId) => {
|
|
61
|
+
dismissedIdsRef.current.add(broadcastId);
|
|
62
|
+
setBroadcasts((prev) => prev.filter((b) => b.id !== broadcastId));
|
|
63
|
+
broadcasts.trackBroadcastDismiss(broadcastId).catch((err) => {
|
|
64
|
+
console.error("[shared-features] Failed to track dismiss:", err);
|
|
65
|
+
});
|
|
66
|
+
}, []);
|
|
67
|
+
const handleTrackImpression = react.useCallback((broadcastId) => {
|
|
68
|
+
if (impressionTrackedRef.current.has(broadcastId)) return;
|
|
69
|
+
impressionTrackedRef.current.add(broadcastId);
|
|
70
|
+
broadcasts.trackBroadcastImpression(broadcastId).catch((err) => {
|
|
71
|
+
console.error("[shared-features] Failed to track impression:", err);
|
|
72
|
+
});
|
|
73
|
+
}, []);
|
|
74
|
+
const handleTrackClick = react.useCallback((broadcastId) => {
|
|
75
|
+
broadcasts.trackBroadcastClick(broadcastId).catch((err) => {
|
|
76
|
+
console.error("[shared-features] Failed to track click:", err);
|
|
77
|
+
});
|
|
78
|
+
}, []);
|
|
79
|
+
const checkIsDismissed = react.useCallback((broadcastId) => {
|
|
80
|
+
return dismissedIdsRef.current.has(broadcastId);
|
|
81
|
+
}, []);
|
|
82
|
+
const handleRefresh = react.useCallback(async () => {
|
|
83
|
+
broadcasts.clearBroadcastsCache();
|
|
84
|
+
await fetchBroadcastsData();
|
|
85
|
+
}, [fetchBroadcastsData]);
|
|
86
|
+
return {
|
|
87
|
+
broadcasts: broadcasts$1,
|
|
88
|
+
isLoading,
|
|
89
|
+
error,
|
|
90
|
+
dismissBroadcast: handleDismissBroadcast,
|
|
91
|
+
trackImpression: handleTrackImpression,
|
|
92
|
+
trackClick: handleTrackClick,
|
|
93
|
+
isDismissed: checkIsDismissed,
|
|
94
|
+
refresh: handleRefresh
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function useBannerBroadcasts(options = {}) {
|
|
98
|
+
return useBroadcasts({ ...options, variant: "banner" });
|
|
99
|
+
}
|
|
100
|
+
function useModalBroadcasts(options = {}) {
|
|
101
|
+
return useBroadcasts({ ...options, variant: "modal", maxBroadcasts: 1 });
|
|
102
|
+
}
|
|
103
|
+
function useToastBroadcasts(options = {}) {
|
|
104
|
+
return useBroadcasts({ ...options, variant: "toast" });
|
|
105
|
+
}
|
|
106
|
+
function useBellBroadcasts(options = {}) {
|
|
107
|
+
return useBroadcasts({ ...options, variant: "bell" });
|
|
108
|
+
}
|
|
109
|
+
function useSingleBroadcast(options = {}) {
|
|
110
|
+
const result = useBroadcasts({ ...options, maxBroadcasts: 1 });
|
|
111
|
+
const broadcast = result.broadcasts[0] || null;
|
|
112
|
+
return {
|
|
113
|
+
broadcast,
|
|
114
|
+
isLoading: result.isLoading,
|
|
115
|
+
error: result.error,
|
|
116
|
+
dismiss: () => broadcast && result.dismissBroadcast(broadcast.id),
|
|
117
|
+
trackImpression: () => broadcast && result.trackImpression(broadcast.id),
|
|
118
|
+
trackClick: () => broadcast && result.trackClick(broadcast.id),
|
|
119
|
+
isDismissed: broadcast ? result.isDismissed(broadcast.id) : false
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function useAnnouncementModal() {
|
|
123
|
+
const { broadcast, dismiss, trackImpression, trackClick } = useSingleBroadcast({
|
|
124
|
+
variant: "modal"
|
|
125
|
+
});
|
|
126
|
+
const [isOpen, setIsOpen] = react.useState(false);
|
|
127
|
+
const impressionTrackedRef = react.useRef(false);
|
|
128
|
+
react.useEffect(() => {
|
|
129
|
+
if (broadcast && !impressionTrackedRef.current) {
|
|
130
|
+
setIsOpen(true);
|
|
131
|
+
trackImpression();
|
|
132
|
+
impressionTrackedRef.current = true;
|
|
133
|
+
}
|
|
134
|
+
}, [broadcast, trackImpression]);
|
|
135
|
+
const handleClose = react.useCallback(() => {
|
|
136
|
+
setIsOpen(false);
|
|
137
|
+
dismiss();
|
|
138
|
+
}, [dismiss]);
|
|
139
|
+
const handleAction = react.useCallback(() => {
|
|
140
|
+
trackClick();
|
|
141
|
+
if (broadcast?.actionUrl) {
|
|
142
|
+
window.open(broadcast.actionUrl, "_blank");
|
|
143
|
+
}
|
|
144
|
+
handleClose();
|
|
145
|
+
}, [broadcast, trackClick, handleClose]);
|
|
146
|
+
return {
|
|
147
|
+
broadcast,
|
|
148
|
+
isOpen,
|
|
149
|
+
close: handleClose,
|
|
150
|
+
handleAction
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
exports.useAnnouncementModal = useAnnouncementModal;
|
|
154
|
+
exports.useBannerBroadcasts = useBannerBroadcasts;
|
|
155
|
+
exports.useBellBroadcasts = useBellBroadcasts;
|
|
156
|
+
exports.useBroadcasts = useBroadcasts;
|
|
157
|
+
exports.useModalBroadcasts = useModalBroadcasts;
|
|
158
|
+
exports.useSingleBroadcast = useSingleBroadcast;
|
|
159
|
+
exports.useToastBroadcasts = useToastBroadcasts;
|
|
160
|
+
//# sourceMappingURL=useBroadcasts-FP6ZrcY_.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useBroadcasts-FP6ZrcY_.cjs","sources":["../src/hooks/useBroadcasts.ts"],"sourcesContent":["/**\n * useBroadcasts Hook\n *\n * React hook for fetching and displaying broadcast notifications in consumer projects.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport { useState, useEffect, useCallback, useRef } from 'react';\nimport {\n fetchActiveBroadcasts,\n subscribeToBroadcasts,\n trackBroadcastImpression,\n trackBroadcastClick,\n trackBroadcastDismiss,\n clearBroadcastsCache,\n} from '../services/broadcasts';\nimport { isInitialized } from '../firebase/config';\nimport type {\n BroadcastNotification,\n BroadcastVariant,\n NotificationPlatform,\n UseBroadcastsReturn,\n} from '../types/notifications';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\nexport interface UseBroadcastsOptions {\n /** Filter by variant type */\n variant?: BroadcastVariant;\n /** Override platform detection */\n platform?: NotificationPlatform;\n /** Maximum number of broadcasts to return */\n maxBroadcasts?: number;\n /** Whether to auto-fetch on mount (default: true) */\n autoFetch?: boolean;\n /** Whether to subscribe to real-time updates (default: false) */\n realtime?: boolean;\n}\n\n// ============================================================================\n// MAIN HOOK\n// ============================================================================\n\n/**\n * Hook to fetch and manage broadcast notifications\n *\n * @example\n * ```tsx\n * const { broadcasts, isLoading, dismissBroadcast, trackClick } = useBroadcasts({\n * variant: 'banner',\n * maxBroadcasts: 3,\n * });\n *\n * return (\n * <div>\n * {broadcasts.map(broadcast => (\n * <BroadcastBanner\n * key={broadcast.id}\n * broadcast={broadcast}\n * onDismiss={() => dismissBroadcast(broadcast.id)}\n * onActionClick={() => trackClick(broadcast.id)}\n * />\n * ))}\n * </div>\n * );\n * ```\n */\nexport function useBroadcasts(\n options: UseBroadcastsOptions = {}\n): UseBroadcastsReturn {\n const {\n variant,\n platform,\n maxBroadcasts = 10,\n autoFetch = true,\n realtime = false,\n } = options;\n\n const [broadcasts, setBroadcasts] = useState<BroadcastNotification[]>([]);\n const [isLoading, setIsLoading] = useState(autoFetch);\n const [error, setError] = useState<Error | null>(null);\n const dismissedIdsRef = useRef<Set<string>>(new Set());\n const impressionTrackedRef = useRef<Set<string>>(new Set());\n\n // Fetch broadcasts\n const fetchBroadcastsData = useCallback(async () => {\n if (!isInitialized()) {\n setError(new Error('shared-features not initialized'));\n setIsLoading(false);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const allBroadcasts = await fetchActiveBroadcasts({\n variant,\n platform,\n });\n\n // Filter out locally dismissed and limit count\n const filteredBroadcasts = allBroadcasts\n .filter((b) => !dismissedIdsRef.current.has(b.id))\n .slice(0, maxBroadcasts);\n\n setBroadcasts(filteredBroadcasts);\n } catch (err) {\n const errorObj =\n err instanceof Error ? err : new Error('Failed to fetch broadcasts');\n setError(errorObj);\n console.error('[shared-features] Error fetching broadcasts:', err);\n } finally {\n setIsLoading(false);\n }\n }, [variant, platform, maxBroadcasts]);\n\n // Auto-fetch on mount\n useEffect(() => {\n if (autoFetch) {\n fetchBroadcastsData();\n }\n }, [autoFetch, fetchBroadcastsData]);\n\n // Real-time subscription\n useEffect(() => {\n if (!realtime || !isInitialized()) return;\n\n const unsubscribe = subscribeToBroadcasts(\n (newBroadcasts) => {\n // Filter and limit\n const filtered = newBroadcasts\n .filter((b) => !dismissedIdsRef.current.has(b.id))\n .filter((b) => !variant || b.variant === variant)\n .slice(0, maxBroadcasts);\n\n setBroadcasts(filtered);\n setIsLoading(false);\n },\n { variant, platform }\n );\n\n return () => {\n unsubscribe();\n };\n }, [realtime, variant, platform, maxBroadcasts]);\n\n // Dismiss broadcast\n const handleDismissBroadcast = useCallback((broadcastId: string) => {\n // Update local state immediately\n dismissedIdsRef.current.add(broadcastId);\n setBroadcasts((prev) => prev.filter((b) => b.id !== broadcastId));\n\n // Track dismiss and persist\n trackBroadcastDismiss(broadcastId).catch((err) => {\n console.error('[shared-features] Failed to track dismiss:', err);\n });\n }, []);\n\n // Track impression\n const handleTrackImpression = useCallback((broadcastId: string) => {\n // Only track once per session\n if (impressionTrackedRef.current.has(broadcastId)) return;\n impressionTrackedRef.current.add(broadcastId);\n\n trackBroadcastImpression(broadcastId).catch((err) => {\n console.error('[shared-features] Failed to track impression:', err);\n });\n }, []);\n\n // Track click\n const handleTrackClick = useCallback((broadcastId: string) => {\n trackBroadcastClick(broadcastId).catch((err) => {\n console.error('[shared-features] Failed to track click:', err);\n });\n }, []);\n\n // Check if dismissed\n const checkIsDismissed = useCallback((broadcastId: string) => {\n return dismissedIdsRef.current.has(broadcastId);\n }, []);\n\n // Refresh broadcasts\n const handleRefresh = useCallback(async () => {\n clearBroadcastsCache();\n await fetchBroadcastsData();\n }, [fetchBroadcastsData]);\n\n return {\n broadcasts,\n isLoading,\n error,\n dismissBroadcast: handleDismissBroadcast,\n trackImpression: handleTrackImpression,\n trackClick: handleTrackClick,\n isDismissed: checkIsDismissed,\n refresh: handleRefresh,\n };\n}\n\n// ============================================================================\n// SPECIALIZED HOOKS\n// ============================================================================\n\n/**\n * Hook specifically for banner broadcasts\n */\nexport function useBannerBroadcasts(\n options: Omit<UseBroadcastsOptions, 'variant'> = {}\n) {\n return useBroadcasts({ ...options, variant: 'banner' });\n}\n\n/**\n * Hook specifically for modal broadcasts\n */\nexport function useModalBroadcasts(\n options: Omit<UseBroadcastsOptions, 'variant'> = {}\n) {\n return useBroadcasts({ ...options, variant: 'modal', maxBroadcasts: 1 });\n}\n\n/**\n * Hook specifically for toast broadcasts\n */\nexport function useToastBroadcasts(\n options: Omit<UseBroadcastsOptions, 'variant'> = {}\n) {\n return useBroadcasts({ ...options, variant: 'toast' });\n}\n\n/**\n * Hook specifically for bell/notification center broadcasts\n */\nexport function useBellBroadcasts(\n options: Omit<UseBroadcastsOptions, 'variant'> = {}\n) {\n return useBroadcasts({ ...options, variant: 'bell' });\n}\n\n// ============================================================================\n// SINGLE BROADCAST HOOK\n// ============================================================================\n\nexport interface UseSingleBroadcastReturn {\n /** The broadcast (first matching one) */\n broadcast: BroadcastNotification | null;\n /** Whether loading */\n isLoading: boolean;\n /** Error if any */\n error: Error | null;\n /** Dismiss the broadcast */\n dismiss: () => void;\n /** Track impression */\n trackImpression: () => void;\n /** Track click */\n trackClick: () => void;\n /** Whether broadcast was dismissed */\n isDismissed: boolean;\n}\n\n/**\n * Hook to get a single broadcast (convenience wrapper)\n *\n * @example\n * ```tsx\n * const { broadcast, dismiss, trackClick } = useSingleBroadcast({ variant: 'modal' });\n *\n * if (!broadcast) return null;\n *\n * return (\n * <AnnouncementModal\n * broadcast={broadcast}\n * onClose={dismiss}\n * onActionClick={trackClick}\n * />\n * );\n * ```\n */\nexport function useSingleBroadcast(\n options: Omit<UseBroadcastsOptions, 'maxBroadcasts'> = {}\n): UseSingleBroadcastReturn {\n const result = useBroadcasts({ ...options, maxBroadcasts: 1 });\n const broadcast = result.broadcasts[0] || null;\n\n return {\n broadcast,\n isLoading: result.isLoading,\n error: result.error,\n dismiss: () => broadcast && result.dismissBroadcast(broadcast.id),\n trackImpression: () => broadcast && result.trackImpression(broadcast.id),\n trackClick: () => broadcast && result.trackClick(broadcast.id),\n isDismissed: broadcast ? result.isDismissed(broadcast.id) : false,\n };\n}\n\n// ============================================================================\n// ANNOUNCEMENT MODAL HOOK\n// ============================================================================\n\nexport interface UseAnnouncementModalReturn {\n /** The modal broadcast to display */\n broadcast: BroadcastNotification | null;\n /** Whether the modal should be shown */\n isOpen: boolean;\n /** Close the modal */\n close: () => void;\n /** Handle action button click */\n handleAction: () => void;\n}\n\n/**\n * Hook for managing announcement modal display\n * Automatically tracks impressions and handles dismissal\n *\n * @example\n * ```tsx\n * const { broadcast, isOpen, close, handleAction } = useAnnouncementModal();\n *\n * return (\n * <Dialog open={isOpen} onOpenChange={(open) => !open && close()}>\n * <DialogContent>\n * <h2>{broadcast?.title}</h2>\n * <p>{broadcast?.message}</p>\n * {broadcast?.actionUrl && (\n * <Button onClick={handleAction}>{broadcast.actionText || 'Learn More'}</Button>\n * )}\n * </DialogContent>\n * </Dialog>\n * );\n * ```\n */\nexport function useAnnouncementModal(): UseAnnouncementModalReturn {\n const { broadcast, dismiss, trackImpression, trackClick } = useSingleBroadcast({\n variant: 'modal',\n });\n\n const [isOpen, setIsOpen] = useState(false);\n const impressionTrackedRef = useRef(false);\n\n // Show modal when broadcast is available\n useEffect(() => {\n if (broadcast && !impressionTrackedRef.current) {\n setIsOpen(true);\n trackImpression();\n impressionTrackedRef.current = true;\n }\n }, [broadcast, trackImpression]);\n\n const handleClose = useCallback(() => {\n setIsOpen(false);\n dismiss();\n }, [dismiss]);\n\n const handleAction = useCallback(() => {\n trackClick();\n if (broadcast?.actionUrl) {\n window.open(broadcast.actionUrl, '_blank');\n }\n handleClose();\n }, [broadcast, trackClick, handleClose]);\n\n return {\n broadcast,\n isOpen,\n close: handleClose,\n handleAction,\n };\n}\n"],"names":["broadcasts","useState","useRef","useCallback","isInitialized","fetchActiveBroadcasts","useEffect","subscribeToBroadcasts","trackBroadcastDismiss","trackBroadcastImpression","trackBroadcastClick","clearBroadcastsCache"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const react = require("react");
|
|
3
|
-
const analytics = require("./analytics
|
|
3
|
+
const analytics = require("./analytics-lEzOx2vl.cjs");
|
|
4
4
|
function useCampaigns(options) {
|
|
5
5
|
const {
|
|
6
6
|
placement,
|
|
@@ -149,4 +149,4 @@ exports.useCampaign = useCampaign;
|
|
|
149
149
|
exports.useCampaigns = useCampaigns;
|
|
150
150
|
exports.useOneTimeAdModal = useOneTimeAdModal;
|
|
151
151
|
exports.useUpdateAdModal = useUpdateAdModal;
|
|
152
|
-
//# sourceMappingURL=useCampaigns-
|
|
152
|
+
//# sourceMappingURL=useCampaigns-BOZ9dDsG.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCampaigns-Dltisb9N.cjs","sources":["../src/hooks/useCampaigns.ts"],"sourcesContent":["/**\n * useCampaigns Hook\n *\n * React hook for fetching and displaying campaigns in consumer projects.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { fetchActiveCampaigns, clearCampaignsCache } from '../services/campaigns';\nimport {\n trackImpression,\n trackClick,\n trackClose,\n isEligibleForCampaign,\n} from '../services/analytics';\nimport { isInitialized } from '../firebase/config';\nimport type {\n CampaignWithProduct,\n AdPlacement,\n SmallPanelVariant,\n LargePanelVariant,\n} from '../types/campaigns';\n\nexport interface UseCampaignsOptions {\n /** Placement to fetch campaigns for */\n placement: AdPlacement;\n /** Maximum number of campaigns to fetch */\n maxCampaigns?: number;\n /** Whether to auto-fetch on mount (default: true) */\n autoFetch?: boolean;\n /** Default variant for small placements */\n defaultSmallVariant?: SmallPanelVariant;\n /** Default variant for large placements */\n defaultLargeVariant?: LargePanelVariant;\n}\n\nexport interface UseCampaignsResult {\n /** List of eligible campaigns with product data */\n campaigns: CampaignWithProduct[];\n /** Single campaign (first eligible) - convenience accessor */\n campaign: CampaignWithProduct | null;\n /** Whether campaigns are being fetched */\n loading: boolean;\n /** Error message if fetch failed */\n error: string | null;\n /** Refetch campaigns */\n refetch: () => Promise<void>;\n /** Record impression for a campaign */\n recordImpression: (campaign: CampaignWithProduct) => Promise<void>;\n /** Record click for a campaign */\n recordClick: (campaign: CampaignWithProduct) => Promise<void>;\n /** Record close/dismiss for a campaign */\n recordClose: (campaign: CampaignWithProduct) => Promise<void>;\n}\n\n/**\n * Hook to fetch and manage campaigns for a specific placement\n *\n * @example\n * ```tsx\n * const { campaigns, loading, recordImpression, recordClick } = useCampaigns({\n * placement: 'footer_slider',\n * maxCampaigns: 5,\n * });\n *\n * if (loading) return <Spinner />;\n *\n * return (\n * <AdSlider\n * campaigns={campaigns}\n * onImpression={recordImpression}\n * onClick={recordClick}\n * />\n * );\n * ```\n */\nexport function useCampaigns(options: UseCampaignsOptions): UseCampaignsResult {\n const {\n placement,\n maxCampaigns = 5,\n autoFetch = true,\n defaultSmallVariant = 'small_panel_2',\n defaultLargeVariant: _defaultLargeVariant = 'large_slider_1',\n } = options;\n // Note: _defaultLargeVariant reserved for large variant components\n\n const [campaigns, setCampaigns] = useState<CampaignWithProduct[]>([]);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n\n const fetchCampaigns = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const allCampaigns = await fetchActiveCampaigns(placement);\n\n // Filter by frequency capping\n const eligibleCampaigns: CampaignWithProduct[] = [];\n\n for (const campaign of allCampaigns) {\n const eligible = await isEligibleForCampaign(\n campaign.id,\n campaign.frequencyDays\n );\n if (eligible) {\n eligibleCampaigns.push(campaign);\n if (eligibleCampaigns.length >= maxCampaigns) break;\n }\n }\n\n setCampaigns(eligibleCampaigns);\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to fetch campaigns';\n setError(message);\n console.error('[shared-features] Error fetching campaigns:', err);\n } finally {\n setLoading(false);\n }\n }, [placement, maxCampaigns]);\n\n // Auto-fetch on mount\n useEffect(() => {\n if (autoFetch) {\n fetchCampaigns();\n }\n }, [autoFetch, fetchCampaigns]);\n\n // Record impression\n const handleRecordImpression = useCallback(\n async (campaign: CampaignWithProduct) => {\n const variant = campaign.variant.startsWith('small_')\n ? campaign.variant\n : campaign.variant.startsWith('large_')\n ? campaign.variant\n : defaultSmallVariant;\n\n await trackImpression(\n campaign.id,\n campaign.productId,\n placement,\n variant,\n campaign.frequencyDays\n );\n },\n [placement, defaultSmallVariant]\n );\n\n // Record click\n const handleRecordClick = useCallback(\n async (campaign: CampaignWithProduct) => {\n const variant = campaign.variant;\n await trackClick(campaign.id, campaign.productId, placement, variant);\n },\n [placement]\n );\n\n // Record close\n const handleRecordClose = useCallback(\n async (campaign: CampaignWithProduct) => {\n const variant = campaign.variant;\n await trackClose(campaign.id, campaign.productId, placement, variant);\n },\n [placement]\n );\n\n // Refetch with cache clear\n const refetch = useCallback(async () => {\n clearCampaignsCache();\n await fetchCampaigns();\n }, [fetchCampaigns]);\n\n return {\n campaigns,\n campaign: campaigns[0] || null,\n loading,\n error,\n refetch,\n recordImpression: handleRecordImpression,\n recordClick: handleRecordClick,\n recordClose: handleRecordClose,\n };\n}\n\n/**\n * Hook to fetch a single campaign for a placement\n * Convenience wrapper around useCampaigns\n */\nexport function useCampaign(options: Omit<UseCampaignsOptions, 'maxCampaigns'>) {\n return useCampaigns({ ...options, maxCampaigns: 1 });\n}\n\n// Storage keys for modal hooks\nconst STORAGE_KEYS = {\n oneTimeShown: 'shared_features_onetime_ad_shown',\n appVersion: 'shared_features_app_version',\n} as const;\n\n/**\n * Hook to manage one-time ad modal visibility\n * Shows modal on first visit, then remembers user has seen it\n *\n * @example\n * ```tsx\n * const { shouldShow, markAsShown } = useOneTimeAdModal();\n *\n * if (shouldShow) {\n * return <AdModal onClose={markAsShown} />;\n * }\n * ```\n */\nexport function useOneTimeAdModal() {\n const [hasShown, setHasShown] = useState(false);\n const [shouldShow, setShouldShow] = useState(false);\n\n useEffect(() => {\n // Check if modal has been shown before\n const hasSeenModal = localStorage.getItem(STORAGE_KEYS.oneTimeShown);\n if (!hasSeenModal) {\n setShouldShow(true);\n }\n }, []);\n\n const markAsShown = useCallback(() => {\n localStorage.setItem(STORAGE_KEYS.oneTimeShown, 'true');\n setHasShown(true);\n setShouldShow(false);\n }, []);\n\n return {\n /** Whether the modal should be displayed */\n shouldShow: shouldShow && !hasShown,\n /** Mark the modal as shown (call when user dismisses) */\n markAsShown,\n };\n}\n\n/**\n * Hook to manage update ad modal visibility\n * Shows modal when app version changes\n *\n * @param currentVersion - Current app version (defaults to VITE_APP_VERSION env var or '1.0.0')\n *\n * @example\n * ```tsx\n * const { shouldShow, currentVersion, markAsShown } = useUpdateAdModal();\n *\n * if (shouldShow) {\n * return <AdUpdateModal version={currentVersion} onClose={markAsShown} />;\n * }\n * ```\n */\nexport function useUpdateAdModal(currentVersion?: string) {\n const version = currentVersion || import.meta.env.VITE_APP_VERSION || '1.0.0';\n const [shouldShow, setShouldShow] = useState(false);\n const [previousVersion, setPreviousVersion] = useState<string | null>(null);\n\n useEffect(() => {\n const storedVersion = localStorage.getItem(STORAGE_KEYS.appVersion);\n\n if (!storedVersion) {\n // First time user - store version but don't show update modal\n localStorage.setItem(STORAGE_KEYS.appVersion, version);\n } else if (storedVersion !== version) {\n // Version changed - show update modal\n setShouldShow(true);\n setPreviousVersion(storedVersion);\n }\n }, [version]);\n\n const markAsShown = useCallback(() => {\n localStorage.setItem(STORAGE_KEYS.appVersion, version);\n setShouldShow(false);\n }, [version]);\n\n return {\n /** Whether the modal should be displayed */\n shouldShow,\n /** Previous app version (before update) */\n previousVersion,\n /** Current app version */\n currentVersion: version,\n /** Mark the modal as shown (call when user dismisses) */\n markAsShown,\n };\n}\n"],"names":["useState","useCallback","isInitialized","fetchActiveCampaigns","isEligibleForCampaign","useEffect","trackImpression","trackClick","trackClose","clearCampaignsCache"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useCampaigns-BOZ9dDsG.cjs","sources":["../src/hooks/useCampaigns.ts"],"sourcesContent":["/**\n * useCampaigns Hook\n *\n * React hook for fetching and displaying campaigns in consumer projects.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { fetchActiveCampaigns, clearCampaignsCache } from '../services/campaigns';\nimport {\n trackImpression,\n trackClick,\n trackClose,\n isEligibleForCampaign,\n} from '../services/analytics';\nimport { isInitialized } from '../firebase/config';\nimport type {\n CampaignWithProduct,\n AdPlacement,\n SmallPanelVariant,\n LargePanelVariant,\n} from '../types/campaigns';\n\nexport interface UseCampaignsOptions {\n /** Placement to fetch campaigns for */\n placement: AdPlacement;\n /** Maximum number of campaigns to fetch */\n maxCampaigns?: number;\n /** Whether to auto-fetch on mount (default: true) */\n autoFetch?: boolean;\n /** Default variant for small placements */\n defaultSmallVariant?: SmallPanelVariant;\n /** Default variant for large placements */\n defaultLargeVariant?: LargePanelVariant;\n}\n\nexport interface UseCampaignsResult {\n /** List of eligible campaigns with product data */\n campaigns: CampaignWithProduct[];\n /** Single campaign (first eligible) - convenience accessor */\n campaign: CampaignWithProduct | null;\n /** Whether campaigns are being fetched */\n loading: boolean;\n /** Error message if fetch failed */\n error: string | null;\n /** Refetch campaigns */\n refetch: () => Promise<void>;\n /** Record impression for a campaign */\n recordImpression: (campaign: CampaignWithProduct) => Promise<void>;\n /** Record click for a campaign */\n recordClick: (campaign: CampaignWithProduct) => Promise<void>;\n /** Record close/dismiss for a campaign */\n recordClose: (campaign: CampaignWithProduct) => Promise<void>;\n}\n\n/**\n * Hook to fetch and manage campaigns for a specific placement\n *\n * @example\n * ```tsx\n * const { campaigns, loading, recordImpression, recordClick } = useCampaigns({\n * placement: 'footer_slider',\n * maxCampaigns: 5,\n * });\n *\n * if (loading) return <Spinner />;\n *\n * return (\n * <AdSlider\n * campaigns={campaigns}\n * onImpression={recordImpression}\n * onClick={recordClick}\n * />\n * );\n * ```\n */\nexport function useCampaigns(options: UseCampaignsOptions): UseCampaignsResult {\n const {\n placement,\n maxCampaigns = 5,\n autoFetch = true,\n defaultSmallVariant = 'small_panel_2',\n defaultLargeVariant: _defaultLargeVariant = 'large_slider_1',\n } = options;\n // Note: _defaultLargeVariant reserved for large variant components\n\n const [campaigns, setCampaigns] = useState<CampaignWithProduct[]>([]);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n\n const fetchCampaigns = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const allCampaigns = await fetchActiveCampaigns(placement);\n\n // Filter by frequency capping\n const eligibleCampaigns: CampaignWithProduct[] = [];\n\n for (const campaign of allCampaigns) {\n const eligible = await isEligibleForCampaign(\n campaign.id,\n campaign.frequencyDays\n );\n if (eligible) {\n eligibleCampaigns.push(campaign);\n if (eligibleCampaigns.length >= maxCampaigns) break;\n }\n }\n\n setCampaigns(eligibleCampaigns);\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to fetch campaigns';\n setError(message);\n console.error('[shared-features] Error fetching campaigns:', err);\n } finally {\n setLoading(false);\n }\n }, [placement, maxCampaigns]);\n\n // Auto-fetch on mount\n useEffect(() => {\n if (autoFetch) {\n fetchCampaigns();\n }\n }, [autoFetch, fetchCampaigns]);\n\n // Record impression\n const handleRecordImpression = useCallback(\n async (campaign: CampaignWithProduct) => {\n const variant = campaign.variant.startsWith('small_')\n ? campaign.variant\n : campaign.variant.startsWith('large_')\n ? campaign.variant\n : defaultSmallVariant;\n\n await trackImpression(\n campaign.id,\n campaign.productId,\n placement,\n variant,\n campaign.frequencyDays\n );\n },\n [placement, defaultSmallVariant]\n );\n\n // Record click\n const handleRecordClick = useCallback(\n async (campaign: CampaignWithProduct) => {\n const variant = campaign.variant;\n await trackClick(campaign.id, campaign.productId, placement, variant);\n },\n [placement]\n );\n\n // Record close\n const handleRecordClose = useCallback(\n async (campaign: CampaignWithProduct) => {\n const variant = campaign.variant;\n await trackClose(campaign.id, campaign.productId, placement, variant);\n },\n [placement]\n );\n\n // Refetch with cache clear\n const refetch = useCallback(async () => {\n clearCampaignsCache();\n await fetchCampaigns();\n }, [fetchCampaigns]);\n\n return {\n campaigns,\n campaign: campaigns[0] || null,\n loading,\n error,\n refetch,\n recordImpression: handleRecordImpression,\n recordClick: handleRecordClick,\n recordClose: handleRecordClose,\n };\n}\n\n/**\n * Hook to fetch a single campaign for a placement\n * Convenience wrapper around useCampaigns\n */\nexport function useCampaign(options: Omit<UseCampaignsOptions, 'maxCampaigns'>) {\n return useCampaigns({ ...options, maxCampaigns: 1 });\n}\n\n// Storage keys for modal hooks\nconst STORAGE_KEYS = {\n oneTimeShown: 'shared_features_onetime_ad_shown',\n appVersion: 'shared_features_app_version',\n} as const;\n\n/**\n * Hook to manage one-time ad modal visibility\n * Shows modal on first visit, then remembers user has seen it\n *\n * @example\n * ```tsx\n * const { shouldShow, markAsShown } = useOneTimeAdModal();\n *\n * if (shouldShow) {\n * return <AdModal onClose={markAsShown} />;\n * }\n * ```\n */\nexport function useOneTimeAdModal() {\n const [hasShown, setHasShown] = useState(false);\n const [shouldShow, setShouldShow] = useState(false);\n\n useEffect(() => {\n // Check if modal has been shown before\n const hasSeenModal = localStorage.getItem(STORAGE_KEYS.oneTimeShown);\n if (!hasSeenModal) {\n setShouldShow(true);\n }\n }, []);\n\n const markAsShown = useCallback(() => {\n localStorage.setItem(STORAGE_KEYS.oneTimeShown, 'true');\n setHasShown(true);\n setShouldShow(false);\n }, []);\n\n return {\n /** Whether the modal should be displayed */\n shouldShow: shouldShow && !hasShown,\n /** Mark the modal as shown (call when user dismisses) */\n markAsShown,\n };\n}\n\n/**\n * Hook to manage update ad modal visibility\n * Shows modal when app version changes\n *\n * @param currentVersion - Current app version (defaults to VITE_APP_VERSION env var or '1.0.0')\n *\n * @example\n * ```tsx\n * const { shouldShow, currentVersion, markAsShown } = useUpdateAdModal();\n *\n * if (shouldShow) {\n * return <AdUpdateModal version={currentVersion} onClose={markAsShown} />;\n * }\n * ```\n */\nexport function useUpdateAdModal(currentVersion?: string) {\n const version = currentVersion || import.meta.env.VITE_APP_VERSION || '1.0.0';\n const [shouldShow, setShouldShow] = useState(false);\n const [previousVersion, setPreviousVersion] = useState<string | null>(null);\n\n useEffect(() => {\n const storedVersion = localStorage.getItem(STORAGE_KEYS.appVersion);\n\n if (!storedVersion) {\n // First time user - store version but don't show update modal\n localStorage.setItem(STORAGE_KEYS.appVersion, version);\n } else if (storedVersion !== version) {\n // Version changed - show update modal\n setShouldShow(true);\n setPreviousVersion(storedVersion);\n }\n }, [version]);\n\n const markAsShown = useCallback(() => {\n localStorage.setItem(STORAGE_KEYS.appVersion, version);\n setShouldShow(false);\n }, [version]);\n\n return {\n /** Whether the modal should be displayed */\n shouldShow,\n /** Previous app version (before update) */\n previousVersion,\n /** Current app version */\n currentVersion: version,\n /** Mark the modal as shown (call when user dismisses) */\n markAsShown,\n };\n}\n"],"names":["useState","useCallback","isInitialized","fetchActiveCampaigns","isEligibleForCampaign","useEffect","trackImpression","trackClick","trackClose","clearCampaignsCache"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState, useCallback, useEffect } from "react";
|
|
2
|
-
import { e as isInitialized, h as fetchActiveCampaigns, q as isEligibleForCampaign, t as trackImpression, o as trackClick, p as trackClose, m as clearCampaignsCache } from "./analytics-
|
|
2
|
+
import { e as isInitialized, h as fetchActiveCampaigns, q as isEligibleForCampaign, t as trackImpression, o as trackClick, p as trackClose, m as clearCampaignsCache } from "./analytics-40-S_fHC.js";
|
|
3
3
|
function useCampaigns(options) {
|
|
4
4
|
const {
|
|
5
5
|
placement,
|
|
@@ -150,4 +150,4 @@ export {
|
|
|
150
150
|
useUpdateAdModal as c,
|
|
151
151
|
useCampaigns as u
|
|
152
152
|
};
|
|
153
|
-
//# sourceMappingURL=useCampaigns-
|
|
153
|
+
//# sourceMappingURL=useCampaigns-D46b9zuf.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCampaigns-nwfsALsN.js","sources":["../src/hooks/useCampaigns.ts"],"sourcesContent":["/**\n * useCampaigns Hook\n *\n * React hook for fetching and displaying campaigns in consumer projects.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { fetchActiveCampaigns, clearCampaignsCache } from '../services/campaigns';\nimport {\n trackImpression,\n trackClick,\n trackClose,\n isEligibleForCampaign,\n} from '../services/analytics';\nimport { isInitialized } from '../firebase/config';\nimport type {\n CampaignWithProduct,\n AdPlacement,\n SmallPanelVariant,\n LargePanelVariant,\n} from '../types/campaigns';\n\nexport interface UseCampaignsOptions {\n /** Placement to fetch campaigns for */\n placement: AdPlacement;\n /** Maximum number of campaigns to fetch */\n maxCampaigns?: number;\n /** Whether to auto-fetch on mount (default: true) */\n autoFetch?: boolean;\n /** Default variant for small placements */\n defaultSmallVariant?: SmallPanelVariant;\n /** Default variant for large placements */\n defaultLargeVariant?: LargePanelVariant;\n}\n\nexport interface UseCampaignsResult {\n /** List of eligible campaigns with product data */\n campaigns: CampaignWithProduct[];\n /** Single campaign (first eligible) - convenience accessor */\n campaign: CampaignWithProduct | null;\n /** Whether campaigns are being fetched */\n loading: boolean;\n /** Error message if fetch failed */\n error: string | null;\n /** Refetch campaigns */\n refetch: () => Promise<void>;\n /** Record impression for a campaign */\n recordImpression: (campaign: CampaignWithProduct) => Promise<void>;\n /** Record click for a campaign */\n recordClick: (campaign: CampaignWithProduct) => Promise<void>;\n /** Record close/dismiss for a campaign */\n recordClose: (campaign: CampaignWithProduct) => Promise<void>;\n}\n\n/**\n * Hook to fetch and manage campaigns for a specific placement\n *\n * @example\n * ```tsx\n * const { campaigns, loading, recordImpression, recordClick } = useCampaigns({\n * placement: 'footer_slider',\n * maxCampaigns: 5,\n * });\n *\n * if (loading) return <Spinner />;\n *\n * return (\n * <AdSlider\n * campaigns={campaigns}\n * onImpression={recordImpression}\n * onClick={recordClick}\n * />\n * );\n * ```\n */\nexport function useCampaigns(options: UseCampaignsOptions): UseCampaignsResult {\n const {\n placement,\n maxCampaigns = 5,\n autoFetch = true,\n defaultSmallVariant = 'small_panel_2',\n defaultLargeVariant: _defaultLargeVariant = 'large_slider_1',\n } = options;\n // Note: _defaultLargeVariant reserved for large variant components\n\n const [campaigns, setCampaigns] = useState<CampaignWithProduct[]>([]);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n\n const fetchCampaigns = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const allCampaigns = await fetchActiveCampaigns(placement);\n\n // Filter by frequency capping\n const eligibleCampaigns: CampaignWithProduct[] = [];\n\n for (const campaign of allCampaigns) {\n const eligible = await isEligibleForCampaign(\n campaign.id,\n campaign.frequencyDays\n );\n if (eligible) {\n eligibleCampaigns.push(campaign);\n if (eligibleCampaigns.length >= maxCampaigns) break;\n }\n }\n\n setCampaigns(eligibleCampaigns);\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to fetch campaigns';\n setError(message);\n console.error('[shared-features] Error fetching campaigns:', err);\n } finally {\n setLoading(false);\n }\n }, [placement, maxCampaigns]);\n\n // Auto-fetch on mount\n useEffect(() => {\n if (autoFetch) {\n fetchCampaigns();\n }\n }, [autoFetch, fetchCampaigns]);\n\n // Record impression\n const handleRecordImpression = useCallback(\n async (campaign: CampaignWithProduct) => {\n const variant = campaign.variant.startsWith('small_')\n ? campaign.variant\n : campaign.variant.startsWith('large_')\n ? campaign.variant\n : defaultSmallVariant;\n\n await trackImpression(\n campaign.id,\n campaign.productId,\n placement,\n variant,\n campaign.frequencyDays\n );\n },\n [placement, defaultSmallVariant]\n );\n\n // Record click\n const handleRecordClick = useCallback(\n async (campaign: CampaignWithProduct) => {\n const variant = campaign.variant;\n await trackClick(campaign.id, campaign.productId, placement, variant);\n },\n [placement]\n );\n\n // Record close\n const handleRecordClose = useCallback(\n async (campaign: CampaignWithProduct) => {\n const variant = campaign.variant;\n await trackClose(campaign.id, campaign.productId, placement, variant);\n },\n [placement]\n );\n\n // Refetch with cache clear\n const refetch = useCallback(async () => {\n clearCampaignsCache();\n await fetchCampaigns();\n }, [fetchCampaigns]);\n\n return {\n campaigns,\n campaign: campaigns[0] || null,\n loading,\n error,\n refetch,\n recordImpression: handleRecordImpression,\n recordClick: handleRecordClick,\n recordClose: handleRecordClose,\n };\n}\n\n/**\n * Hook to fetch a single campaign for a placement\n * Convenience wrapper around useCampaigns\n */\nexport function useCampaign(options: Omit<UseCampaignsOptions, 'maxCampaigns'>) {\n return useCampaigns({ ...options, maxCampaigns: 1 });\n}\n\n// Storage keys for modal hooks\nconst STORAGE_KEYS = {\n oneTimeShown: 'shared_features_onetime_ad_shown',\n appVersion: 'shared_features_app_version',\n} as const;\n\n/**\n * Hook to manage one-time ad modal visibility\n * Shows modal on first visit, then remembers user has seen it\n *\n * @example\n * ```tsx\n * const { shouldShow, markAsShown } = useOneTimeAdModal();\n *\n * if (shouldShow) {\n * return <AdModal onClose={markAsShown} />;\n * }\n * ```\n */\nexport function useOneTimeAdModal() {\n const [hasShown, setHasShown] = useState(false);\n const [shouldShow, setShouldShow] = useState(false);\n\n useEffect(() => {\n // Check if modal has been shown before\n const hasSeenModal = localStorage.getItem(STORAGE_KEYS.oneTimeShown);\n if (!hasSeenModal) {\n setShouldShow(true);\n }\n }, []);\n\n const markAsShown = useCallback(() => {\n localStorage.setItem(STORAGE_KEYS.oneTimeShown, 'true');\n setHasShown(true);\n setShouldShow(false);\n }, []);\n\n return {\n /** Whether the modal should be displayed */\n shouldShow: shouldShow && !hasShown,\n /** Mark the modal as shown (call when user dismisses) */\n markAsShown,\n };\n}\n\n/**\n * Hook to manage update ad modal visibility\n * Shows modal when app version changes\n *\n * @param currentVersion - Current app version (defaults to VITE_APP_VERSION env var or '1.0.0')\n *\n * @example\n * ```tsx\n * const { shouldShow, currentVersion, markAsShown } = useUpdateAdModal();\n *\n * if (shouldShow) {\n * return <AdUpdateModal version={currentVersion} onClose={markAsShown} />;\n * }\n * ```\n */\nexport function useUpdateAdModal(currentVersion?: string) {\n const version = currentVersion || import.meta.env.VITE_APP_VERSION || '1.0.0';\n const [shouldShow, setShouldShow] = useState(false);\n const [previousVersion, setPreviousVersion] = useState<string | null>(null);\n\n useEffect(() => {\n const storedVersion = localStorage.getItem(STORAGE_KEYS.appVersion);\n\n if (!storedVersion) {\n // First time user - store version but don't show update modal\n localStorage.setItem(STORAGE_KEYS.appVersion, version);\n } else if (storedVersion !== version) {\n // Version changed - show update modal\n setShouldShow(true);\n setPreviousVersion(storedVersion);\n }\n }, [version]);\n\n const markAsShown = useCallback(() => {\n localStorage.setItem(STORAGE_KEYS.appVersion, version);\n setShouldShow(false);\n }, [version]);\n\n return {\n /** Whether the modal should be displayed */\n shouldShow,\n /** Previous app version (before update) */\n previousVersion,\n /** Current app version */\n currentVersion: version,\n /** Mark the modal as shown (call when user dismisses) */\n markAsShown,\n };\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useCampaigns-D46b9zuf.js","sources":["../src/hooks/useCampaigns.ts"],"sourcesContent":["/**\n * useCampaigns Hook\n *\n * React hook for fetching and displaying campaigns in consumer projects.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { fetchActiveCampaigns, clearCampaignsCache } from '../services/campaigns';\nimport {\n trackImpression,\n trackClick,\n trackClose,\n isEligibleForCampaign,\n} from '../services/analytics';\nimport { isInitialized } from '../firebase/config';\nimport type {\n CampaignWithProduct,\n AdPlacement,\n SmallPanelVariant,\n LargePanelVariant,\n} from '../types/campaigns';\n\nexport interface UseCampaignsOptions {\n /** Placement to fetch campaigns for */\n placement: AdPlacement;\n /** Maximum number of campaigns to fetch */\n maxCampaigns?: number;\n /** Whether to auto-fetch on mount (default: true) */\n autoFetch?: boolean;\n /** Default variant for small placements */\n defaultSmallVariant?: SmallPanelVariant;\n /** Default variant for large placements */\n defaultLargeVariant?: LargePanelVariant;\n}\n\nexport interface UseCampaignsResult {\n /** List of eligible campaigns with product data */\n campaigns: CampaignWithProduct[];\n /** Single campaign (first eligible) - convenience accessor */\n campaign: CampaignWithProduct | null;\n /** Whether campaigns are being fetched */\n loading: boolean;\n /** Error message if fetch failed */\n error: string | null;\n /** Refetch campaigns */\n refetch: () => Promise<void>;\n /** Record impression for a campaign */\n recordImpression: (campaign: CampaignWithProduct) => Promise<void>;\n /** Record click for a campaign */\n recordClick: (campaign: CampaignWithProduct) => Promise<void>;\n /** Record close/dismiss for a campaign */\n recordClose: (campaign: CampaignWithProduct) => Promise<void>;\n}\n\n/**\n * Hook to fetch and manage campaigns for a specific placement\n *\n * @example\n * ```tsx\n * const { campaigns, loading, recordImpression, recordClick } = useCampaigns({\n * placement: 'footer_slider',\n * maxCampaigns: 5,\n * });\n *\n * if (loading) return <Spinner />;\n *\n * return (\n * <AdSlider\n * campaigns={campaigns}\n * onImpression={recordImpression}\n * onClick={recordClick}\n * />\n * );\n * ```\n */\nexport function useCampaigns(options: UseCampaignsOptions): UseCampaignsResult {\n const {\n placement,\n maxCampaigns = 5,\n autoFetch = true,\n defaultSmallVariant = 'small_panel_2',\n defaultLargeVariant: _defaultLargeVariant = 'large_slider_1',\n } = options;\n // Note: _defaultLargeVariant reserved for large variant components\n\n const [campaigns, setCampaigns] = useState<CampaignWithProduct[]>([]);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n\n const fetchCampaigns = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const allCampaigns = await fetchActiveCampaigns(placement);\n\n // Filter by frequency capping\n const eligibleCampaigns: CampaignWithProduct[] = [];\n\n for (const campaign of allCampaigns) {\n const eligible = await isEligibleForCampaign(\n campaign.id,\n campaign.frequencyDays\n );\n if (eligible) {\n eligibleCampaigns.push(campaign);\n if (eligibleCampaigns.length >= maxCampaigns) break;\n }\n }\n\n setCampaigns(eligibleCampaigns);\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to fetch campaigns';\n setError(message);\n console.error('[shared-features] Error fetching campaigns:', err);\n } finally {\n setLoading(false);\n }\n }, [placement, maxCampaigns]);\n\n // Auto-fetch on mount\n useEffect(() => {\n if (autoFetch) {\n fetchCampaigns();\n }\n }, [autoFetch, fetchCampaigns]);\n\n // Record impression\n const handleRecordImpression = useCallback(\n async (campaign: CampaignWithProduct) => {\n const variant = campaign.variant.startsWith('small_')\n ? campaign.variant\n : campaign.variant.startsWith('large_')\n ? campaign.variant\n : defaultSmallVariant;\n\n await trackImpression(\n campaign.id,\n campaign.productId,\n placement,\n variant,\n campaign.frequencyDays\n );\n },\n [placement, defaultSmallVariant]\n );\n\n // Record click\n const handleRecordClick = useCallback(\n async (campaign: CampaignWithProduct) => {\n const variant = campaign.variant;\n await trackClick(campaign.id, campaign.productId, placement, variant);\n },\n [placement]\n );\n\n // Record close\n const handleRecordClose = useCallback(\n async (campaign: CampaignWithProduct) => {\n const variant = campaign.variant;\n await trackClose(campaign.id, campaign.productId, placement, variant);\n },\n [placement]\n );\n\n // Refetch with cache clear\n const refetch = useCallback(async () => {\n clearCampaignsCache();\n await fetchCampaigns();\n }, [fetchCampaigns]);\n\n return {\n campaigns,\n campaign: campaigns[0] || null,\n loading,\n error,\n refetch,\n recordImpression: handleRecordImpression,\n recordClick: handleRecordClick,\n recordClose: handleRecordClose,\n };\n}\n\n/**\n * Hook to fetch a single campaign for a placement\n * Convenience wrapper around useCampaigns\n */\nexport function useCampaign(options: Omit<UseCampaignsOptions, 'maxCampaigns'>) {\n return useCampaigns({ ...options, maxCampaigns: 1 });\n}\n\n// Storage keys for modal hooks\nconst STORAGE_KEYS = {\n oneTimeShown: 'shared_features_onetime_ad_shown',\n appVersion: 'shared_features_app_version',\n} as const;\n\n/**\n * Hook to manage one-time ad modal visibility\n * Shows modal on first visit, then remembers user has seen it\n *\n * @example\n * ```tsx\n * const { shouldShow, markAsShown } = useOneTimeAdModal();\n *\n * if (shouldShow) {\n * return <AdModal onClose={markAsShown} />;\n * }\n * ```\n */\nexport function useOneTimeAdModal() {\n const [hasShown, setHasShown] = useState(false);\n const [shouldShow, setShouldShow] = useState(false);\n\n useEffect(() => {\n // Check if modal has been shown before\n const hasSeenModal = localStorage.getItem(STORAGE_KEYS.oneTimeShown);\n if (!hasSeenModal) {\n setShouldShow(true);\n }\n }, []);\n\n const markAsShown = useCallback(() => {\n localStorage.setItem(STORAGE_KEYS.oneTimeShown, 'true');\n setHasShown(true);\n setShouldShow(false);\n }, []);\n\n return {\n /** Whether the modal should be displayed */\n shouldShow: shouldShow && !hasShown,\n /** Mark the modal as shown (call when user dismisses) */\n markAsShown,\n };\n}\n\n/**\n * Hook to manage update ad modal visibility\n * Shows modal when app version changes\n *\n * @param currentVersion - Current app version (defaults to VITE_APP_VERSION env var or '1.0.0')\n *\n * @example\n * ```tsx\n * const { shouldShow, currentVersion, markAsShown } = useUpdateAdModal();\n *\n * if (shouldShow) {\n * return <AdUpdateModal version={currentVersion} onClose={markAsShown} />;\n * }\n * ```\n */\nexport function useUpdateAdModal(currentVersion?: string) {\n const version = currentVersion || import.meta.env.VITE_APP_VERSION || '1.0.0';\n const [shouldShow, setShouldShow] = useState(false);\n const [previousVersion, setPreviousVersion] = useState<string | null>(null);\n\n useEffect(() => {\n const storedVersion = localStorage.getItem(STORAGE_KEYS.appVersion);\n\n if (!storedVersion) {\n // First time user - store version but don't show update modal\n localStorage.setItem(STORAGE_KEYS.appVersion, version);\n } else if (storedVersion !== version) {\n // Version changed - show update modal\n setShouldShow(true);\n setPreviousVersion(storedVersion);\n }\n }, [version]);\n\n const markAsShown = useCallback(() => {\n localStorage.setItem(STORAGE_KEYS.appVersion, version);\n setShouldShow(false);\n }, [version]);\n\n return {\n /** Whether the modal should be displayed */\n shouldShow,\n /** Previous app version (before update) */\n previousVersion,\n /** Current app version */\n currentVersion: version,\n /** Mark the modal as shown (call when user dismisses) */\n markAsShown,\n };\n}\n"],"names":[],"mappings":"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"}
|