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.
Files changed (60) hide show
  1. package/README.md +200 -0
  2. package/dist/AdPanel-D0BiV6Xb.cjs +88 -0
  3. package/dist/AdPanel-D0BiV6Xb.cjs.map +1 -0
  4. package/dist/AdPanel-RGRBf4ub.js +89 -0
  5. package/dist/AdPanel-RGRBf4ub.js.map +1 -0
  6. package/dist/analytics-6shJHRZG.cjs +463 -0
  7. package/dist/analytics-6shJHRZG.cjs.map +1 -0
  8. package/dist/analytics-Bbmodnm_.js +442 -0
  9. package/dist/analytics-Bbmodnm_.js.map +1 -0
  10. package/dist/components/ads/AdPanel.d.ts +12 -0
  11. package/dist/components/ads/AdPanel.d.ts.map +1 -0
  12. package/dist/components/ads/index.d.ts +9 -0
  13. package/dist/components/ads/index.d.ts.map +1 -0
  14. package/dist/components/index.cjs +5 -0
  15. package/dist/components/index.cjs.map +1 -0
  16. package/dist/components/index.d.ts +9 -0
  17. package/dist/components/index.d.ts.map +1 -0
  18. package/dist/components/index.js +5 -0
  19. package/dist/components/index.js.map +1 -0
  20. package/dist/firebase/config.d.ts +77 -0
  21. package/dist/firebase/config.d.ts.map +1 -0
  22. package/dist/firebase/init.d.ts +55 -0
  23. package/dist/firebase/init.d.ts.map +1 -0
  24. package/dist/hooks/index.cjs +6 -0
  25. package/dist/hooks/index.cjs.map +1 -0
  26. package/dist/hooks/index.d.ts +10 -0
  27. package/dist/hooks/index.d.ts.map +1 -0
  28. package/dist/hooks/index.js +6 -0
  29. package/dist/hooks/index.js.map +1 -0
  30. package/dist/hooks/useCampaigns.d.ts +59 -0
  31. package/dist/hooks/useCampaigns.d.ts.map +1 -0
  32. package/dist/index.cjs +37 -0
  33. package/dist/index.cjs.map +1 -0
  34. package/dist/index.d.ts +25 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +37 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/services/analytics.d.ts +32 -0
  39. package/dist/services/analytics.d.ts.map +1 -0
  40. package/dist/services/campaigns.d.ts +31 -0
  41. package/dist/services/campaigns.d.ts.map +1 -0
  42. package/dist/services/index.cjs +18 -0
  43. package/dist/services/index.cjs.map +1 -0
  44. package/dist/services/index.d.ts +10 -0
  45. package/dist/services/index.d.ts.map +1 -0
  46. package/dist/services/index.js +18 -0
  47. package/dist/services/index.js.map +1 -0
  48. package/dist/types/campaigns.d.ts +378 -0
  49. package/dist/types/campaigns.d.ts.map +1 -0
  50. package/dist/types/index.cjs +49 -0
  51. package/dist/types/index.cjs.map +1 -0
  52. package/dist/types/index.d.ts +10 -0
  53. package/dist/types/index.d.ts.map +1 -0
  54. package/dist/types/index.js +49 -0
  55. package/dist/types/index.js.map +1 -0
  56. package/dist/useCampaigns-3NxODLLs.js +98 -0
  57. package/dist/useCampaigns-3NxODLLs.js.map +1 -0
  58. package/dist/useCampaigns-BNOHpETm.cjs +97 -0
  59. package/dist/useCampaigns-BNOHpETm.cjs.map +1 -0
  60. package/package.json +90 -0
