ani-ads-sdk 1.0.0 → 1.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/dist/index.js CHANGED
@@ -30,14 +30,16 @@ var AniAds = ({
30
30
  creator_code,
31
31
  user_wallet_address,
32
32
  ad_format,
33
- api_url = "https://your-domain.com",
33
+ api_url = "https://ani-ads.vercel.app",
34
34
  // Replace with your Ani Ads API domain
35
35
  onAdClick
36
36
  }) => {
37
37
  const [ad, setAd] = (0, import_react.useState)(null);
38
38
  const [loading, setLoading] = (0, import_react.useState)(true);
39
39
  const [error, setError] = (0, import_react.useState)(null);
40
- const formats = Array.isArray(ad_format) ? ad_format : typeof ad_format === "string" ? ad_format.split(",").map((f) => f.trim()) : [];
40
+ const formats = (0, import_react.useMemo)(() => {
41
+ return Array.isArray(ad_format) ? ad_format : typeof ad_format === "string" ? ad_format.split(",").map((f) => f.trim()) : [];
42
+ }, [ad_format]);
41
43
  const getImageUrl = (ad2, format) => {
42
44
  const normalizedFormat = format.toLowerCase().replace(/[:\s]/g, "");
43
45
  if (normalizedFormat === "11" || normalizedFormat === "1:1") {
@@ -99,7 +101,6 @@ var AniAds = ({
99
101
  onAdClick(ad.id, ad.destination_url);
100
102
  }
101
103
  window.open(ad.destination_url, "_blank", "noopener,noreferrer");
102
- await fetchAd();
103
104
  }
104
105
  } catch (err) {
105
106
  console.error("Error tracking click:", err);
@@ -114,12 +115,52 @@ var AniAds = ({
114
115
  return ad2.image_square || ad2.image_portrait || ad2.image_landscape || null;
115
116
  };
116
117
  (0, import_react.useEffect)(() => {
117
- if (creator_code && formats.length > 0) {
118
- fetchAd();
119
- } else {
120
- setLoading(false);
121
- }
122
- }, [creator_code, formats, fetchAd]);
118
+ let cancelled = false;
119
+ const loadAd = async () => {
120
+ if (!creator_code || formats.length === 0) {
121
+ setLoading(false);
122
+ return;
123
+ }
124
+ try {
125
+ setLoading(true);
126
+ setError(null);
127
+ const response = await fetch(`${api_url}/api/ads/sdk`, {
128
+ method: "POST",
129
+ headers: {
130
+ "Content-Type": "application/json"
131
+ },
132
+ body: JSON.stringify({
133
+ creator_code,
134
+ formats
135
+ })
136
+ });
137
+ if (cancelled) return;
138
+ if (!response.ok) {
139
+ throw new Error("Failed to fetch ads");
140
+ }
141
+ const data = await response.json();
142
+ if (cancelled) return;
143
+ if (data.ad) {
144
+ setAd(data.ad);
145
+ } else {
146
+ setAd(null);
147
+ }
148
+ } catch (err) {
149
+ if (cancelled) return;
150
+ console.error("Error fetching ad:", err);
151
+ setError(err instanceof Error ? err.message : "Failed to load ad");
152
+ setAd(null);
153
+ } finally {
154
+ if (!cancelled) {
155
+ setLoading(false);
156
+ }
157
+ }
158
+ };
159
+ loadAd();
160
+ return () => {
161
+ cancelled = true;
162
+ };
163
+ }, [creator_code, ad_format, api_url]);
123
164
  if (loading) {
124
165
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
125
166
  display: "flex",
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.tsx"],"sourcesContent":["import React, { useEffect, useState, useCallback } from 'react'\r\n\r\nexport interface AniAdsProps {\r\n creator_code: string\r\n user_wallet_address: string\r\n ad_format: string[] | string // Can be array like [\"16:9\", \"9:16\"] or string like \"16:9,9:16\"\r\n api_url?: string // Optional API URL, defaults to production\r\n onAdClick?: (adId: string, destinationUrl: string) => void // Optional click handler\r\n}\r\n\r\ninterface Ad {\r\n id: string\r\n title: string\r\n destination_url: string\r\n image_square: string\r\n image_portrait: string\r\n image_landscape: string\r\n status: 'active' | 'paused' | 'deleting'\r\n remaining_budget: number\r\n}\r\n\r\nexport const AniAds: React.FC<AniAdsProps> = ({\r\n creator_code,\r\n user_wallet_address,\r\n ad_format,\r\n api_url = 'https://your-domain.com', // Replace with your Ani Ads API domain\r\n onAdClick,\r\n}) => {\r\n const [ad, setAd] = useState<Ad | null>(null)\r\n const [loading, setLoading] = useState(true)\r\n const [error, setError] = useState<string | null>(null)\r\n\r\n // Parse ad format\r\n const formats = Array.isArray(ad_format) \r\n ? ad_format \r\n : typeof ad_format === 'string' \r\n ? ad_format.split(',').map(f => f.trim())\r\n : []\r\n\r\n // Map format strings to image keys\r\n const getImageUrl = (ad: Ad, format: string): string | null => {\r\n const normalizedFormat = format.toLowerCase().replace(/[:\\s]/g, '')\r\n \r\n if (normalizedFormat === '11' || normalizedFormat === '1:1') {\r\n return ad.image_square || null\r\n } else if (normalizedFormat === '916' || normalizedFormat === '9:16') {\r\n return ad.image_portrait || null\r\n } else if (normalizedFormat === '169' || normalizedFormat === '16:9') {\r\n return ad.image_landscape || null\r\n }\r\n \r\n return null\r\n }\r\n\r\n // Fetch available ads\r\n const fetchAd = useCallback(async () => {\r\n try {\r\n setLoading(true)\r\n setError(null)\r\n\r\n const response = await fetch(`${api_url}/api/ads/sdk`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n creator_code,\r\n formats,\r\n }),\r\n })\r\n\r\n if (!response.ok) {\r\n throw new Error('Failed to fetch ads')\r\n }\r\n\r\n const data = await response.json()\r\n \r\n if (data.ad) {\r\n setAd(data.ad)\r\n } else {\r\n setAd(null)\r\n }\r\n } catch (err) {\r\n console.error('Error fetching ad:', err)\r\n setError(err instanceof Error ? err.message : 'Failed to load ad')\r\n setAd(null)\r\n } finally {\r\n setLoading(false)\r\n }\r\n }, [creator_code, formats, api_url])\r\n\r\n // Track ad click\r\n const handleClick = useCallback(async () => {\r\n if (!ad || !user_wallet_address) return\r\n\r\n try {\r\n // Track click on backend\r\n const response = await fetch(`${api_url}/api/ads/click`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n ad_id: ad.id,\r\n creator_code,\r\n user_wallet_address: user_wallet_address.toLowerCase(),\r\n }),\r\n })\r\n\r\n if (response.ok) {\r\n // Call optional click handler\r\n if (onAdClick) {\r\n onAdClick(ad.id, ad.destination_url)\r\n }\r\n\r\n // Open destination URL\r\n window.open(ad.destination_url, '_blank', 'noopener,noreferrer')\r\n\r\n // Fetch new ad after click\r\n await fetchAd()\r\n }\r\n } catch (err) {\r\n console.error('Error tracking click:', err)\r\n // Still open the URL even if tracking fails\r\n window.open(ad.destination_url, '_blank', 'noopener,noreferrer')\r\n }\r\n }, [ad, creator_code, user_wallet_address, api_url, onAdClick, fetchAd])\r\n\r\n // Find best matching image for formats\r\n const getBestImage = (ad: Ad): string | null => {\r\n for (const format of formats) {\r\n const imageUrl = getImageUrl(ad, format)\r\n if (imageUrl) return imageUrl\r\n }\r\n // Fallback to any available image\r\n return ad.image_square || ad.image_portrait || ad.image_landscape || null\r\n }\r\n\r\n useEffect(() => {\r\n if (creator_code && formats.length > 0) {\r\n fetchAd()\r\n } else {\r\n setLoading(false)\r\n }\r\n }, [creator_code, formats, fetchAd])\r\n\r\n if (loading) {\r\n return (\r\n <div style={{\r\n display: 'flex',\r\n width: '100%',\r\n minHeight: '100px',\r\n backgroundColor: '#f3f4f6',\r\n borderRadius: '8px',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n }}>\r\n <span style={{ color: '#6b7280' }}>Loading ad...</span>\r\n </div>\r\n )\r\n }\r\n\r\n if (error) {\r\n return (\r\n <div style={{\r\n display: 'flex',\r\n width: '100%',\r\n minHeight: '100px',\r\n backgroundColor: '#fee2e2',\r\n borderRadius: '8px',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n padding: '12px',\r\n }}>\r\n <span style={{ color: '#dc2626', fontSize: '14px' }}>{error}</span>\r\n </div>\r\n )\r\n }\r\n\r\n if (!ad) {\r\n return null // Don't render anything if no ad available\r\n }\r\n\r\n const imageUrl = getBestImage(ad)\r\n\r\n if (!imageUrl) {\r\n return null\r\n }\r\n\r\n return (\r\n <div\r\n style={{\r\n display: 'inline-block',\r\n width: '100%',\r\n cursor: 'pointer',\r\n borderRadius: '8px',\r\n overflow: 'hidden',\r\n transition: 'transform 0.2s, box-shadow 0.2s',\r\n }}\r\n onClick={handleClick}\r\n onMouseEnter={(e) => {\r\n e.currentTarget.style.transform = 'scale(1.02)'\r\n e.currentTarget.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)'\r\n }}\r\n onMouseLeave={(e) => {\r\n e.currentTarget.style.transform = 'scale(1)'\r\n e.currentTarget.style.boxShadow = 'none'\r\n }}\r\n >\r\n <img\r\n src={imageUrl}\r\n alt={ad.title}\r\n style={{\r\n width: '100%',\r\n height: 'auto',\r\n display: 'block',\r\n }}\r\n loading=\"lazy\"\r\n />\r\n </div>\r\n )\r\n}\r\n\r\nexport default AniAds\r\n\r\n"],"mappings":"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","names":["ad","imageUrl"]}
1
+ {"version":3,"sources":["../src/index.tsx"],"sourcesContent":["import React, { useEffect, useState, useCallback, useMemo } from 'react'\r\n\r\nexport interface AniAdsProps {\r\n creator_code: string\r\n user_wallet_address: string\r\n ad_format: string[] | string // Can be array like [\"16:9\", \"9:16\"] or string like \"16:9,9:16\"\r\n api_url?: string // Optional API URL, defaults to production\r\n onAdClick?: (adId: string, destinationUrl: string) => void // Optional click handler\r\n}\r\n\r\ninterface Ad {\r\n id: string\r\n title: string\r\n destination_url: string\r\n image_square: string\r\n image_portrait: string\r\n image_landscape: string\r\n status: 'active' | 'paused' | 'deleting'\r\n remaining_budget: number\r\n}\r\n\r\nexport const AniAds: React.FC<AniAdsProps> = ({\r\n creator_code,\r\n user_wallet_address,\r\n ad_format,\r\n api_url = 'https://ani-ads.vercel.app', // Replace with your Ani Ads API domain\r\n onAdClick,\r\n}) => {\r\n const [ad, setAd] = useState<Ad | null>(null)\r\n const [loading, setLoading] = useState(true)\r\n const [error, setError] = useState<string | null>(null)\r\n\r\n // Parse ad format - memoized to prevent unnecessary re-renders\r\n const formats = useMemo(() => {\r\n return Array.isArray(ad_format) \r\n ? ad_format \r\n : typeof ad_format === 'string' \r\n ? ad_format.split(',').map(f => f.trim())\r\n : []\r\n }, [ad_format])\r\n\r\n // Map format strings to image keys\r\n const getImageUrl = (ad: Ad, format: string): string | null => {\r\n const normalizedFormat = format.toLowerCase().replace(/[:\\s]/g, '')\r\n \r\n if (normalizedFormat === '11' || normalizedFormat === '1:1') {\r\n return ad.image_square || null\r\n } else if (normalizedFormat === '916' || normalizedFormat === '9:16') {\r\n return ad.image_portrait || null\r\n } else if (normalizedFormat === '169' || normalizedFormat === '16:9') {\r\n return ad.image_landscape || null\r\n }\r\n \r\n return null\r\n }\r\n\r\n // Fetch available ads\r\n const fetchAd = useCallback(async () => {\r\n try {\r\n setLoading(true)\r\n setError(null)\r\n\r\n const response = await fetch(`${api_url}/api/ads/sdk`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n creator_code,\r\n formats,\r\n }),\r\n })\r\n\r\n if (!response.ok) {\r\n throw new Error('Failed to fetch ads')\r\n }\r\n\r\n const data = await response.json()\r\n \r\n if (data.ad) {\r\n setAd(data.ad)\r\n } else {\r\n setAd(null)\r\n }\r\n } catch (err) {\r\n console.error('Error fetching ad:', err)\r\n setError(err instanceof Error ? err.message : 'Failed to load ad')\r\n setAd(null)\r\n } finally {\r\n setLoading(false)\r\n }\r\n }, [creator_code, formats, api_url]) // formats is memoized, so this is safe\r\n\r\n // Track ad click\r\n const handleClick = useCallback(async () => {\r\n if (!ad || !user_wallet_address) return\r\n\r\n try {\r\n // Track click on backend\r\n const response = await fetch(`${api_url}/api/ads/click`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n ad_id: ad.id,\r\n creator_code,\r\n user_wallet_address: user_wallet_address.toLowerCase(),\r\n }),\r\n })\r\n\r\n if (response.ok) {\r\n // Call optional click handler\r\n if (onAdClick) {\r\n onAdClick(ad.id, ad.destination_url)\r\n }\r\n\r\n // Open destination URL\r\n window.open(ad.destination_url, '_blank', 'noopener,noreferrer')\r\n\r\n // Don't fetch new ad after click - keep showing the same ad until page reload\r\n }\r\n } catch (err) {\r\n console.error('Error tracking click:', err)\r\n // Still open the URL even if tracking fails\r\n window.open(ad.destination_url, '_blank', 'noopener,noreferrer')\r\n }\r\n }, [ad, creator_code, user_wallet_address, api_url, onAdClick, fetchAd])\r\n\r\n // Find best matching image for formats\r\n const getBestImage = (ad: Ad): string | null => {\r\n for (const format of formats) {\r\n const imageUrl = getImageUrl(ad, format)\r\n if (imageUrl) return imageUrl\r\n }\r\n // Fallback to any available image\r\n return ad.image_square || ad.image_portrait || ad.image_landscape || null\r\n }\r\n\r\n // Fetch ad only once when component mounts or when creator_code/ad_format changes\r\n useEffect(() => {\r\n let cancelled = false\r\n \r\n const loadAd = async () => {\r\n if (!creator_code || formats.length === 0) {\r\n setLoading(false)\r\n return\r\n }\r\n\r\n try {\r\n setLoading(true)\r\n setError(null)\r\n\r\n const response = await fetch(`${api_url}/api/ads/sdk`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n creator_code,\r\n formats,\r\n }),\r\n })\r\n\r\n if (cancelled) return\r\n\r\n if (!response.ok) {\r\n throw new Error('Failed to fetch ads')\r\n }\r\n\r\n const data = await response.json()\r\n \r\n if (cancelled) return\r\n \r\n if (data.ad) {\r\n setAd(data.ad)\r\n } else {\r\n setAd(null)\r\n }\r\n } catch (err) {\r\n if (cancelled) return\r\n console.error('Error fetching ad:', err)\r\n setError(err instanceof Error ? err.message : 'Failed to load ad')\r\n setAd(null)\r\n } finally {\r\n if (!cancelled) {\r\n setLoading(false)\r\n }\r\n }\r\n }\r\n\r\n loadAd()\r\n\r\n return () => {\r\n cancelled = true\r\n }\r\n }, [creator_code, ad_format, api_url]) // Only depend on props, not derived values\r\n\r\n if (loading) {\r\n return (\r\n <div style={{\r\n display: 'flex',\r\n width: '100%',\r\n minHeight: '100px',\r\n backgroundColor: '#f3f4f6',\r\n borderRadius: '8px',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n }}>\r\n <span style={{ color: '#6b7280' }}>Loading ad...</span>\r\n </div>\r\n )\r\n }\r\n\r\n if (error) {\r\n return (\r\n <div style={{\r\n display: 'flex',\r\n width: '100%',\r\n minHeight: '100px',\r\n backgroundColor: '#fee2e2',\r\n borderRadius: '8px',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n padding: '12px',\r\n }}>\r\n <span style={{ color: '#dc2626', fontSize: '14px' }}>{error}</span>\r\n </div>\r\n )\r\n }\r\n\r\n if (!ad) {\r\n return null // Don't render anything if no ad available\r\n }\r\n\r\n const imageUrl = getBestImage(ad)\r\n\r\n if (!imageUrl) {\r\n return null\r\n }\r\n\r\n return (\r\n <div\r\n style={{\r\n display: 'inline-block',\r\n width: '100%',\r\n cursor: 'pointer',\r\n borderRadius: '8px',\r\n overflow: 'hidden',\r\n transition: 'transform 0.2s, box-shadow 0.2s',\r\n }}\r\n onClick={handleClick}\r\n onMouseEnter={(e) => {\r\n e.currentTarget.style.transform = 'scale(1.02)'\r\n e.currentTarget.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)'\r\n }}\r\n onMouseLeave={(e) => {\r\n e.currentTarget.style.transform = 'scale(1)'\r\n e.currentTarget.style.boxShadow = 'none'\r\n }}\r\n >\r\n <img\r\n src={imageUrl}\r\n alt={ad.title}\r\n style={{\r\n width: '100%',\r\n height: 'auto',\r\n display: 'block',\r\n }}\r\n loading=\"lazy\"\r\n />\r\n </div>\r\n )\r\n}\r\n\r\nexport default AniAds\r\n\r\n"],"mappings":"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","names":["ad","imageUrl"]}
package/dist/index.mjs CHANGED
@@ -1,18 +1,20 @@
1
1
  // src/index.tsx
2
- import { useEffect, useState, useCallback } from "react";
2
+ import { useEffect, useState, useCallback, useMemo } from "react";
3
3
  import { jsx } from "react/jsx-runtime";
4
4
  var AniAds = ({
5
5
  creator_code,
6
6
  user_wallet_address,
7
7
  ad_format,
8
- api_url = "https://your-domain.com",
8
+ api_url = "https://ani-ads.vercel.app",
9
9
  // Replace with your Ani Ads API domain
10
10
  onAdClick
11
11
  }) => {
12
12
  const [ad, setAd] = useState(null);
13
13
  const [loading, setLoading] = useState(true);
14
14
  const [error, setError] = useState(null);
15
- const formats = Array.isArray(ad_format) ? ad_format : typeof ad_format === "string" ? ad_format.split(",").map((f) => f.trim()) : [];
15
+ const formats = useMemo(() => {
16
+ return Array.isArray(ad_format) ? ad_format : typeof ad_format === "string" ? ad_format.split(",").map((f) => f.trim()) : [];
17
+ }, [ad_format]);
16
18
  const getImageUrl = (ad2, format) => {
17
19
  const normalizedFormat = format.toLowerCase().replace(/[:\s]/g, "");
18
20
  if (normalizedFormat === "11" || normalizedFormat === "1:1") {
@@ -74,7 +76,6 @@ var AniAds = ({
74
76
  onAdClick(ad.id, ad.destination_url);
75
77
  }
76
78
  window.open(ad.destination_url, "_blank", "noopener,noreferrer");
77
- await fetchAd();
78
79
  }
79
80
  } catch (err) {
80
81
  console.error("Error tracking click:", err);
@@ -89,12 +90,52 @@ var AniAds = ({
89
90
  return ad2.image_square || ad2.image_portrait || ad2.image_landscape || null;
90
91
  };
91
92
  useEffect(() => {
92
- if (creator_code && formats.length > 0) {
93
- fetchAd();
94
- } else {
95
- setLoading(false);
96
- }
97
- }, [creator_code, formats, fetchAd]);
93
+ let cancelled = false;
94
+ const loadAd = async () => {
95
+ if (!creator_code || formats.length === 0) {
96
+ setLoading(false);
97
+ return;
98
+ }
99
+ try {
100
+ setLoading(true);
101
+ setError(null);
102
+ const response = await fetch(`${api_url}/api/ads/sdk`, {
103
+ method: "POST",
104
+ headers: {
105
+ "Content-Type": "application/json"
106
+ },
107
+ body: JSON.stringify({
108
+ creator_code,
109
+ formats
110
+ })
111
+ });
112
+ if (cancelled) return;
113
+ if (!response.ok) {
114
+ throw new Error("Failed to fetch ads");
115
+ }
116
+ const data = await response.json();
117
+ if (cancelled) return;
118
+ if (data.ad) {
119
+ setAd(data.ad);
120
+ } else {
121
+ setAd(null);
122
+ }
123
+ } catch (err) {
124
+ if (cancelled) return;
125
+ console.error("Error fetching ad:", err);
126
+ setError(err instanceof Error ? err.message : "Failed to load ad");
127
+ setAd(null);
128
+ } finally {
129
+ if (!cancelled) {
130
+ setLoading(false);
131
+ }
132
+ }
133
+ };
134
+ loadAd();
135
+ return () => {
136
+ cancelled = true;
137
+ };
138
+ }, [creator_code, ad_format, api_url]);
98
139
  if (loading) {
99
140
  return /* @__PURE__ */ jsx("div", { style: {
100
141
  display: "flex",
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.tsx"],"sourcesContent":["import React, { useEffect, useState, useCallback } from 'react'\r\n\r\nexport interface AniAdsProps {\r\n creator_code: string\r\n user_wallet_address: string\r\n ad_format: string[] | string // Can be array like [\"16:9\", \"9:16\"] or string like \"16:9,9:16\"\r\n api_url?: string // Optional API URL, defaults to production\r\n onAdClick?: (adId: string, destinationUrl: string) => void // Optional click handler\r\n}\r\n\r\ninterface Ad {\r\n id: string\r\n title: string\r\n destination_url: string\r\n image_square: string\r\n image_portrait: string\r\n image_landscape: string\r\n status: 'active' | 'paused' | 'deleting'\r\n remaining_budget: number\r\n}\r\n\r\nexport const AniAds: React.FC<AniAdsProps> = ({\r\n creator_code,\r\n user_wallet_address,\r\n ad_format,\r\n api_url = 'https://your-domain.com', // Replace with your Ani Ads API domain\r\n onAdClick,\r\n}) => {\r\n const [ad, setAd] = useState<Ad | null>(null)\r\n const [loading, setLoading] = useState(true)\r\n const [error, setError] = useState<string | null>(null)\r\n\r\n // Parse ad format\r\n const formats = Array.isArray(ad_format) \r\n ? ad_format \r\n : typeof ad_format === 'string' \r\n ? ad_format.split(',').map(f => f.trim())\r\n : []\r\n\r\n // Map format strings to image keys\r\n const getImageUrl = (ad: Ad, format: string): string | null => {\r\n const normalizedFormat = format.toLowerCase().replace(/[:\\s]/g, '')\r\n \r\n if (normalizedFormat === '11' || normalizedFormat === '1:1') {\r\n return ad.image_square || null\r\n } else if (normalizedFormat === '916' || normalizedFormat === '9:16') {\r\n return ad.image_portrait || null\r\n } else if (normalizedFormat === '169' || normalizedFormat === '16:9') {\r\n return ad.image_landscape || null\r\n }\r\n \r\n return null\r\n }\r\n\r\n // Fetch available ads\r\n const fetchAd = useCallback(async () => {\r\n try {\r\n setLoading(true)\r\n setError(null)\r\n\r\n const response = await fetch(`${api_url}/api/ads/sdk`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n creator_code,\r\n formats,\r\n }),\r\n })\r\n\r\n if (!response.ok) {\r\n throw new Error('Failed to fetch ads')\r\n }\r\n\r\n const data = await response.json()\r\n \r\n if (data.ad) {\r\n setAd(data.ad)\r\n } else {\r\n setAd(null)\r\n }\r\n } catch (err) {\r\n console.error('Error fetching ad:', err)\r\n setError(err instanceof Error ? err.message : 'Failed to load ad')\r\n setAd(null)\r\n } finally {\r\n setLoading(false)\r\n }\r\n }, [creator_code, formats, api_url])\r\n\r\n // Track ad click\r\n const handleClick = useCallback(async () => {\r\n if (!ad || !user_wallet_address) return\r\n\r\n try {\r\n // Track click on backend\r\n const response = await fetch(`${api_url}/api/ads/click`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n ad_id: ad.id,\r\n creator_code,\r\n user_wallet_address: user_wallet_address.toLowerCase(),\r\n }),\r\n })\r\n\r\n if (response.ok) {\r\n // Call optional click handler\r\n if (onAdClick) {\r\n onAdClick(ad.id, ad.destination_url)\r\n }\r\n\r\n // Open destination URL\r\n window.open(ad.destination_url, '_blank', 'noopener,noreferrer')\r\n\r\n // Fetch new ad after click\r\n await fetchAd()\r\n }\r\n } catch (err) {\r\n console.error('Error tracking click:', err)\r\n // Still open the URL even if tracking fails\r\n window.open(ad.destination_url, '_blank', 'noopener,noreferrer')\r\n }\r\n }, [ad, creator_code, user_wallet_address, api_url, onAdClick, fetchAd])\r\n\r\n // Find best matching image for formats\r\n const getBestImage = (ad: Ad): string | null => {\r\n for (const format of formats) {\r\n const imageUrl = getImageUrl(ad, format)\r\n if (imageUrl) return imageUrl\r\n }\r\n // Fallback to any available image\r\n return ad.image_square || ad.image_portrait || ad.image_landscape || null\r\n }\r\n\r\n useEffect(() => {\r\n if (creator_code && formats.length > 0) {\r\n fetchAd()\r\n } else {\r\n setLoading(false)\r\n }\r\n }, [creator_code, formats, fetchAd])\r\n\r\n if (loading) {\r\n return (\r\n <div style={{\r\n display: 'flex',\r\n width: '100%',\r\n minHeight: '100px',\r\n backgroundColor: '#f3f4f6',\r\n borderRadius: '8px',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n }}>\r\n <span style={{ color: '#6b7280' }}>Loading ad...</span>\r\n </div>\r\n )\r\n }\r\n\r\n if (error) {\r\n return (\r\n <div style={{\r\n display: 'flex',\r\n width: '100%',\r\n minHeight: '100px',\r\n backgroundColor: '#fee2e2',\r\n borderRadius: '8px',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n padding: '12px',\r\n }}>\r\n <span style={{ color: '#dc2626', fontSize: '14px' }}>{error}</span>\r\n </div>\r\n )\r\n }\r\n\r\n if (!ad) {\r\n return null // Don't render anything if no ad available\r\n }\r\n\r\n const imageUrl = getBestImage(ad)\r\n\r\n if (!imageUrl) {\r\n return null\r\n }\r\n\r\n return (\r\n <div\r\n style={{\r\n display: 'inline-block',\r\n width: '100%',\r\n cursor: 'pointer',\r\n borderRadius: '8px',\r\n overflow: 'hidden',\r\n transition: 'transform 0.2s, box-shadow 0.2s',\r\n }}\r\n onClick={handleClick}\r\n onMouseEnter={(e) => {\r\n e.currentTarget.style.transform = 'scale(1.02)'\r\n e.currentTarget.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)'\r\n }}\r\n onMouseLeave={(e) => {\r\n e.currentTarget.style.transform = 'scale(1)'\r\n e.currentTarget.style.boxShadow = 'none'\r\n }}\r\n >\r\n <img\r\n src={imageUrl}\r\n alt={ad.title}\r\n style={{\r\n width: '100%',\r\n height: 'auto',\r\n display: 'block',\r\n }}\r\n loading=\"lazy\"\r\n />\r\n </div>\r\n )\r\n}\r\n\r\nexport default AniAds\r\n\r\n"],"mappings":";AAAA,SAAgB,WAAW,UAAU,mBAAmB;AA6JhD;AAxID,IAAM,SAAgC,CAAC;AAAA,EAC5C;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA;AAAA,EACV;AACF,MAAM;AACJ,QAAM,CAAC,IAAI,KAAK,IAAI,SAAoB,IAAI;AAC5C,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,IAAI;AAGtD,QAAM,UAAU,MAAM,QAAQ,SAAS,IACnC,YACA,OAAO,cAAc,WACrB,UAAU,MAAM,GAAG,EAAE,IAAI,OAAK,EAAE,KAAK,CAAC,IACtC,CAAC;AAGL,QAAM,cAAc,CAACA,KAAQ,WAAkC;AAC7D,UAAM,mBAAmB,OAAO,YAAY,EAAE,QAAQ,UAAU,EAAE;AAElE,QAAI,qBAAqB,QAAQ,qBAAqB,OAAO;AAC3D,aAAOA,IAAG,gBAAgB;AAAA,IAC5B,WAAW,qBAAqB,SAAS,qBAAqB,QAAQ;AACpE,aAAOA,IAAG,kBAAkB;AAAA,IAC9B,WAAW,qBAAqB,SAAS,qBAAqB,QAAQ;AACpE,aAAOA,IAAG,mBAAmB;AAAA,IAC/B;AAEA,WAAO;AAAA,EACT;AAGA,QAAM,UAAU,YAAY,YAAY;AACtC,QAAI;AACF,iBAAW,IAAI;AACf,eAAS,IAAI;AAEb,YAAM,WAAW,MAAM,MAAM,GAAG,OAAO,gBAAgB;AAAA,QACrD,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,QAClB;AAAA,QACA,MAAM,KAAK,UAAU;AAAA,UACnB;AAAA,UACA;AAAA,QACF,CAAC;AAAA,MACH,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,IAAI,MAAM,qBAAqB;AAAA,MACvC;AAEA,YAAM,OAAO,MAAM,SAAS,KAAK;AAEjC,UAAI,KAAK,IAAI;AACX,cAAM,KAAK,EAAE;AAAA,MACf,OAAO;AACL,cAAM,IAAI;AAAA,MACZ;AAAA,IACF,SAAS,KAAK;AACZ,cAAQ,MAAM,sBAAsB,GAAG;AACvC,eAAS,eAAe,QAAQ,IAAI,UAAU,mBAAmB;AACjE,YAAM,IAAI;AAAA,IACZ,UAAE;AACA,iBAAW,KAAK;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,cAAc,SAAS,OAAO,CAAC;AAGnC,QAAM,cAAc,YAAY,YAAY;AAC1C,QAAI,CAAC,MAAM,CAAC,oBAAqB;AAEjC,QAAI;AAEF,YAAM,WAAW,MAAM,MAAM,GAAG,OAAO,kBAAkB;AAAA,QACvD,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,QAClB;AAAA,QACA,MAAM,KAAK,UAAU;AAAA,UACnB,OAAO,GAAG;AAAA,UACV;AAAA,UACA,qBAAqB,oBAAoB,YAAY;AAAA,QACvD,CAAC;AAAA,MACH,CAAC;AAED,UAAI,SAAS,IAAI;AAEf,YAAI,WAAW;AACb,oBAAU,GAAG,IAAI,GAAG,eAAe;AAAA,QACrC;AAGA,eAAO,KAAK,GAAG,iBAAiB,UAAU,qBAAqB;AAG/D,cAAM,QAAQ;AAAA,MAChB;AAAA,IACF,SAAS,KAAK;AACZ,cAAQ,MAAM,yBAAyB,GAAG;AAE1C,aAAO,KAAK,GAAG,iBAAiB,UAAU,qBAAqB;AAAA,IACjE;AAAA,EACF,GAAG,CAAC,IAAI,cAAc,qBAAqB,SAAS,WAAW,OAAO,CAAC;AAGvE,QAAM,eAAe,CAACA,QAA0B;AAC9C,eAAW,UAAU,SAAS;AAC5B,YAAMC,YAAW,YAAYD,KAAI,MAAM;AACvC,UAAIC,UAAU,QAAOA;AAAA,IACvB;AAEA,WAAOD,IAAG,gBAAgBA,IAAG,kBAAkBA,IAAG,mBAAmB;AAAA,EACvE;AAEA,YAAU,MAAM;AACd,QAAI,gBAAgB,QAAQ,SAAS,GAAG;AACtC,cAAQ;AAAA,IACV,OAAO;AACL,iBAAW,KAAK;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,cAAc,SAAS,OAAO,CAAC;AAEnC,MAAI,SAAS;AACX,WACE,oBAAC,SAAI,OAAO;AAAA,MACV,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,MACX,iBAAiB;AAAA,MACjB,cAAc;AAAA,MACd,YAAY;AAAA,MACZ,gBAAgB;AAAA,IAClB,GACE,8BAAC,UAAK,OAAO,EAAE,OAAO,UAAU,GAAG,2BAAa,GAClD;AAAA,EAEJ;AAEA,MAAI,OAAO;AACT,WACE,oBAAC,SAAI,OAAO;AAAA,MACV,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,MACX,iBAAiB;AAAA,MACjB,cAAc;AAAA,MACd,YAAY;AAAA,MACZ,gBAAgB;AAAA,MAChB,SAAS;AAAA,IACX,GACE,8BAAC,UAAK,OAAO,EAAE,OAAO,WAAW,UAAU,OAAO,GAAI,iBAAM,GAC9D;AAAA,EAEJ;AAEA,MAAI,CAAC,IAAI;AACP,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,aAAa,EAAE;AAEhC,MAAI,CAAC,UAAU;AACb,WAAO;AAAA,EACT;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,cAAc;AAAA,QACd,UAAU;AAAA,QACV,YAAY;AAAA,MACd;AAAA,MACA,SAAS;AAAA,MACT,cAAc,CAAC,MAAM;AACnB,UAAE,cAAc,MAAM,YAAY;AAClC,UAAE,cAAc,MAAM,YAAY;AAAA,MACpC;AAAA,MACA,cAAc,CAAC,MAAM;AACnB,UAAE,cAAc,MAAM,YAAY;AAClC,UAAE,cAAc,MAAM,YAAY;AAAA,MACpC;AAAA,MAEA;AAAA,QAAC;AAAA;AAAA,UACC,KAAK;AAAA,UACL,KAAK,GAAG;AAAA,UACR,OAAO;AAAA,YACL,OAAO;AAAA,YACP,QAAQ;AAAA,YACR,SAAS;AAAA,UACX;AAAA,UACA,SAAQ;AAAA;AAAA,MACV;AAAA;AAAA,EACF;AAEJ;AAEA,IAAO,gBAAQ;","names":["ad","imageUrl"]}
1
+ {"version":3,"sources":["../src/index.tsx"],"sourcesContent":["import React, { useEffect, useState, useCallback, useMemo } from 'react'\r\n\r\nexport interface AniAdsProps {\r\n creator_code: string\r\n user_wallet_address: string\r\n ad_format: string[] | string // Can be array like [\"16:9\", \"9:16\"] or string like \"16:9,9:16\"\r\n api_url?: string // Optional API URL, defaults to production\r\n onAdClick?: (adId: string, destinationUrl: string) => void // Optional click handler\r\n}\r\n\r\ninterface Ad {\r\n id: string\r\n title: string\r\n destination_url: string\r\n image_square: string\r\n image_portrait: string\r\n image_landscape: string\r\n status: 'active' | 'paused' | 'deleting'\r\n remaining_budget: number\r\n}\r\n\r\nexport const AniAds: React.FC<AniAdsProps> = ({\r\n creator_code,\r\n user_wallet_address,\r\n ad_format,\r\n api_url = 'https://ani-ads.vercel.app', // Replace with your Ani Ads API domain\r\n onAdClick,\r\n}) => {\r\n const [ad, setAd] = useState<Ad | null>(null)\r\n const [loading, setLoading] = useState(true)\r\n const [error, setError] = useState<string | null>(null)\r\n\r\n // Parse ad format - memoized to prevent unnecessary re-renders\r\n const formats = useMemo(() => {\r\n return Array.isArray(ad_format) \r\n ? ad_format \r\n : typeof ad_format === 'string' \r\n ? ad_format.split(',').map(f => f.trim())\r\n : []\r\n }, [ad_format])\r\n\r\n // Map format strings to image keys\r\n const getImageUrl = (ad: Ad, format: string): string | null => {\r\n const normalizedFormat = format.toLowerCase().replace(/[:\\s]/g, '')\r\n \r\n if (normalizedFormat === '11' || normalizedFormat === '1:1') {\r\n return ad.image_square || null\r\n } else if (normalizedFormat === '916' || normalizedFormat === '9:16') {\r\n return ad.image_portrait || null\r\n } else if (normalizedFormat === '169' || normalizedFormat === '16:9') {\r\n return ad.image_landscape || null\r\n }\r\n \r\n return null\r\n }\r\n\r\n // Fetch available ads\r\n const fetchAd = useCallback(async () => {\r\n try {\r\n setLoading(true)\r\n setError(null)\r\n\r\n const response = await fetch(`${api_url}/api/ads/sdk`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n creator_code,\r\n formats,\r\n }),\r\n })\r\n\r\n if (!response.ok) {\r\n throw new Error('Failed to fetch ads')\r\n }\r\n\r\n const data = await response.json()\r\n \r\n if (data.ad) {\r\n setAd(data.ad)\r\n } else {\r\n setAd(null)\r\n }\r\n } catch (err) {\r\n console.error('Error fetching ad:', err)\r\n setError(err instanceof Error ? err.message : 'Failed to load ad')\r\n setAd(null)\r\n } finally {\r\n setLoading(false)\r\n }\r\n }, [creator_code, formats, api_url]) // formats is memoized, so this is safe\r\n\r\n // Track ad click\r\n const handleClick = useCallback(async () => {\r\n if (!ad || !user_wallet_address) return\r\n\r\n try {\r\n // Track click on backend\r\n const response = await fetch(`${api_url}/api/ads/click`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n ad_id: ad.id,\r\n creator_code,\r\n user_wallet_address: user_wallet_address.toLowerCase(),\r\n }),\r\n })\r\n\r\n if (response.ok) {\r\n // Call optional click handler\r\n if (onAdClick) {\r\n onAdClick(ad.id, ad.destination_url)\r\n }\r\n\r\n // Open destination URL\r\n window.open(ad.destination_url, '_blank', 'noopener,noreferrer')\r\n\r\n // Don't fetch new ad after click - keep showing the same ad until page reload\r\n }\r\n } catch (err) {\r\n console.error('Error tracking click:', err)\r\n // Still open the URL even if tracking fails\r\n window.open(ad.destination_url, '_blank', 'noopener,noreferrer')\r\n }\r\n }, [ad, creator_code, user_wallet_address, api_url, onAdClick, fetchAd])\r\n\r\n // Find best matching image for formats\r\n const getBestImage = (ad: Ad): string | null => {\r\n for (const format of formats) {\r\n const imageUrl = getImageUrl(ad, format)\r\n if (imageUrl) return imageUrl\r\n }\r\n // Fallback to any available image\r\n return ad.image_square || ad.image_portrait || ad.image_landscape || null\r\n }\r\n\r\n // Fetch ad only once when component mounts or when creator_code/ad_format changes\r\n useEffect(() => {\r\n let cancelled = false\r\n \r\n const loadAd = async () => {\r\n if (!creator_code || formats.length === 0) {\r\n setLoading(false)\r\n return\r\n }\r\n\r\n try {\r\n setLoading(true)\r\n setError(null)\r\n\r\n const response = await fetch(`${api_url}/api/ads/sdk`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n creator_code,\r\n formats,\r\n }),\r\n })\r\n\r\n if (cancelled) return\r\n\r\n if (!response.ok) {\r\n throw new Error('Failed to fetch ads')\r\n }\r\n\r\n const data = await response.json()\r\n \r\n if (cancelled) return\r\n \r\n if (data.ad) {\r\n setAd(data.ad)\r\n } else {\r\n setAd(null)\r\n }\r\n } catch (err) {\r\n if (cancelled) return\r\n console.error('Error fetching ad:', err)\r\n setError(err instanceof Error ? err.message : 'Failed to load ad')\r\n setAd(null)\r\n } finally {\r\n if (!cancelled) {\r\n setLoading(false)\r\n }\r\n }\r\n }\r\n\r\n loadAd()\r\n\r\n return () => {\r\n cancelled = true\r\n }\r\n }, [creator_code, ad_format, api_url]) // Only depend on props, not derived values\r\n\r\n if (loading) {\r\n return (\r\n <div style={{\r\n display: 'flex',\r\n width: '100%',\r\n minHeight: '100px',\r\n backgroundColor: '#f3f4f6',\r\n borderRadius: '8px',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n }}>\r\n <span style={{ color: '#6b7280' }}>Loading ad...</span>\r\n </div>\r\n )\r\n }\r\n\r\n if (error) {\r\n return (\r\n <div style={{\r\n display: 'flex',\r\n width: '100%',\r\n minHeight: '100px',\r\n backgroundColor: '#fee2e2',\r\n borderRadius: '8px',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n padding: '12px',\r\n }}>\r\n <span style={{ color: '#dc2626', fontSize: '14px' }}>{error}</span>\r\n </div>\r\n )\r\n }\r\n\r\n if (!ad) {\r\n return null // Don't render anything if no ad available\r\n }\r\n\r\n const imageUrl = getBestImage(ad)\r\n\r\n if (!imageUrl) {\r\n return null\r\n }\r\n\r\n return (\r\n <div\r\n style={{\r\n display: 'inline-block',\r\n width: '100%',\r\n cursor: 'pointer',\r\n borderRadius: '8px',\r\n overflow: 'hidden',\r\n transition: 'transform 0.2s, box-shadow 0.2s',\r\n }}\r\n onClick={handleClick}\r\n onMouseEnter={(e) => {\r\n e.currentTarget.style.transform = 'scale(1.02)'\r\n e.currentTarget.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)'\r\n }}\r\n onMouseLeave={(e) => {\r\n e.currentTarget.style.transform = 'scale(1)'\r\n e.currentTarget.style.boxShadow = 'none'\r\n }}\r\n >\r\n <img\r\n src={imageUrl}\r\n alt={ad.title}\r\n style={{\r\n width: '100%',\r\n height: 'auto',\r\n display: 'block',\r\n }}\r\n loading=\"lazy\"\r\n />\r\n </div>\r\n )\r\n}\r\n\r\nexport default AniAds\r\n\r\n"],"mappings":"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","names":["ad","imageUrl"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ani-ads-sdk",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Ani Ads SDK for Mini App Creators to display ads in their apps",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",