shared-features 0.0.1
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 +200 -0
- package/dist/AdPanel-D0BiV6Xb.cjs +88 -0
- package/dist/AdPanel-D0BiV6Xb.cjs.map +1 -0
- package/dist/AdPanel-RGRBf4ub.js +89 -0
- package/dist/AdPanel-RGRBf4ub.js.map +1 -0
- package/dist/analytics-6shJHRZG.cjs +463 -0
- package/dist/analytics-6shJHRZG.cjs.map +1 -0
- package/dist/analytics-Bbmodnm_.js +442 -0
- package/dist/analytics-Bbmodnm_.js.map +1 -0
- package/dist/components/ads/AdPanel.d.ts +12 -0
- package/dist/components/ads/AdPanel.d.ts.map +1 -0
- package/dist/components/ads/index.d.ts +9 -0
- package/dist/components/ads/index.d.ts.map +1 -0
- package/dist/components/index.cjs +5 -0
- package/dist/components/index.cjs.map +1 -0
- package/dist/components/index.d.ts +9 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +5 -0
- package/dist/components/index.js.map +1 -0
- package/dist/firebase/config.d.ts +77 -0
- package/dist/firebase/config.d.ts.map +1 -0
- package/dist/firebase/init.d.ts +55 -0
- package/dist/firebase/init.d.ts.map +1 -0
- package/dist/hooks/index.cjs +6 -0
- package/dist/hooks/index.cjs.map +1 -0
- package/dist/hooks/index.d.ts +10 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +6 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/useCampaigns.d.ts +59 -0
- package/dist/hooks/useCampaigns.d.ts.map +1 -0
- package/dist/index.cjs +37 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/services/analytics.d.ts +32 -0
- package/dist/services/analytics.d.ts.map +1 -0
- package/dist/services/campaigns.d.ts +31 -0
- package/dist/services/campaigns.d.ts.map +1 -0
- package/dist/services/index.cjs +18 -0
- package/dist/services/index.cjs.map +1 -0
- package/dist/services/index.d.ts +10 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +18 -0
- package/dist/services/index.js.map +1 -0
- package/dist/types/campaigns.d.ts +378 -0
- package/dist/types/campaigns.d.ts.map +1 -0
- package/dist/types/index.cjs +49 -0
- package/dist/types/index.cjs.map +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +49 -0
- package/dist/types/index.js.map +1 -0
- package/dist/useCampaigns-3NxODLLs.js +98 -0
- package/dist/useCampaigns-3NxODLLs.js.map +1 -0
- package/dist/useCampaigns-BNOHpETm.cjs +97 -0
- package/dist/useCampaigns-BNOHpETm.cjs.map +1 -0
- package/package.json +90 -0
|
@@ -0,0 +1,98 @@
|
|
|
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-Bbmodnm_.js";
|
|
3
|
+
function useCampaigns(options) {
|
|
4
|
+
const {
|
|
5
|
+
placement,
|
|
6
|
+
maxCampaigns = 5,
|
|
7
|
+
autoFetch = true,
|
|
8
|
+
defaultSmallVariant = "small_panel_2",
|
|
9
|
+
defaultLargeVariant: _defaultLargeVariant = "large_slider_1"
|
|
10
|
+
} = options;
|
|
11
|
+
const [campaigns, setCampaigns] = useState([]);
|
|
12
|
+
const [loading, setLoading] = useState(autoFetch);
|
|
13
|
+
const [error, setError] = useState(null);
|
|
14
|
+
const fetchCampaigns = useCallback(async () => {
|
|
15
|
+
if (!isInitialized()) {
|
|
16
|
+
setError("shared-features not initialized");
|
|
17
|
+
setLoading(false);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
setLoading(true);
|
|
21
|
+
setError(null);
|
|
22
|
+
try {
|
|
23
|
+
const allCampaigns = await fetchActiveCampaigns(placement);
|
|
24
|
+
const eligibleCampaigns = [];
|
|
25
|
+
for (const campaign of allCampaigns) {
|
|
26
|
+
const eligible = await isEligibleForCampaign(
|
|
27
|
+
campaign.id,
|
|
28
|
+
campaign.frequencyDays
|
|
29
|
+
);
|
|
30
|
+
if (eligible) {
|
|
31
|
+
eligibleCampaigns.push(campaign);
|
|
32
|
+
if (eligibleCampaigns.length >= maxCampaigns) break;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
setCampaigns(eligibleCampaigns);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
const message = err instanceof Error ? err.message : "Failed to fetch campaigns";
|
|
38
|
+
setError(message);
|
|
39
|
+
console.error("[shared-features] Error fetching campaigns:", err);
|
|
40
|
+
} finally {
|
|
41
|
+
setLoading(false);
|
|
42
|
+
}
|
|
43
|
+
}, [placement, maxCampaigns]);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (autoFetch) {
|
|
46
|
+
fetchCampaigns();
|
|
47
|
+
}
|
|
48
|
+
}, [autoFetch, fetchCampaigns]);
|
|
49
|
+
const handleRecordImpression = useCallback(
|
|
50
|
+
async (campaign) => {
|
|
51
|
+
const variant = campaign.variant.startsWith("small_") ? campaign.variant : campaign.variant.startsWith("large_") ? campaign.variant : defaultSmallVariant;
|
|
52
|
+
await trackImpression(
|
|
53
|
+
campaign.id,
|
|
54
|
+
campaign.productId,
|
|
55
|
+
placement,
|
|
56
|
+
variant,
|
|
57
|
+
campaign.frequencyDays
|
|
58
|
+
);
|
|
59
|
+
},
|
|
60
|
+
[placement, defaultSmallVariant]
|
|
61
|
+
);
|
|
62
|
+
const handleRecordClick = useCallback(
|
|
63
|
+
async (campaign) => {
|
|
64
|
+
const variant = campaign.variant;
|
|
65
|
+
await trackClick(campaign.id, campaign.productId, placement, variant);
|
|
66
|
+
},
|
|
67
|
+
[placement]
|
|
68
|
+
);
|
|
69
|
+
const handleRecordClose = useCallback(
|
|
70
|
+
async (campaign) => {
|
|
71
|
+
const variant = campaign.variant;
|
|
72
|
+
await trackClose(campaign.id, campaign.productId, placement, variant);
|
|
73
|
+
},
|
|
74
|
+
[placement]
|
|
75
|
+
);
|
|
76
|
+
const refetch = useCallback(async () => {
|
|
77
|
+
clearCampaignsCache();
|
|
78
|
+
await fetchCampaigns();
|
|
79
|
+
}, [fetchCampaigns]);
|
|
80
|
+
return {
|
|
81
|
+
campaigns,
|
|
82
|
+
campaign: campaigns[0] || null,
|
|
83
|
+
loading,
|
|
84
|
+
error,
|
|
85
|
+
refetch,
|
|
86
|
+
recordImpression: handleRecordImpression,
|
|
87
|
+
recordClick: handleRecordClick,
|
|
88
|
+
recordClose: handleRecordClose
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function useCampaign(options) {
|
|
92
|
+
return useCampaigns({ ...options, maxCampaigns: 1 });
|
|
93
|
+
}
|
|
94
|
+
export {
|
|
95
|
+
useCampaign as a,
|
|
96
|
+
useCampaigns as u
|
|
97
|
+
};
|
|
98
|
+
//# sourceMappingURL=useCampaigns-3NxODLLs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCampaigns-3NxODLLs.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"],"names":[],"mappings":";;AA6EO,SAAS,aAAa,SAAkD;AAC7E,QAAM;AAAA,IACJ;AAAA,IACA,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,sBAAsB;AAAA,IACtB,qBAAqB,uBAAuB;AAAA,EAAA,IAC1C;AAGJ,QAAM,CAAC,WAAW,YAAY,IAAI,SAAgC,CAAA,CAAE;AACpE,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,SAAS;AAChD,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,IAAI;AAEtD,QAAM,iBAAiB,YAAY,YAAY;AAC7C,QAAI,CAAC,iBAAiB;AACpB,eAAS,iCAAiC;AAC1C,iBAAW,KAAK;AAChB;AAAA,IACF;AAEA,eAAW,IAAI;AACf,aAAS,IAAI;AAEb,QAAI;AACF,YAAM,eAAe,MAAM,qBAAqB,SAAS;AAGzD,YAAM,oBAA2C,CAAA;AAEjD,iBAAW,YAAY,cAAc;AACnC,cAAM,WAAW,MAAM;AAAA,UACrB,SAAS;AAAA,UACT,SAAS;AAAA,QAAA;AAEX,YAAI,UAAU;AACZ,4BAAkB,KAAK,QAAQ;AAC/B,cAAI,kBAAkB,UAAU,aAAc;AAAA,QAChD;AAAA,MACF;AAEA,mBAAa,iBAAiB;AAAA,IAChC,SAAS,KAAK;AACZ,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU;AACrD,eAAS,OAAO;AAChB,cAAQ,MAAM,+CAA+C,GAAG;AAAA,IAClE,UAAA;AACE,iBAAW,KAAK;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,WAAW,YAAY,CAAC;AAG5B,YAAU,MAAM;AACd,QAAI,WAAW;AACb,qBAAA;AAAA,IACF;AAAA,EACF,GAAG,CAAC,WAAW,cAAc,CAAC;AAG9B,QAAM,yBAAyB;AAAA,IAC7B,OAAO,aAAkC;AACvC,YAAM,UAAU,SAAS,QAAQ,WAAW,QAAQ,IAChD,SAAS,UACT,SAAS,QAAQ,WAAW,QAAQ,IAClC,SAAS,UACT;AAEN,YAAM;AAAA,QACJ,SAAS;AAAA,QACT,SAAS;AAAA,QACT;AAAA,QACA;AAAA,QACA,SAAS;AAAA,MAAA;AAAA,IAEb;AAAA,IACA,CAAC,WAAW,mBAAmB;AAAA,EAAA;AAIjC,QAAM,oBAAoB;AAAA,IACxB,OAAO,aAAkC;AACvC,YAAM,UAAU,SAAS;AACzB,YAAM,WAAW,SAAS,IAAI,SAAS,WAAW,WAAW,OAAO;AAAA,IACtE;AAAA,IACA,CAAC,SAAS;AAAA,EAAA;AAIZ,QAAM,oBAAoB;AAAA,IACxB,OAAO,aAAkC;AACvC,YAAM,UAAU,SAAS;AACzB,YAAM,WAAW,SAAS,IAAI,SAAS,WAAW,WAAW,OAAO;AAAA,IACtE;AAAA,IACA,CAAC,SAAS;AAAA,EAAA;AAIZ,QAAM,UAAU,YAAY,YAAY;AACtC,wBAAA;AACA,UAAM,eAAA;AAAA,EACR,GAAG,CAAC,cAAc,CAAC;AAEnB,SAAO;AAAA,IACL;AAAA,IACA,UAAU,UAAU,CAAC,KAAK;AAAA,IAC1B;AAAA,IACA;AAAA,IACA;AAAA,IACA,kBAAkB;AAAA,IAClB,aAAa;AAAA,IACb,aAAa;AAAA,EAAA;AAEjB;AAMO,SAAS,YAAY,SAAoD;AAC9E,SAAO,aAAa,EAAE,GAAG,SAAS,cAAc,GAAG;AACrD;"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const react = require("react");
|
|
3
|
+
const analytics = require("./analytics-6shJHRZG.cjs");
|
|
4
|
+
function useCampaigns(options) {
|
|
5
|
+
const {
|
|
6
|
+
placement,
|
|
7
|
+
maxCampaigns = 5,
|
|
8
|
+
autoFetch = true,
|
|
9
|
+
defaultSmallVariant = "small_panel_2",
|
|
10
|
+
defaultLargeVariant: _defaultLargeVariant = "large_slider_1"
|
|
11
|
+
} = options;
|
|
12
|
+
const [campaigns, setCampaigns] = react.useState([]);
|
|
13
|
+
const [loading, setLoading] = react.useState(autoFetch);
|
|
14
|
+
const [error, setError] = react.useState(null);
|
|
15
|
+
const fetchCampaigns = react.useCallback(async () => {
|
|
16
|
+
if (!analytics.isInitialized()) {
|
|
17
|
+
setError("shared-features not initialized");
|
|
18
|
+
setLoading(false);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
setLoading(true);
|
|
22
|
+
setError(null);
|
|
23
|
+
try {
|
|
24
|
+
const allCampaigns = await analytics.fetchActiveCampaigns(placement);
|
|
25
|
+
const eligibleCampaigns = [];
|
|
26
|
+
for (const campaign of allCampaigns) {
|
|
27
|
+
const eligible = await analytics.isEligibleForCampaign(
|
|
28
|
+
campaign.id,
|
|
29
|
+
campaign.frequencyDays
|
|
30
|
+
);
|
|
31
|
+
if (eligible) {
|
|
32
|
+
eligibleCampaigns.push(campaign);
|
|
33
|
+
if (eligibleCampaigns.length >= maxCampaigns) break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
setCampaigns(eligibleCampaigns);
|
|
37
|
+
} catch (err) {
|
|
38
|
+
const message = err instanceof Error ? err.message : "Failed to fetch campaigns";
|
|
39
|
+
setError(message);
|
|
40
|
+
console.error("[shared-features] Error fetching campaigns:", err);
|
|
41
|
+
} finally {
|
|
42
|
+
setLoading(false);
|
|
43
|
+
}
|
|
44
|
+
}, [placement, maxCampaigns]);
|
|
45
|
+
react.useEffect(() => {
|
|
46
|
+
if (autoFetch) {
|
|
47
|
+
fetchCampaigns();
|
|
48
|
+
}
|
|
49
|
+
}, [autoFetch, fetchCampaigns]);
|
|
50
|
+
const handleRecordImpression = react.useCallback(
|
|
51
|
+
async (campaign) => {
|
|
52
|
+
const variant = campaign.variant.startsWith("small_") ? campaign.variant : campaign.variant.startsWith("large_") ? campaign.variant : defaultSmallVariant;
|
|
53
|
+
await analytics.trackImpression(
|
|
54
|
+
campaign.id,
|
|
55
|
+
campaign.productId,
|
|
56
|
+
placement,
|
|
57
|
+
variant,
|
|
58
|
+
campaign.frequencyDays
|
|
59
|
+
);
|
|
60
|
+
},
|
|
61
|
+
[placement, defaultSmallVariant]
|
|
62
|
+
);
|
|
63
|
+
const handleRecordClick = react.useCallback(
|
|
64
|
+
async (campaign) => {
|
|
65
|
+
const variant = campaign.variant;
|
|
66
|
+
await analytics.trackClick(campaign.id, campaign.productId, placement, variant);
|
|
67
|
+
},
|
|
68
|
+
[placement]
|
|
69
|
+
);
|
|
70
|
+
const handleRecordClose = react.useCallback(
|
|
71
|
+
async (campaign) => {
|
|
72
|
+
const variant = campaign.variant;
|
|
73
|
+
await analytics.trackClose(campaign.id, campaign.productId, placement, variant);
|
|
74
|
+
},
|
|
75
|
+
[placement]
|
|
76
|
+
);
|
|
77
|
+
const refetch = react.useCallback(async () => {
|
|
78
|
+
analytics.clearCampaignsCache();
|
|
79
|
+
await fetchCampaigns();
|
|
80
|
+
}, [fetchCampaigns]);
|
|
81
|
+
return {
|
|
82
|
+
campaigns,
|
|
83
|
+
campaign: campaigns[0] || null,
|
|
84
|
+
loading,
|
|
85
|
+
error,
|
|
86
|
+
refetch,
|
|
87
|
+
recordImpression: handleRecordImpression,
|
|
88
|
+
recordClick: handleRecordClick,
|
|
89
|
+
recordClose: handleRecordClose
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function useCampaign(options) {
|
|
93
|
+
return useCampaigns({ ...options, maxCampaigns: 1 });
|
|
94
|
+
}
|
|
95
|
+
exports.useCampaign = useCampaign;
|
|
96
|
+
exports.useCampaigns = useCampaigns;
|
|
97
|
+
//# sourceMappingURL=useCampaigns-BNOHpETm.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCampaigns-BNOHpETm.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"],"names":["useState","useCallback","isInitialized","fetchActiveCampaigns","isEligibleForCampaign","useEffect","trackImpression","trackClick","trackClose","clearCampaignsCache"],"mappings":";;;AA6EO,SAAS,aAAa,SAAkD;AAC7E,QAAM;AAAA,IACJ;AAAA,IACA,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,sBAAsB;AAAA,IACtB,qBAAqB,uBAAuB;AAAA,EAAA,IAC1C;AAGJ,QAAM,CAAC,WAAW,YAAY,IAAIA,MAAAA,SAAgC,CAAA,CAAE;AACpE,QAAM,CAAC,SAAS,UAAU,IAAIA,MAAAA,SAAS,SAAS;AAChD,QAAM,CAAC,OAAO,QAAQ,IAAIA,MAAAA,SAAwB,IAAI;AAEtD,QAAM,iBAAiBC,MAAAA,YAAY,YAAY;AAC7C,QAAI,CAACC,UAAAA,iBAAiB;AACpB,eAAS,iCAAiC;AAC1C,iBAAW,KAAK;AAChB;AAAA,IACF;AAEA,eAAW,IAAI;AACf,aAAS,IAAI;AAEb,QAAI;AACF,YAAM,eAAe,MAAMC,UAAAA,qBAAqB,SAAS;AAGzD,YAAM,oBAA2C,CAAA;AAEjD,iBAAW,YAAY,cAAc;AACnC,cAAM,WAAW,MAAMC,UAAAA;AAAAA,UACrB,SAAS;AAAA,UACT,SAAS;AAAA,QAAA;AAEX,YAAI,UAAU;AACZ,4BAAkB,KAAK,QAAQ;AAC/B,cAAI,kBAAkB,UAAU,aAAc;AAAA,QAChD;AAAA,MACF;AAEA,mBAAa,iBAAiB;AAAA,IAChC,SAAS,KAAK;AACZ,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU;AACrD,eAAS,OAAO;AAChB,cAAQ,MAAM,+CAA+C,GAAG;AAAA,IAClE,UAAA;AACE,iBAAW,KAAK;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,WAAW,YAAY,CAAC;AAG5BC,QAAAA,UAAU,MAAM;AACd,QAAI,WAAW;AACb,qBAAA;AAAA,IACF;AAAA,EACF,GAAG,CAAC,WAAW,cAAc,CAAC;AAG9B,QAAM,yBAAyBJ,MAAAA;AAAAA,IAC7B,OAAO,aAAkC;AACvC,YAAM,UAAU,SAAS,QAAQ,WAAW,QAAQ,IAChD,SAAS,UACT,SAAS,QAAQ,WAAW,QAAQ,IAClC,SAAS,UACT;AAEN,YAAMK,UAAAA;AAAAA,QACJ,SAAS;AAAA,QACT,SAAS;AAAA,QACT;AAAA,QACA;AAAA,QACA,SAAS;AAAA,MAAA;AAAA,IAEb;AAAA,IACA,CAAC,WAAW,mBAAmB;AAAA,EAAA;AAIjC,QAAM,oBAAoBL,MAAAA;AAAAA,IACxB,OAAO,aAAkC;AACvC,YAAM,UAAU,SAAS;AACzB,YAAMM,UAAAA,WAAW,SAAS,IAAI,SAAS,WAAW,WAAW,OAAO;AAAA,IACtE;AAAA,IACA,CAAC,SAAS;AAAA,EAAA;AAIZ,QAAM,oBAAoBN,MAAAA;AAAAA,IACxB,OAAO,aAAkC;AACvC,YAAM,UAAU,SAAS;AACzB,YAAMO,UAAAA,WAAW,SAAS,IAAI,SAAS,WAAW,WAAW,OAAO;AAAA,IACtE;AAAA,IACA,CAAC,SAAS;AAAA,EAAA;AAIZ,QAAM,UAAUP,MAAAA,YAAY,YAAY;AACtCQ,kCAAA;AACA,UAAM,eAAA;AAAA,EACR,GAAG,CAAC,cAAc,CAAC;AAEnB,SAAO;AAAA,IACL;AAAA,IACA,UAAU,UAAU,CAAC,KAAK;AAAA,IAC1B;AAAA,IACA;AAAA,IACA;AAAA,IACA,kBAAkB;AAAA,IAClB,aAAa;AAAA,IACb,aAAa;AAAA,EAAA;AAEjB;AAMO,SAAS,YAAY,SAAoD;AAC9E,SAAO,aAAa,EAAE,GAAG,SAAS,cAAc,GAAG;AACrD;;;"}
|
package/package.json
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shared-features",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Shared features for Zaions projects - centralized ads, contacts, feature requests, and more",
|
|
5
|
+
"author": "Ahsan Mahmood <aoneahsan@gmail.com>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/aoneahsan/shared-features"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"zaions",
|
|
13
|
+
"shared-features",
|
|
14
|
+
"ads",
|
|
15
|
+
"campaigns",
|
|
16
|
+
"react",
|
|
17
|
+
"firebase",
|
|
18
|
+
"cross-promotion"
|
|
19
|
+
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"main": "./dist/index.cjs",
|
|
22
|
+
"module": "./dist/index.js",
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"import": "./dist/index.js",
|
|
28
|
+
"require": "./dist/index.cjs"
|
|
29
|
+
},
|
|
30
|
+
"./components": {
|
|
31
|
+
"types": "./dist/components/index.d.ts",
|
|
32
|
+
"import": "./dist/components/index.js",
|
|
33
|
+
"require": "./dist/components/index.cjs"
|
|
34
|
+
},
|
|
35
|
+
"./hooks": {
|
|
36
|
+
"types": "./dist/hooks/index.d.ts",
|
|
37
|
+
"import": "./dist/hooks/index.js",
|
|
38
|
+
"require": "./dist/hooks/index.cjs"
|
|
39
|
+
},
|
|
40
|
+
"./services": {
|
|
41
|
+
"types": "./dist/services/index.d.ts",
|
|
42
|
+
"import": "./dist/services/index.js",
|
|
43
|
+
"require": "./dist/services/index.cjs"
|
|
44
|
+
},
|
|
45
|
+
"./types": {
|
|
46
|
+
"types": "./dist/types/index.d.ts",
|
|
47
|
+
"import": "./dist/types/index.js",
|
|
48
|
+
"require": "./dist/types/index.cjs"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"files": [
|
|
52
|
+
"dist",
|
|
53
|
+
"README.md"
|
|
54
|
+
],
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "vite build && tsc --emitDeclarationOnly",
|
|
57
|
+
"dev": "vite build --watch",
|
|
58
|
+
"lint": "eslint src --ext .ts,.tsx",
|
|
59
|
+
"typecheck": "tsc --noEmit",
|
|
60
|
+
"prepublishOnly": "yarn build"
|
|
61
|
+
},
|
|
62
|
+
"peerDependencies": {
|
|
63
|
+
"@capacitor/preferences": ">=8.0.0",
|
|
64
|
+
"@radix-ui/react-icons": ">=1.3.2",
|
|
65
|
+
"@radix-ui/themes": ">=3.2.1",
|
|
66
|
+
"firebase": ">=12.8.0",
|
|
67
|
+
"react": ">=19.2.8",
|
|
68
|
+
"react-dom": ">=19.2.3",
|
|
69
|
+
"zustand": ">=5.0.10"
|
|
70
|
+
},
|
|
71
|
+
"peerDependenciesMeta": {
|
|
72
|
+
"@capacitor/preferences": {
|
|
73
|
+
"optional": true
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"devDependencies": {
|
|
77
|
+
"@capacitor/preferences": "^8.0.0",
|
|
78
|
+
"@radix-ui/react-icons": "^1.3.2",
|
|
79
|
+
"@radix-ui/themes": "^3.2.1",
|
|
80
|
+
"@types/react": "^19.2.8",
|
|
81
|
+
"@types/react-dom": "^19.2.3",
|
|
82
|
+
"@vitejs/plugin-react": "^4.7.0",
|
|
83
|
+
"firebase": "^12.8.0",
|
|
84
|
+
"typescript": "^5.9.3",
|
|
85
|
+
"vite": "^7.3.1",
|
|
86
|
+
"vite-plugin-dts": "^4.5.4",
|
|
87
|
+
"zustand": "^5.0.10"
|
|
88
|
+
},
|
|
89
|
+
"dependencies": {}
|
|
90
|
+
}
|