package/README.md ADDED
@@ -0,0 +1,200 @@
1
+ # shared-features
2
+
3
+ Centralized common features for Zaions projects. Manage ads, contacts, feature requests, and more from a single admin panel at [aoneahsan.com](https://aoneahsan.com).
4
+
5
+ ## Features
6
+
7
+ - **Advertising Campaigns** - Cross-promote Zaions products across all projects
8
+ - **Products Catalog** - Centralized product information
9
+ - **Contact Forms** - (Coming soon)
10
+ - **Feature Requests** - (Coming soon)
11
+ - **Payment Options** - (Coming soon)
12
+ - **Social Links** - (Coming soon)
13
+ - **Developer Info** - (Coming soon)
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ yarn add shared-features
19
+ ```
20
+
21
+ ## Peer Dependencies
22
+
23
+ This package requires the following peer dependencies:
24
+
25
+ ```bash
26
+ yarn add react react-dom firebase @radix-ui/themes zustand
27
+ # Optional for mobile:
28
+ yarn add @capacitor/preferences
29
+ ```
30
+
31
+ ## Setup
32
+
33
+ ### 1. Initialize the package
34
+
35
+ ```typescript
36
+ // src/config/shared-features.ts
37
+ import { initSharedFeatures } from 'shared-features';
38
+
39
+ export const sharedFeatures = initSharedFeatures({
40
+ firebaseConfig: {
41
+ apiKey: import.meta.env.VITE_ZAIONS_FIREBASE_API_KEY,
42
+ authDomain: 'aoneahsan-portfolio.firebaseapp.com',
43
+ projectId: 'aoneahsan-portfolio',
44
+ },
45
+ projectId: 'your-project-id',
46
+ projectName: 'Your Project Name',
47
+ platform: 'web', // or 'android', 'ios', 'extension'
48
+ debug: import.meta.env.DEV,
49
+ });
50
+ ```
51
+
52
+ ### 2. Add environment variables
53
+
54
+ Create or update your `.env` file:
55
+
56
+ ```env
57
+ # shared-features configuration
58
+ VITE_ZAIONS_FIREBASE_API_KEY=your-api-key
59
+ ```
60
+
61
+ ### 3. Initialize on app startup
62
+
63
+ ```typescript
64
+ // src/main.tsx or src/App.tsx
65
+ import { initSharedFeatures } from 'shared-features';
66
+ import './config/shared-features'; // Import to initialize
67
+ ```
68
+
69
+ ## Usage
70
+
71
+ ### Displaying Ads
72
+
73
+ ```tsx
74
+ import { AdPanel, useCampaigns } from 'shared-features';
75
+
76
+ // Simple panel in sidebar
77
+ function Sidebar() {
78
+ return <AdPanel placement="sidebar_panel" />;
79
+ }
80
+
81
+ // Custom implementation with hook
82
+ function Footer() {
83
+ const { campaigns, loading, recordImpression, recordClick } = useCampaigns({
84
+ placement: 'footer_slider',
85
+ maxCampaigns: 5,
86
+ });
87
+
88
+ if (loading || campaigns.length === 0) return null;
89
+
90
+ return (
91
+ <div>
92
+ {campaigns.map(campaign => (
93
+ <div
94
+ key={campaign.id}
95
+ onMouseEnter={() => recordImpression(campaign)}
96
+ onClick={() => {
97
+ recordClick(campaign);
98
+ window.open(campaign.product.url, '_blank');
99
+ }}
100
+ >
101
+ <h3>{campaign.product.name}</h3>
102
+ <p>{campaign.product.tagline}</p>
103
+ </div>
104
+ ))}
105
+ </div>
106
+ );
107
+ }
108
+ ```
109
+
110
+ ### Available Placements
111
+
112
+ | Placement | Description | Recommended Variant |
113
+ |-----------|-------------|-------------------|
114
+ | `popup_slider` | Extension popup | Small variants |
115
+ | `options_panel` | Extension options page | Small variants |
116
+ | `onetime_modal` | First-visit welcome modal | Large variants |
117
+ | `update_modal` | Version update modal | Large variants |
118
+ | `notification` | Push notification | - |
119
+ | `footer_slider` | Web app footer | Small variants |
120
+ | `sidebar_panel` | Web app sidebar | Small variants |
121
+ | `home_banner` | Home page hero | Large variants |
122
+
123
+ ### Available Variants
124
+
125
+ **Small Variants (compact spaces):**
126
+ - `small_panel_1` - Minimal
127
+ - `small_panel_2` - Tagline
128
+ - `small_panel_3` - Features
129
+ - `small_panel_4` - Gradient
130
+ - `small_panel_5` - Card
131
+
132
+ **Large Variants (feature areas):**
133
+ - `large_slider_1` - Hero
134
+ - `large_slider_2` - Feature Grid
135
+ - `large_slider_3` - Testimonial
136
+ - `large_slider_4` - Comparison
137
+ - `large_slider_5` - Video Placeholder
138
+
139
+ ## API Reference
140
+
141
+ ### `initSharedFeatures(config)`
142
+
143
+ Initialize the package with your configuration.
144
+
145
+ ```typescript
146
+ interface SharedFeaturesConfig {
147
+ firebaseConfig: FirebaseConfig;
148
+ projectId: string;
149
+ projectName: string;
150
+ platform: 'web' | 'android' | 'ios' | 'extension';
151
+ debug?: boolean;
152
+ }
153
+ ```
154
+
155
+ ### `useCampaigns(options)`
156
+
157
+ Hook to fetch and manage campaigns.
158
+
159
+ ```typescript
160
+ interface UseCampaignsOptions {
161
+ placement: AdPlacement;
162
+ maxCampaigns?: number;
163
+ autoFetch?: boolean;
164
+ }
165
+
166
+ interface UseCampaignsResult {
167
+ campaigns: CampaignWithProduct[];
168
+ campaign: CampaignWithProduct | null;
169
+ loading: boolean;
170
+ error: string | null;
171
+ refetch: () => Promise<void>;
172
+ recordImpression: (campaign: CampaignWithProduct) => Promise<void>;
173
+ recordClick: (campaign: CampaignWithProduct) => Promise<void>;
174
+ recordClose: (campaign: CampaignWithProduct) => Promise<void>;
175
+ }
176
+ ```
177
+
178
+ ### `AdPanel`
179
+
180
+ Component to display a single ad.
181
+
182
+ ```tsx
183
+ <AdPanel
184
+ placement="sidebar_panel"
185
+ variant="small_panel_2"
186
+ className="my-ad"
187
+ />
188
+ ```
189
+
190
+ ## Frequency Capping
191
+
192
+ Ads automatically respect frequency capping set in the admin panel. By default, each campaign is shown to a user once every 20 days. This is tracked locally using Capacitor Preferences (mobile) or localStorage (web).
193
+
194
+ ## Admin Panel
195
+
196
+ All campaigns are managed through the admin panel at [aoneahsan.com/admin/campaigns](https://aoneahsan.com/admin/campaigns). Consumer projects only need read access.
197
+
198
+ ## License
199
+
200
+ MIT - Ahsan Mahmood <aoneahsan@gmail.com>
@@ -0,0 +1,88 @@
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
@@ -0,0 +1 @@
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;;"}
@@ -0,0 +1,89 @@
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
@@ -0,0 +1 @@
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;"}