shared-features 0.0.1 → 0.0.2
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 +84 -7
- package/dist/AdBanner-B9918Vst.cjs +1427 -0
- package/dist/AdBanner-B9918Vst.cjs.map +1 -0
- package/dist/AdBanner-z1osYgog.js +1428 -0
- package/dist/AdBanner-z1osYgog.js.map +1 -0
- package/dist/components/ads/AdBanner.d.ts +25 -0
- package/dist/components/ads/AdBanner.d.ts.map +1 -0
- package/dist/components/ads/AdModal.d.ts +27 -0
- package/dist/components/ads/AdModal.d.ts.map +1 -0
- package/dist/components/ads/AdSlider.d.ts +21 -0
- package/dist/components/ads/AdSlider.d.ts.map +1 -0
- package/dist/components/ads/AdUpdateModal.d.ts +27 -0
- package/dist/components/ads/AdUpdateModal.d.ts.map +1 -0
- package/dist/components/ads/index.d.ts +9 -0
- package/dist/components/ads/index.d.ts.map +1 -1
- package/dist/components/ads/variants/LargePanelVariants.d.ts +34 -0
- package/dist/components/ads/variants/LargePanelVariants.d.ts.map +1 -0
- package/dist/components/ads/variants/SmallPanelVariants.d.ts +34 -0
- package/dist/components/ads/variants/SmallPanelVariants.d.ts.map +1 -0
- package/dist/components/ads/variants/index.d.ts +12 -0
- package/dist/components/ads/variants/index.d.ts.map +1 -0
- package/dist/components/index.cjs +20 -2
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +20 -2
- package/dist/hooks/index.cjs +3 -1
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +4 -2
- package/dist/hooks/useCampaigns.d.ts +44 -0
- package/dist/hooks/useCampaigns.d.ts.map +1 -1
- package/dist/index.cjs +23 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +23 -3
- package/dist/types/campaigns.d.ts +20 -32
- package/dist/types/campaigns.d.ts.map +1 -1
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/{useCampaigns-BNOHpETm.cjs → useCampaigns-BKGqKAUo.cjs} +56 -1
- package/dist/useCampaigns-BKGqKAUo.cjs.map +1 -0
- package/dist/{useCampaigns-3NxODLLs.js → useCampaigns-osYDc6WC.js} +56 -1
- package/dist/{useCampaigns-3NxODLLs.js.map → useCampaigns-osYDc6WC.js.map} +1 -1
- package/package.json +3 -1
- package/dist/AdPanel-D0BiV6Xb.cjs +0 -88
- package/dist/AdPanel-D0BiV6Xb.cjs.map +0 -1
- package/dist/AdPanel-RGRBf4ub.js +0 -89
- package/dist/AdPanel-RGRBf4ub.js.map +0 -1
- package/dist/useCampaigns-BNOHpETm.cjs.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shared-features",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "Shared features for Zaions projects - centralized ads, contacts, feature requests, and more",
|
|
5
5
|
"author": "Ahsan Mahmood <aoneahsan@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
"@radix-ui/react-icons": ">=1.3.2",
|
|
65
65
|
"@radix-ui/themes": ">=3.2.1",
|
|
66
66
|
"firebase": ">=12.8.0",
|
|
67
|
+
"lucide-react": ">=0.400.0",
|
|
67
68
|
"react": ">=19.2.8",
|
|
68
69
|
"react-dom": ">=19.2.3",
|
|
69
70
|
"zustand": ">=5.0.10"
|
|
@@ -81,6 +82,7 @@
|
|
|
81
82
|
"@types/react-dom": "^19.2.3",
|
|
82
83
|
"@vitejs/plugin-react": "^4.7.0",
|
|
83
84
|
"firebase": "^12.8.0",
|
|
85
|
+
"lucide-react": "^0.511.0",
|
|
84
86
|
"typescript": "^5.9.3",
|
|
85
87
|
"vite": "^7.3.1",
|
|
86
88
|
"vite-plugin-dts": "^4.5.4",
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
const jsxRuntime = require("react/jsx-runtime");
|
|
3
|
-
const react = require("react");
|
|
4
|
-
const themes = require("@radix-ui/themes");
|
|
5
|
-
const reactIcons = require("@radix-ui/react-icons");
|
|
6
|
-
const useCampaigns = require("./useCampaigns-BNOHpETm.cjs");
|
|
7
|
-
function AdPanel({
|
|
8
|
-
placement,
|
|
9
|
-
variant: _variant = "small_panel_2",
|
|
10
|
-
className
|
|
11
|
-
}) {
|
|
12
|
-
const { campaign, loading, error, recordImpression, recordClick, recordClose } = useCampaigns.useCampaign({ placement });
|
|
13
|
-
const hasRecordedImpression = react.useRef(false);
|
|
14
|
-
react.useEffect(() => {
|
|
15
|
-
if (campaign && !hasRecordedImpression.current) {
|
|
16
|
-
hasRecordedImpression.current = true;
|
|
17
|
-
recordImpression(campaign);
|
|
18
|
-
}
|
|
19
|
-
}, [campaign, recordImpression]);
|
|
20
|
-
if (loading || error || !campaign) {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
const handleClick = () => {
|
|
24
|
-
recordClick(campaign);
|
|
25
|
-
const url = campaign.customCtaUrl || campaign.product.url;
|
|
26
|
-
window.open(url, "_blank", "noopener,noreferrer");
|
|
27
|
-
};
|
|
28
|
-
const handleClose = () => {
|
|
29
|
-
recordClose(campaign);
|
|
30
|
-
};
|
|
31
|
-
const title = campaign.customTitle || campaign.product.name;
|
|
32
|
-
const tagline = campaign.customTagline || campaign.product.tagline;
|
|
33
|
-
const ctaText = campaign.customCta || "Learn More";
|
|
34
|
-
const color = campaign.customProductColor || campaign.product.color;
|
|
35
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
36
|
-
themes.Box,
|
|
37
|
-
{
|
|
38
|
-
className,
|
|
39
|
-
style: {
|
|
40
|
-
border: `1px solid ${color}`,
|
|
41
|
-
borderRadius: "8px",
|
|
42
|
-
padding: "12px",
|
|
43
|
-
backgroundColor: `${color}10`,
|
|
44
|
-
position: "relative"
|
|
45
|
-
},
|
|
46
|
-
children: [
|
|
47
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
48
|
-
themes.IconButton,
|
|
49
|
-
{
|
|
50
|
-
size: "1",
|
|
51
|
-
variant: "ghost",
|
|
52
|
-
onClick: handleClose,
|
|
53
|
-
style: {
|
|
54
|
-
position: "absolute",
|
|
55
|
-
top: "4px",
|
|
56
|
-
right: "4px"
|
|
57
|
-
},
|
|
58
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.Cross2Icon, {})
|
|
59
|
-
}
|
|
60
|
-
),
|
|
61
|
-
/* @__PURE__ */ jsxRuntime.jsxs(themes.Flex, { direction: "column", gap: "2", children: [
|
|
62
|
-
/* @__PURE__ */ jsxRuntime.jsxs(themes.Flex, { align: "center", gap: "2", children: [
|
|
63
|
-
campaign.product.icon64 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
64
|
-
themes.Box,
|
|
65
|
-
{
|
|
66
|
-
dangerouslySetInnerHTML: { __html: campaign.product.icon64 },
|
|
67
|
-
style: { width: 32, height: 32 }
|
|
68
|
-
}
|
|
69
|
-
),
|
|
70
|
-
/* @__PURE__ */ jsxRuntime.jsx(themes.Text, { weight: "bold", size: "3", style: { color }, children: title })
|
|
71
|
-
] }),
|
|
72
|
-
/* @__PURE__ */ jsxRuntime.jsx(themes.Text, { size: "2", color: "gray", children: tagline }),
|
|
73
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
74
|
-
themes.Button,
|
|
75
|
-
{
|
|
76
|
-
size: "2",
|
|
77
|
-
onClick: handleClick,
|
|
78
|
-
style: { backgroundColor: color },
|
|
79
|
-
children: ctaText
|
|
80
|
-
}
|
|
81
|
-
)
|
|
82
|
-
] })
|
|
83
|
-
]
|
|
84
|
-
}
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
exports.AdPanel = AdPanel;
|
|
88
|
-
//# sourceMappingURL=AdPanel-D0BiV6Xb.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AdPanel-D0BiV6Xb.cjs","sources":["../src/components/ads/AdPanel.tsx"],"sourcesContent":["/**\n * AdPanel Component\n *\n * A simple ad panel component that displays a single campaign.\n * Can be placed in sidebars, footers, or other static locations.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport { useEffect, useRef } from 'react';\nimport { Box, Flex, Text, Button, IconButton } from '@radix-ui/themes';\nimport { Cross2Icon } from '@radix-ui/react-icons';\nimport { useCampaign } from '../../hooks/useCampaigns';\nimport type { AdPanelProps } from '../../types/campaigns';\n\n/**\n * AdPanel displays a single campaign in a compact panel format.\n *\n * @example\n * ```tsx\n * <AdPanel placement=\"sidebar_panel\" variant=\"small_panel_2\" />\n * ```\n */\nexport function AdPanel({\n placement,\n variant: _variant = 'small_panel_2',\n className,\n}: AdPanelProps) {\n // TODO: variant will be used for different display styles in future variants\n const { campaign, loading, error, recordImpression, recordClick, recordClose } =\n useCampaign({ placement });\n\n const hasRecordedImpression = useRef(false);\n\n // Record impression when campaign is first displayed\n useEffect(() => {\n if (campaign && !hasRecordedImpression.current) {\n hasRecordedImpression.current = true;\n recordImpression(campaign);\n }\n }, [campaign, recordImpression]);\n\n if (loading || error || !campaign) {\n return null;\n }\n\n const handleClick = () => {\n recordClick(campaign);\n const url = campaign.customCtaUrl || campaign.product.url;\n window.open(url, '_blank', 'noopener,noreferrer');\n };\n\n const handleClose = () => {\n recordClose(campaign);\n // Hide the panel (could use state or CSS)\n };\n\n const title = campaign.customTitle || campaign.product.name;\n const tagline = campaign.customTagline || campaign.product.tagline;\n const ctaText = campaign.customCta || 'Learn More';\n const color = campaign.customProductColor || campaign.product.color;\n\n return (\n <Box\n className={className}\n style={{\n border: `1px solid ${color}`,\n borderRadius: '8px',\n padding: '12px',\n backgroundColor: `${color}10`,\n position: 'relative',\n }}\n >\n <IconButton\n size=\"1\"\n variant=\"ghost\"\n onClick={handleClose}\n style={{\n position: 'absolute',\n top: '4px',\n right: '4px',\n }}\n >\n <Cross2Icon />\n </IconButton>\n\n <Flex direction=\"column\" gap=\"2\">\n <Flex align=\"center\" gap=\"2\">\n {campaign.product.icon64 && (\n <Box\n dangerouslySetInnerHTML={{ __html: campaign.product.icon64 }}\n style={{ width: 32, height: 32 }}\n />\n )}\n <Text weight=\"bold\" size=\"3\" style={{ color }}>\n {title}\n </Text>\n </Flex>\n\n <Text size=\"2\" color=\"gray\">\n {tagline}\n </Text>\n\n <Button\n size=\"2\"\n onClick={handleClick}\n style={{ backgroundColor: color }}\n >\n {ctaText}\n </Button>\n </Flex>\n </Box>\n );\n}\n\nexport default AdPanel;\n"],"names":["useCampaign","useRef","useEffect","jsxs","Box","jsx","IconButton","Cross2Icon","Flex","Text","Button"],"mappings":";;;;;;AAuBO,SAAS,QAAQ;AAAA,EACtB;AAAA,EACA,SAAS,WAAW;AAAA,EACpB;AACF,GAAiB;AAEf,QAAM,EAAE,UAAU,SAAS,OAAO,kBAAkB,aAAa,gBAC/DA,aAAAA,YAAY,EAAE,WAAW;AAE3B,QAAM,wBAAwBC,MAAAA,OAAO,KAAK;AAG1CC,QAAAA,UAAU,MAAM;AACd,QAAI,YAAY,CAAC,sBAAsB,SAAS;AAC9C,4BAAsB,UAAU;AAChC,uBAAiB,QAAQ;AAAA,IAC3B;AAAA,EACF,GAAG,CAAC,UAAU,gBAAgB,CAAC;AAE/B,MAAI,WAAW,SAAS,CAAC,UAAU;AACjC,WAAO;AAAA,EACT;AAEA,QAAM,cAAc,MAAM;AACxB,gBAAY,QAAQ;AACpB,UAAM,MAAM,SAAS,gBAAgB,SAAS,QAAQ;AACtD,WAAO,KAAK,KAAK,UAAU,qBAAqB;AAAA,EAClD;AAEA,QAAM,cAAc,MAAM;AACxB,gBAAY,QAAQ;AAAA,EAEtB;AAEA,QAAM,QAAQ,SAAS,eAAe,SAAS,QAAQ;AACvD,QAAM,UAAU,SAAS,iBAAiB,SAAS,QAAQ;AAC3D,QAAM,UAAU,SAAS,aAAa;AACtC,QAAM,QAAQ,SAAS,sBAAsB,SAAS,QAAQ;AAE9D,SACEC,2BAAAA;AAAAA,IAACC,OAAAA;AAAAA,IAAA;AAAA,MACC;AAAA,MACA,OAAO;AAAA,QACL,QAAQ,aAAa,KAAK;AAAA,QAC1B,cAAc;AAAA,QACd,SAAS;AAAA,QACT,iBAAiB,GAAG,KAAK;AAAA,QACzB,UAAU;AAAA,MAAA;AAAA,MAGZ,UAAA;AAAA,QAAAC,2BAAAA;AAAAA,UAACC,OAAAA;AAAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,SAAS;AAAA,YACT,OAAO;AAAA,cACL,UAAU;AAAA,cACV,KAAK;AAAA,cACL,OAAO;AAAA,YAAA;AAAA,YAGT,yCAACC,WAAAA,YAAA,CAAA,CAAW;AAAA,UAAA;AAAA,QAAA;AAAA,QAGdJ,2BAAAA,KAACK,OAAAA,MAAA,EAAK,WAAU,UAAS,KAAI,KAC3B,UAAA;AAAA,UAAAL,2BAAAA,KAACK,OAAAA,MAAA,EAAK,OAAM,UAAS,KAAI,KACtB,UAAA;AAAA,YAAA,SAAS,QAAQ,UAChBH,2BAAAA;AAAAA,cAACD,OAAAA;AAAAA,cAAA;AAAA,gBACC,yBAAyB,EAAE,QAAQ,SAAS,QAAQ,OAAA;AAAA,gBACpD,OAAO,EAAE,OAAO,IAAI,QAAQ,GAAA;AAAA,cAAG;AAAA,YAAA;AAAA,YAGnCC,2BAAAA,IAACI,OAAAA,MAAA,EAAK,QAAO,QAAO,MAAK,KAAI,OAAO,EAAE,SACnC,UAAA,MAAA,CACH;AAAA,UAAA,GACF;AAAA,yCAECA,OAAAA,MAAA,EAAK,MAAK,KAAI,OAAM,QAClB,UAAA,SACH;AAAA,UAEAJ,2BAAAA;AAAAA,YAACK,OAAAA;AAAAA,YAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS;AAAA,cACT,OAAO,EAAE,iBAAiB,MAAA;AAAA,cAEzB,UAAA;AAAA,YAAA;AAAA,UAAA;AAAA,QACH,EAAA,CACF;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;;"}
|
package/dist/AdPanel-RGRBf4ub.js
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useRef, useEffect } from "react";
|
|
3
|
-
import { Box, IconButton, Flex, Text, Button } from "@radix-ui/themes";
|
|
4
|
-
import { Cross2Icon } from "@radix-ui/react-icons";
|
|
5
|
-
import { a as useCampaign } from "./useCampaigns-3NxODLLs.js";
|
|
6
|
-
function AdPanel({
|
|
7
|
-
placement,
|
|
8
|
-
variant: _variant = "small_panel_2",
|
|
9
|
-
className
|
|
10
|
-
}) {
|
|
11
|
-
const { campaign, loading, error, recordImpression, recordClick, recordClose } = useCampaign({ placement });
|
|
12
|
-
const hasRecordedImpression = useRef(false);
|
|
13
|
-
useEffect(() => {
|
|
14
|
-
if (campaign && !hasRecordedImpression.current) {
|
|
15
|
-
hasRecordedImpression.current = true;
|
|
16
|
-
recordImpression(campaign);
|
|
17
|
-
}
|
|
18
|
-
}, [campaign, recordImpression]);
|
|
19
|
-
if (loading || error || !campaign) {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
const handleClick = () => {
|
|
23
|
-
recordClick(campaign);
|
|
24
|
-
const url = campaign.customCtaUrl || campaign.product.url;
|
|
25
|
-
window.open(url, "_blank", "noopener,noreferrer");
|
|
26
|
-
};
|
|
27
|
-
const handleClose = () => {
|
|
28
|
-
recordClose(campaign);
|
|
29
|
-
};
|
|
30
|
-
const title = campaign.customTitle || campaign.product.name;
|
|
31
|
-
const tagline = campaign.customTagline || campaign.product.tagline;
|
|
32
|
-
const ctaText = campaign.customCta || "Learn More";
|
|
33
|
-
const color = campaign.customProductColor || campaign.product.color;
|
|
34
|
-
return /* @__PURE__ */ jsxs(
|
|
35
|
-
Box,
|
|
36
|
-
{
|
|
37
|
-
className,
|
|
38
|
-
style: {
|
|
39
|
-
border: `1px solid ${color}`,
|
|
40
|
-
borderRadius: "8px",
|
|
41
|
-
padding: "12px",
|
|
42
|
-
backgroundColor: `${color}10`,
|
|
43
|
-
position: "relative"
|
|
44
|
-
},
|
|
45
|
-
children: [
|
|
46
|
-
/* @__PURE__ */ jsx(
|
|
47
|
-
IconButton,
|
|
48
|
-
{
|
|
49
|
-
size: "1",
|
|
50
|
-
variant: "ghost",
|
|
51
|
-
onClick: handleClose,
|
|
52
|
-
style: {
|
|
53
|
-
position: "absolute",
|
|
54
|
-
top: "4px",
|
|
55
|
-
right: "4px"
|
|
56
|
-
},
|
|
57
|
-
children: /* @__PURE__ */ jsx(Cross2Icon, {})
|
|
58
|
-
}
|
|
59
|
-
),
|
|
60
|
-
/* @__PURE__ */ jsxs(Flex, { direction: "column", gap: "2", children: [
|
|
61
|
-
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: "2", children: [
|
|
62
|
-
campaign.product.icon64 && /* @__PURE__ */ jsx(
|
|
63
|
-
Box,
|
|
64
|
-
{
|
|
65
|
-
dangerouslySetInnerHTML: { __html: campaign.product.icon64 },
|
|
66
|
-
style: { width: 32, height: 32 }
|
|
67
|
-
}
|
|
68
|
-
),
|
|
69
|
-
/* @__PURE__ */ jsx(Text, { weight: "bold", size: "3", style: { color }, children: title })
|
|
70
|
-
] }),
|
|
71
|
-
/* @__PURE__ */ jsx(Text, { size: "2", color: "gray", children: tagline }),
|
|
72
|
-
/* @__PURE__ */ jsx(
|
|
73
|
-
Button,
|
|
74
|
-
{
|
|
75
|
-
size: "2",
|
|
76
|
-
onClick: handleClick,
|
|
77
|
-
style: { backgroundColor: color },
|
|
78
|
-
children: ctaText
|
|
79
|
-
}
|
|
80
|
-
)
|
|
81
|
-
] })
|
|
82
|
-
]
|
|
83
|
-
}
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
export {
|
|
87
|
-
AdPanel as A
|
|
88
|
-
};
|
|
89
|
-
//# sourceMappingURL=AdPanel-RGRBf4ub.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AdPanel-RGRBf4ub.js","sources":["../src/components/ads/AdPanel.tsx"],"sourcesContent":["/**\n * AdPanel Component\n *\n * A simple ad panel component that displays a single campaign.\n * Can be placed in sidebars, footers, or other static locations.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport { useEffect, useRef } from 'react';\nimport { Box, Flex, Text, Button, IconButton } from '@radix-ui/themes';\nimport { Cross2Icon } from '@radix-ui/react-icons';\nimport { useCampaign } from '../../hooks/useCampaigns';\nimport type { AdPanelProps } from '../../types/campaigns';\n\n/**\n * AdPanel displays a single campaign in a compact panel format.\n *\n * @example\n * ```tsx\n * <AdPanel placement=\"sidebar_panel\" variant=\"small_panel_2\" />\n * ```\n */\nexport function AdPanel({\n placement,\n variant: _variant = 'small_panel_2',\n className,\n}: AdPanelProps) {\n // TODO: variant will be used for different display styles in future variants\n const { campaign, loading, error, recordImpression, recordClick, recordClose } =\n useCampaign({ placement });\n\n const hasRecordedImpression = useRef(false);\n\n // Record impression when campaign is first displayed\n useEffect(() => {\n if (campaign && !hasRecordedImpression.current) {\n hasRecordedImpression.current = true;\n recordImpression(campaign);\n }\n }, [campaign, recordImpression]);\n\n if (loading || error || !campaign) {\n return null;\n }\n\n const handleClick = () => {\n recordClick(campaign);\n const url = campaign.customCtaUrl || campaign.product.url;\n window.open(url, '_blank', 'noopener,noreferrer');\n };\n\n const handleClose = () => {\n recordClose(campaign);\n // Hide the panel (could use state or CSS)\n };\n\n const title = campaign.customTitle || campaign.product.name;\n const tagline = campaign.customTagline || campaign.product.tagline;\n const ctaText = campaign.customCta || 'Learn More';\n const color = campaign.customProductColor || campaign.product.color;\n\n return (\n <Box\n className={className}\n style={{\n border: `1px solid ${color}`,\n borderRadius: '8px',\n padding: '12px',\n backgroundColor: `${color}10`,\n position: 'relative',\n }}\n >\n <IconButton\n size=\"1\"\n variant=\"ghost\"\n onClick={handleClose}\n style={{\n position: 'absolute',\n top: '4px',\n right: '4px',\n }}\n >\n <Cross2Icon />\n </IconButton>\n\n <Flex direction=\"column\" gap=\"2\">\n <Flex align=\"center\" gap=\"2\">\n {campaign.product.icon64 && (\n <Box\n dangerouslySetInnerHTML={{ __html: campaign.product.icon64 }}\n style={{ width: 32, height: 32 }}\n />\n )}\n <Text weight=\"bold\" size=\"3\" style={{ color }}>\n {title}\n </Text>\n </Flex>\n\n <Text size=\"2\" color=\"gray\">\n {tagline}\n </Text>\n\n <Button\n size=\"2\"\n onClick={handleClick}\n style={{ backgroundColor: color }}\n >\n {ctaText}\n </Button>\n </Flex>\n </Box>\n );\n}\n\nexport default AdPanel;\n"],"names":[],"mappings":";;;;;AAuBO,SAAS,QAAQ;AAAA,EACtB;AAAA,EACA,SAAS,WAAW;AAAA,EACpB;AACF,GAAiB;AAEf,QAAM,EAAE,UAAU,SAAS,OAAO,kBAAkB,aAAa,gBAC/D,YAAY,EAAE,WAAW;AAE3B,QAAM,wBAAwB,OAAO,KAAK;AAG1C,YAAU,MAAM;AACd,QAAI,YAAY,CAAC,sBAAsB,SAAS;AAC9C,4BAAsB,UAAU;AAChC,uBAAiB,QAAQ;AAAA,IAC3B;AAAA,EACF,GAAG,CAAC,UAAU,gBAAgB,CAAC;AAE/B,MAAI,WAAW,SAAS,CAAC,UAAU;AACjC,WAAO;AAAA,EACT;AAEA,QAAM,cAAc,MAAM;AACxB,gBAAY,QAAQ;AACpB,UAAM,MAAM,SAAS,gBAAgB,SAAS,QAAQ;AACtD,WAAO,KAAK,KAAK,UAAU,qBAAqB;AAAA,EAClD;AAEA,QAAM,cAAc,MAAM;AACxB,gBAAY,QAAQ;AAAA,EAEtB;AAEA,QAAM,QAAQ,SAAS,eAAe,SAAS,QAAQ;AACvD,QAAM,UAAU,SAAS,iBAAiB,SAAS,QAAQ;AAC3D,QAAM,UAAU,SAAS,aAAa;AACtC,QAAM,QAAQ,SAAS,sBAAsB,SAAS,QAAQ;AAE9D,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,OAAO;AAAA,QACL,QAAQ,aAAa,KAAK;AAAA,QAC1B,cAAc;AAAA,QACd,SAAS;AAAA,QACT,iBAAiB,GAAG,KAAK;AAAA,QACzB,UAAU;AAAA,MAAA;AAAA,MAGZ,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,SAAS;AAAA,YACT,OAAO;AAAA,cACL,UAAU;AAAA,cACV,KAAK;AAAA,cACL,OAAO;AAAA,YAAA;AAAA,YAGT,8BAAC,YAAA,CAAA,CAAW;AAAA,UAAA;AAAA,QAAA;AAAA,QAGd,qBAAC,MAAA,EAAK,WAAU,UAAS,KAAI,KAC3B,UAAA;AAAA,UAAA,qBAAC,MAAA,EAAK,OAAM,UAAS,KAAI,KACtB,UAAA;AAAA,YAAA,SAAS,QAAQ,UAChB;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,yBAAyB,EAAE,QAAQ,SAAS,QAAQ,OAAA;AAAA,gBACpD,OAAO,EAAE,OAAO,IAAI,QAAQ,GAAA;AAAA,cAAG;AAAA,YAAA;AAAA,YAGnC,oBAAC,MAAA,EAAK,QAAO,QAAO,MAAK,KAAI,OAAO,EAAE,SACnC,UAAA,MAAA,CACH;AAAA,UAAA,GACF;AAAA,8BAEC,MAAA,EAAK,MAAK,KAAI,OAAM,QAClB,UAAA,SACH;AAAA,UAEA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS;AAAA,cACT,OAAO,EAAE,iBAAiB,MAAA;AAAA,cAEzB,UAAA;AAAA,YAAA;AAAA,UAAA;AAAA,QACH,EAAA,CACF;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;"}
|
|
@@ -1 +0,0 @@
|
|
|
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;;;"}
|