ani-ads-sdk 2.0.2 → 2.0.4
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 +43 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +43 -13
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -41,10 +41,26 @@ var AniAds = ({
|
|
|
41
41
|
const getImageUrl = (ad2) => {
|
|
42
42
|
return ad2.image_square || null;
|
|
43
43
|
};
|
|
44
|
-
const
|
|
45
|
-
|
|
44
|
+
const trackClick = (0, import_react.useCallback)(async () => {
|
|
45
|
+
console.log("[AniAds SDK] Click detected", {
|
|
46
|
+
hasAd: !!ad,
|
|
47
|
+
hasWalletAddress: !!user_wallet_address,
|
|
48
|
+
destinationUrl: ad?.destination_url,
|
|
49
|
+
adId: ad?.id
|
|
50
|
+
});
|
|
51
|
+
if (!ad || !user_wallet_address) {
|
|
52
|
+
console.warn("[AniAds SDK] Click tracking skipped - missing ad or wallet address");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
46
55
|
try {
|
|
47
|
-
|
|
56
|
+
console.log("[AniAds SDK] Sending click tracking request", {
|
|
57
|
+
ad_id: ad.id,
|
|
58
|
+
creator_wallet: creator_wallet.toLowerCase(),
|
|
59
|
+
app_name,
|
|
60
|
+
user_wallet_address: user_wallet_address.toLowerCase(),
|
|
61
|
+
api_url: `${normalizedApiUrl}/api/ads/click`
|
|
62
|
+
});
|
|
63
|
+
fetch(`${normalizedApiUrl}/api/ads/click`, {
|
|
48
64
|
method: "POST",
|
|
49
65
|
headers: {
|
|
50
66
|
"Content-Type": "application/json"
|
|
@@ -55,16 +71,20 @@ var AniAds = ({
|
|
|
55
71
|
app_name,
|
|
56
72
|
user_wallet_address: user_wallet_address.toLowerCase()
|
|
57
73
|
})
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
|
|
74
|
+
}).then((response) => {
|
|
75
|
+
console.log("[AniAds SDK] Click tracking response", {
|
|
76
|
+
status: response.status,
|
|
77
|
+
ok: response.ok
|
|
78
|
+
});
|
|
79
|
+
if (response.ok && onAdClick) {
|
|
80
|
+
console.log("[AniAds SDK] Calling onAdClick callback");
|
|
61
81
|
onAdClick(ad.id, ad.destination_url);
|
|
62
82
|
}
|
|
63
|
-
|
|
64
|
-
|
|
83
|
+
}).catch((err) => {
|
|
84
|
+
console.error("[AniAds SDK] Error tracking click:", err);
|
|
85
|
+
});
|
|
65
86
|
} catch (err) {
|
|
66
|
-
console.error("Error
|
|
67
|
-
window.open(ad.destination_url, "_blank", "noopener,noreferrer");
|
|
87
|
+
console.error("[AniAds SDK] Error in trackClick:", err);
|
|
68
88
|
}
|
|
69
89
|
}, [ad, creator_wallet, app_name, user_wallet_address, normalizedApiUrl, onAdClick]);
|
|
70
90
|
const getBestImage = (ad2) => {
|
|
@@ -149,17 +169,27 @@ var AniAds = ({
|
|
|
149
169
|
return null;
|
|
150
170
|
}
|
|
151
171
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
152
|
-
"
|
|
172
|
+
"a",
|
|
153
173
|
{
|
|
174
|
+
href: ad.destination_url,
|
|
175
|
+
target: "_blank",
|
|
176
|
+
rel: "noopener noreferrer",
|
|
177
|
+
onClick: (e) => {
|
|
178
|
+
console.log("[AniAds SDK] Anchor tag onClick fired", {
|
|
179
|
+
destinationUrl: ad.destination_url,
|
|
180
|
+
href: e.currentTarget.href
|
|
181
|
+
});
|
|
182
|
+
trackClick();
|
|
183
|
+
},
|
|
154
184
|
style: {
|
|
155
185
|
display: "inline-block",
|
|
156
186
|
width: "100%",
|
|
157
187
|
cursor: "pointer",
|
|
158
188
|
borderRadius: "8px",
|
|
159
189
|
overflow: "hidden",
|
|
160
|
-
transition: "transform 0.2s, box-shadow 0.2s"
|
|
190
|
+
transition: "transform 0.2s, box-shadow 0.2s",
|
|
191
|
+
textDecoration: "none"
|
|
161
192
|
},
|
|
162
|
-
onClick: handleClick,
|
|
163
193
|
onMouseEnter: (e) => {
|
|
164
194
|
e.currentTarget.style.transform = "scale(1.02)";
|
|
165
195
|
e.currentTarget.style.boxShadow = "0 4px 12px rgba(0,0,0,0.15)";
|
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_wallet: string\r\n app_name: string\r\n user_wallet_address: string\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_wallet,\r\n app_name,\r\n user_wallet_address,\r\n api_url = 'https://ani-ads.vercel.app', // Default API URL\r\n onAdClick,\r\n}) => {\r\n // Normalize API URL - ensure it doesn't have trailing slash for proper path joining\r\n const normalizedApiUrl = api_url.replace(/\\/+$/, '')\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 // Get image URL - always use image_square (380x90 banner format)\r\n const getImageUrl = (ad: Ad): string | null => {\r\n return ad.image_square || null\r\n }\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(`${normalizedApiUrl}/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_wallet: creator_wallet.toLowerCase(),\r\n app_name,\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_wallet, app_name, user_wallet_address, normalizedApiUrl, onAdClick])\r\n\r\n // Get image - always use banner format (380x90)\r\n const getBestImage = (ad: Ad): string | null => {\r\n return getImageUrl(ad)\r\n }\r\n\r\n // Track impression when ad is displayed\r\n const trackImpression = useCallback(async (adId: string) => {\r\n if (!adId || !creator_wallet || !app_name || !user_wallet_address) return\r\n\r\n try {\r\n await fetch(`${normalizedApiUrl}/api/ads/impression`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n ad_id: adId,\r\n creator_wallet: creator_wallet.toLowerCase(),\r\n app_name,\r\n user_wallet_address: user_wallet_address.toLowerCase(),\r\n }),\r\n })\r\n // Don't show errors to user - impression tracking is not critical\r\n } catch (err) {\r\n console.error('Error tracking impression:', err)\r\n // Silently fail - impression tracking should not break the ad display\r\n }\r\n }, [creator_wallet, app_name, user_wallet_address, normalizedApiUrl])\r\n\r\n // Fetch ad only once when component mounts or when creator_wallet/app_name changes\r\n useEffect(() => {\r\n let cancelled = false\r\n \r\n const loadAd = async () => {\r\n if (!creator_wallet || !app_name) {\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(`${normalizedApiUrl}/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_wallet: creator_wallet.toLowerCase(),\r\n app_name,\r\n }),\r\n })\r\n\r\n if (cancelled) return\r\n\r\n if (!response.ok) {\r\n const errorData = await response.json().catch(() => ({}))\r\n throw new Error(errorData.error || `HTTP ${response.status}: 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 setError(null) // Clear any previous errors\r\n \r\n // Track impression when ad is loaded and displayed\r\n trackImpression(data.ad.id)\r\n } else {\r\n // No ad available, but not an error - just don't show anything\r\n setAd(null)\r\n setError(null)\r\n }\r\n } catch (err) {\r\n if (cancelled) return\r\n console.error('Error fetching ad:', err)\r\n const errorMessage = err instanceof Error ? err.message : 'Failed to load ad'\r\n setError(errorMessage)\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_wallet, app_name, normalizedApiUrl, trackImpression]) // Only depend on props, not derived values\r\n\r\n // Don't show anything while loading, on error, or when no ad - only render when ad is ready\r\n if (loading || error || !ad) {\r\n return null\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"],"mappings":"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","names":["ad"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.tsx"],"sourcesContent":["import React, { useEffect, useState, useCallback } from 'react'\r\n\r\nexport interface AniAdsProps {\r\n creator_wallet: string\r\n app_name: string\r\n user_wallet_address: string\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_wallet,\r\n app_name,\r\n user_wallet_address,\r\n api_url = 'https://ani-ads.vercel.app', // Default API URL\r\n onAdClick,\r\n}) => {\r\n // Normalize API URL - ensure it doesn't have trailing slash for proper path joining\r\n const normalizedApiUrl = api_url.replace(/\\/+$/, '')\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 // Get image URL - always use image_square (380x90 banner format)\r\n const getImageUrl = (ad: Ad): string | null => {\r\n return ad.image_square || null\r\n }\r\n\r\n // Track ad click - fire and forget, don't block navigation\r\n const trackClick = useCallback(async () => {\r\n console.log('[AniAds SDK] Click detected', {\r\n hasAd: !!ad,\r\n hasWalletAddress: !!user_wallet_address,\r\n destinationUrl: ad?.destination_url,\r\n adId: ad?.id\r\n })\r\n\r\n if (!ad || !user_wallet_address) {\r\n console.warn('[AniAds SDK] Click tracking skipped - missing ad or wallet address')\r\n return\r\n }\r\n\r\n try {\r\n console.log('[AniAds SDK] Sending click tracking request', {\r\n ad_id: ad.id,\r\n creator_wallet: creator_wallet.toLowerCase(),\r\n app_name,\r\n user_wallet_address: user_wallet_address.toLowerCase(),\r\n api_url: `${normalizedApiUrl}/api/ads/click`\r\n })\r\n\r\n // Track click on backend (fire and forget - don't wait for response)\r\n fetch(`${normalizedApiUrl}/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_wallet: creator_wallet.toLowerCase(),\r\n app_name,\r\n user_wallet_address: user_wallet_address.toLowerCase(),\r\n }),\r\n })\r\n .then((response) => {\r\n console.log('[AniAds SDK] Click tracking response', {\r\n status: response.status,\r\n ok: response.ok\r\n })\r\n if (response.ok && onAdClick) {\r\n console.log('[AniAds SDK] Calling onAdClick callback')\r\n onAdClick(ad.id, ad.destination_url)\r\n }\r\n })\r\n .catch((err) => {\r\n console.error('[AniAds SDK] Error tracking click:', err)\r\n })\r\n } catch (err) {\r\n console.error('[AniAds SDK] Error in trackClick:', err)\r\n }\r\n }, [ad, creator_wallet, app_name, user_wallet_address, normalizedApiUrl, onAdClick])\r\n\r\n // Get image - always use banner format (380x90)\r\n const getBestImage = (ad: Ad): string | null => {\r\n return getImageUrl(ad)\r\n }\r\n\r\n // Track impression when ad is displayed\r\n const trackImpression = useCallback(async (adId: string) => {\r\n if (!adId || !creator_wallet || !app_name || !user_wallet_address) return\r\n\r\n try {\r\n await fetch(`${normalizedApiUrl}/api/ads/impression`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n ad_id: adId,\r\n creator_wallet: creator_wallet.toLowerCase(),\r\n app_name,\r\n user_wallet_address: user_wallet_address.toLowerCase(),\r\n }),\r\n })\r\n // Don't show errors to user - impression tracking is not critical\r\n } catch (err) {\r\n console.error('Error tracking impression:', err)\r\n // Silently fail - impression tracking should not break the ad display\r\n }\r\n }, [creator_wallet, app_name, user_wallet_address, normalizedApiUrl])\r\n\r\n // Fetch ad only once when component mounts or when creator_wallet/app_name changes\r\n useEffect(() => {\r\n let cancelled = false\r\n \r\n const loadAd = async () => {\r\n if (!creator_wallet || !app_name) {\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(`${normalizedApiUrl}/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_wallet: creator_wallet.toLowerCase(),\r\n app_name,\r\n }),\r\n })\r\n\r\n if (cancelled) return\r\n\r\n if (!response.ok) {\r\n const errorData = await response.json().catch(() => ({}))\r\n throw new Error(errorData.error || `HTTP ${response.status}: 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 setError(null) // Clear any previous errors\r\n \r\n // Track impression when ad is loaded and displayed\r\n trackImpression(data.ad.id)\r\n } else {\r\n // No ad available, but not an error - just don't show anything\r\n setAd(null)\r\n setError(null)\r\n }\r\n } catch (err) {\r\n if (cancelled) return\r\n console.error('Error fetching ad:', err)\r\n const errorMessage = err instanceof Error ? err.message : 'Failed to load ad'\r\n setError(errorMessage)\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_wallet, app_name, normalizedApiUrl, trackImpression]) // Only depend on props, not derived values\r\n\r\n // Don't show anything while loading, on error, or when no ad - only render when ad is ready\r\n if (loading || error || !ad) {\r\n return null\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 // Use an anchor tag for webview compatibility\r\n // Let the link navigate normally, track click in parallel\r\n return (\r\n <a\r\n href={ad.destination_url}\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n onClick={(e) => {\r\n console.log('[AniAds SDK] Anchor tag onClick fired', {\r\n destinationUrl: ad.destination_url,\r\n href: e.currentTarget.href\r\n })\r\n trackClick()\r\n // Don't prevent default - let the link navigate normally\r\n }}\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 textDecoration: 'none',\r\n }}\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 </a>\r\n )\r\n}\r\n\r\nexport default AniAds\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAwD;AA0OlD;AArNC,IAAM,SAAgC,CAAC;AAAA,EAC5C;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA;AAAA,EACV;AACF,MAAM;AAEJ,QAAM,mBAAmB,QAAQ,QAAQ,QAAQ,EAAE;AAEnD,QAAM,CAAC,IAAI,KAAK,QAAI,uBAAoB,IAAI;AAC5C,QAAM,CAAC,SAAS,UAAU,QAAI,uBAAS,IAAI;AAC3C,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAwB,IAAI;AAGtD,QAAM,cAAc,CAACA,QAA0B;AAC7C,WAAOA,IAAG,gBAAgB;AAAA,EAC5B;AAGA,QAAM,iBAAa,0BAAY,YAAY;AACzC,YAAQ,IAAI,+BAA+B;AAAA,MACzC,OAAO,CAAC,CAAC;AAAA,MACT,kBAAkB,CAAC,CAAC;AAAA,MACpB,gBAAgB,IAAI;AAAA,MACpB,MAAM,IAAI;AAAA,IACZ,CAAC;AAED,QAAI,CAAC,MAAM,CAAC,qBAAqB;AAC/B,cAAQ,KAAK,oEAAoE;AACjF;AAAA,IACF;AAEA,QAAI;AACF,cAAQ,IAAI,+CAA+C;AAAA,QACzD,OAAO,GAAG;AAAA,QACV,gBAAgB,eAAe,YAAY;AAAA,QAC3C;AAAA,QACA,qBAAqB,oBAAoB,YAAY;AAAA,QACrD,SAAS,GAAG,gBAAgB;AAAA,MAC9B,CAAC;AAGD,YAAM,GAAG,gBAAgB,kBAAkB;AAAA,QACzC,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,QAClB;AAAA,QACA,MAAM,KAAK,UAAU;AAAA,UACnB,OAAO,GAAG;AAAA,UACV,gBAAgB,eAAe,YAAY;AAAA,UAC3C;AAAA,UACA,qBAAqB,oBAAoB,YAAY;AAAA,QACvD,CAAC;AAAA,MACH,CAAC,EACE,KAAK,CAAC,aAAa;AAClB,gBAAQ,IAAI,wCAAwC;AAAA,UAClD,QAAQ,SAAS;AAAA,UACjB,IAAI,SAAS;AAAA,QACf,CAAC;AACD,YAAI,SAAS,MAAM,WAAW;AAC5B,kBAAQ,IAAI,yCAAyC;AACrD,oBAAU,GAAG,IAAI,GAAG,eAAe;AAAA,QACrC;AAAA,MACF,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,gBAAQ,MAAM,sCAAsC,GAAG;AAAA,MACzD,CAAC;AAAA,IACL,SAAS,KAAK;AACZ,cAAQ,MAAM,qCAAqC,GAAG;AAAA,IACxD;AAAA,EACF,GAAG,CAAC,IAAI,gBAAgB,UAAU,qBAAqB,kBAAkB,SAAS,CAAC;AAGnF,QAAM,eAAe,CAACA,QAA0B;AAC9C,WAAO,YAAYA,GAAE;AAAA,EACvB;AAGA,QAAM,sBAAkB,0BAAY,OAAO,SAAiB;AAC1D,QAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC,YAAY,CAAC,oBAAqB;AAEnE,QAAI;AACF,YAAM,MAAM,GAAG,gBAAgB,uBAAuB;AAAA,QACpD,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,QAClB;AAAA,QACA,MAAM,KAAK,UAAU;AAAA,UACnB,OAAO;AAAA,UACP,gBAAgB,eAAe,YAAY;AAAA,UAC3C;AAAA,UACA,qBAAqB,oBAAoB,YAAY;AAAA,QACvD,CAAC;AAAA,MACH,CAAC;AAAA,IAEH,SAAS,KAAK;AACZ,cAAQ,MAAM,8BAA8B,GAAG;AAAA,IAEjD;AAAA,EACF,GAAG,CAAC,gBAAgB,UAAU,qBAAqB,gBAAgB,CAAC;AAGpE,8BAAU,MAAM;AACd,QAAI,YAAY;AAEhB,UAAM,SAAS,YAAY;AACzB,UAAI,CAAC,kBAAkB,CAAC,UAAU;AAChC,mBAAW,KAAK;AAChB;AAAA,MACF;AAEA,UAAI;AACF,mBAAW,IAAI;AACf,iBAAS,IAAI;AAEb,cAAM,WAAW,MAAM,MAAM,GAAG,gBAAgB,gBAAgB;AAAA,UAC9D,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,gBAAgB;AAAA,UAClB;AAAA,UACA,MAAM,KAAK,UAAU;AAAA,YACnB,gBAAgB,eAAe,YAAY;AAAA,YAC3C;AAAA,UACF,CAAC;AAAA,QACH,CAAC;AAED,YAAI,UAAW;AAEf,YAAI,CAAC,SAAS,IAAI;AAChB,gBAAM,YAAY,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACxD,gBAAM,IAAI,MAAM,UAAU,SAAS,QAAQ,SAAS,MAAM,uBAAuB;AAAA,QACnF;AAEA,cAAM,OAAO,MAAM,SAAS,KAAK;AAEjC,YAAI,UAAW;AAEf,YAAI,KAAK,IAAI;AACX,gBAAM,KAAK,EAAE;AACb,mBAAS,IAAI;AAGb,0BAAgB,KAAK,GAAG,EAAE;AAAA,QAC5B,OAAO;AAEL,gBAAM,IAAI;AACV,mBAAS,IAAI;AAAA,QACf;AAAA,MACF,SAAS,KAAK;AACZ,YAAI,UAAW;AACf,gBAAQ,MAAM,sBAAsB,GAAG;AACvC,cAAM,eAAe,eAAe,QAAQ,IAAI,UAAU;AAC1D,iBAAS,YAAY;AACrB,cAAM,IAAI;AAAA,MACZ,UAAE;AACA,YAAI,CAAC,WAAW;AACd,qBAAW,KAAK;AAAA,QAClB;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAEP,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,gBAAgB,UAAU,kBAAkB,eAAe,CAAC;AAGhE,MAAI,WAAW,SAAS,CAAC,IAAI;AAC3B,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,aAAa,EAAE;AAEhC,MAAI,CAAC,UAAU;AACb,WAAO;AAAA,EACT;AAIA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,GAAG;AAAA,MACT,QAAO;AAAA,MACP,KAAI;AAAA,MACJ,SAAS,CAAC,MAAM;AACd,gBAAQ,IAAI,yCAAyC;AAAA,UACnD,gBAAgB,GAAG;AAAA,UACnB,MAAM,EAAE,cAAc;AAAA,QACxB,CAAC;AACD,mBAAW;AAAA,MAEb;AAAA,MACA,OAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,cAAc;AAAA,QACd,UAAU;AAAA,QACV,YAAY;AAAA,QACZ,gBAAgB;AAAA,MAClB;AAAA,MACA,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"]}
|
package/dist/index.mjs
CHANGED
|
@@ -16,10 +16,26 @@ var AniAds = ({
|
|
|
16
16
|
const getImageUrl = (ad2) => {
|
|
17
17
|
return ad2.image_square || null;
|
|
18
18
|
};
|
|
19
|
-
const
|
|
20
|
-
|
|
19
|
+
const trackClick = useCallback(async () => {
|
|
20
|
+
console.log("[AniAds SDK] Click detected", {
|
|
21
|
+
hasAd: !!ad,
|
|
22
|
+
hasWalletAddress: !!user_wallet_address,
|
|
23
|
+
destinationUrl: ad?.destination_url,
|
|
24
|
+
adId: ad?.id
|
|
25
|
+
});
|
|
26
|
+
if (!ad || !user_wallet_address) {
|
|
27
|
+
console.warn("[AniAds SDK] Click tracking skipped - missing ad or wallet address");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
21
30
|
try {
|
|
22
|
-
|
|
31
|
+
console.log("[AniAds SDK] Sending click tracking request", {
|
|
32
|
+
ad_id: ad.id,
|
|
33
|
+
creator_wallet: creator_wallet.toLowerCase(),
|
|
34
|
+
app_name,
|
|
35
|
+
user_wallet_address: user_wallet_address.toLowerCase(),
|
|
36
|
+
api_url: `${normalizedApiUrl}/api/ads/click`
|
|
37
|
+
});
|
|
38
|
+
fetch(`${normalizedApiUrl}/api/ads/click`, {
|
|
23
39
|
method: "POST",
|
|
24
40
|
headers: {
|
|
25
41
|
"Content-Type": "application/json"
|
|
@@ -30,16 +46,20 @@ var AniAds = ({
|
|
|
30
46
|
app_name,
|
|
31
47
|
user_wallet_address: user_wallet_address.toLowerCase()
|
|
32
48
|
})
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
|
|
49
|
+
}).then((response) => {
|
|
50
|
+
console.log("[AniAds SDK] Click tracking response", {
|
|
51
|
+
status: response.status,
|
|
52
|
+
ok: response.ok
|
|
53
|
+
});
|
|
54
|
+
if (response.ok && onAdClick) {
|
|
55
|
+
console.log("[AniAds SDK] Calling onAdClick callback");
|
|
36
56
|
onAdClick(ad.id, ad.destination_url);
|
|
37
57
|
}
|
|
38
|
-
|
|
39
|
-
|
|
58
|
+
}).catch((err) => {
|
|
59
|
+
console.error("[AniAds SDK] Error tracking click:", err);
|
|
60
|
+
});
|
|
40
61
|
} catch (err) {
|
|
41
|
-
console.error("Error
|
|
42
|
-
window.open(ad.destination_url, "_blank", "noopener,noreferrer");
|
|
62
|
+
console.error("[AniAds SDK] Error in trackClick:", err);
|
|
43
63
|
}
|
|
44
64
|
}, [ad, creator_wallet, app_name, user_wallet_address, normalizedApiUrl, onAdClick]);
|
|
45
65
|
const getBestImage = (ad2) => {
|
|
@@ -124,17 +144,27 @@ var AniAds = ({
|
|
|
124
144
|
return null;
|
|
125
145
|
}
|
|
126
146
|
return /* @__PURE__ */ jsx(
|
|
127
|
-
"
|
|
147
|
+
"a",
|
|
128
148
|
{
|
|
149
|
+
href: ad.destination_url,
|
|
150
|
+
target: "_blank",
|
|
151
|
+
rel: "noopener noreferrer",
|
|
152
|
+
onClick: (e) => {
|
|
153
|
+
console.log("[AniAds SDK] Anchor tag onClick fired", {
|
|
154
|
+
destinationUrl: ad.destination_url,
|
|
155
|
+
href: e.currentTarget.href
|
|
156
|
+
});
|
|
157
|
+
trackClick();
|
|
158
|
+
},
|
|
129
159
|
style: {
|
|
130
160
|
display: "inline-block",
|
|
131
161
|
width: "100%",
|
|
132
162
|
cursor: "pointer",
|
|
133
163
|
borderRadius: "8px",
|
|
134
164
|
overflow: "hidden",
|
|
135
|
-
transition: "transform 0.2s, box-shadow 0.2s"
|
|
165
|
+
transition: "transform 0.2s, box-shadow 0.2s",
|
|
166
|
+
textDecoration: "none"
|
|
136
167
|
},
|
|
137
|
-
onClick: handleClick,
|
|
138
168
|
onMouseEnter: (e) => {
|
|
139
169
|
e.currentTarget.style.transform = "scale(1.02)";
|
|
140
170
|
e.currentTarget.style.boxShadow = "0 4px 12px rgba(0,0,0,0.15)";
|
package/dist/index.mjs.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_wallet: string\r\n app_name: string\r\n user_wallet_address: string\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_wallet,\r\n app_name,\r\n user_wallet_address,\r\n api_url = 'https://ani-ads.vercel.app', // Default API URL\r\n onAdClick,\r\n}) => {\r\n // Normalize API URL - ensure it doesn't have trailing slash for proper path joining\r\n const normalizedApiUrl = api_url.replace(/\\/+$/, '')\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 // Get image URL - always use image_square (380x90 banner format)\r\n const getImageUrl = (ad: Ad): string | null => {\r\n return ad.image_square || null\r\n }\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(`${normalizedApiUrl}/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_wallet: creator_wallet.toLowerCase(),\r\n app_name,\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_wallet, app_name, user_wallet_address, normalizedApiUrl, onAdClick])\r\n\r\n // Get image - always use banner format (380x90)\r\n const getBestImage = (ad: Ad): string | null => {\r\n return getImageUrl(ad)\r\n }\r\n\r\n // Track impression when ad is displayed\r\n const trackImpression = useCallback(async (adId: string) => {\r\n if (!adId || !creator_wallet || !app_name || !user_wallet_address) return\r\n\r\n try {\r\n await fetch(`${normalizedApiUrl}/api/ads/impression`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n ad_id: adId,\r\n creator_wallet: creator_wallet.toLowerCase(),\r\n app_name,\r\n user_wallet_address: user_wallet_address.toLowerCase(),\r\n }),\r\n })\r\n // Don't show errors to user - impression tracking is not critical\r\n } catch (err) {\r\n console.error('Error tracking impression:', err)\r\n // Silently fail - impression tracking should not break the ad display\r\n }\r\n }, [creator_wallet, app_name, user_wallet_address, normalizedApiUrl])\r\n\r\n // Fetch ad only once when component mounts or when creator_wallet/app_name changes\r\n useEffect(() => {\r\n let cancelled = false\r\n \r\n const loadAd = async () => {\r\n if (!creator_wallet || !app_name) {\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(`${normalizedApiUrl}/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_wallet: creator_wallet.toLowerCase(),\r\n app_name,\r\n }),\r\n })\r\n\r\n if (cancelled) return\r\n\r\n if (!response.ok) {\r\n const errorData = await response.json().catch(() => ({}))\r\n throw new Error(errorData.error || `HTTP ${response.status}: 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 setError(null) // Clear any previous errors\r\n \r\n // Track impression when ad is loaded and displayed\r\n trackImpression(data.ad.id)\r\n } else {\r\n // No ad available, but not an error - just don't show anything\r\n setAd(null)\r\n setError(null)\r\n }\r\n } catch (err) {\r\n if (cancelled) return\r\n console.error('Error fetching ad:', err)\r\n const errorMessage = err instanceof Error ? err.message : 'Failed to load ad'\r\n setError(errorMessage)\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_wallet, app_name, normalizedApiUrl, trackImpression]) // Only depend on props, not derived values\r\n\r\n // Don't show anything while loading, on error, or when no ad - only render when ad is ready\r\n if (loading || error || !ad) {\r\n return null\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"],"mappings":"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","names":["ad"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.tsx"],"sourcesContent":["import React, { useEffect, useState, useCallback } from 'react'\r\n\r\nexport interface AniAdsProps {\r\n creator_wallet: string\r\n app_name: string\r\n user_wallet_address: string\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_wallet,\r\n app_name,\r\n user_wallet_address,\r\n api_url = 'https://ani-ads.vercel.app', // Default API URL\r\n onAdClick,\r\n}) => {\r\n // Normalize API URL - ensure it doesn't have trailing slash for proper path joining\r\n const normalizedApiUrl = api_url.replace(/\\/+$/, '')\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 // Get image URL - always use image_square (380x90 banner format)\r\n const getImageUrl = (ad: Ad): string | null => {\r\n return ad.image_square || null\r\n }\r\n\r\n // Track ad click - fire and forget, don't block navigation\r\n const trackClick = useCallback(async () => {\r\n console.log('[AniAds SDK] Click detected', {\r\n hasAd: !!ad,\r\n hasWalletAddress: !!user_wallet_address,\r\n destinationUrl: ad?.destination_url,\r\n adId: ad?.id\r\n })\r\n\r\n if (!ad || !user_wallet_address) {\r\n console.warn('[AniAds SDK] Click tracking skipped - missing ad or wallet address')\r\n return\r\n }\r\n\r\n try {\r\n console.log('[AniAds SDK] Sending click tracking request', {\r\n ad_id: ad.id,\r\n creator_wallet: creator_wallet.toLowerCase(),\r\n app_name,\r\n user_wallet_address: user_wallet_address.toLowerCase(),\r\n api_url: `${normalizedApiUrl}/api/ads/click`\r\n })\r\n\r\n // Track click on backend (fire and forget - don't wait for response)\r\n fetch(`${normalizedApiUrl}/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_wallet: creator_wallet.toLowerCase(),\r\n app_name,\r\n user_wallet_address: user_wallet_address.toLowerCase(),\r\n }),\r\n })\r\n .then((response) => {\r\n console.log('[AniAds SDK] Click tracking response', {\r\n status: response.status,\r\n ok: response.ok\r\n })\r\n if (response.ok && onAdClick) {\r\n console.log('[AniAds SDK] Calling onAdClick callback')\r\n onAdClick(ad.id, ad.destination_url)\r\n }\r\n })\r\n .catch((err) => {\r\n console.error('[AniAds SDK] Error tracking click:', err)\r\n })\r\n } catch (err) {\r\n console.error('[AniAds SDK] Error in trackClick:', err)\r\n }\r\n }, [ad, creator_wallet, app_name, user_wallet_address, normalizedApiUrl, onAdClick])\r\n\r\n // Get image - always use banner format (380x90)\r\n const getBestImage = (ad: Ad): string | null => {\r\n return getImageUrl(ad)\r\n }\r\n\r\n // Track impression when ad is displayed\r\n const trackImpression = useCallback(async (adId: string) => {\r\n if (!adId || !creator_wallet || !app_name || !user_wallet_address) return\r\n\r\n try {\r\n await fetch(`${normalizedApiUrl}/api/ads/impression`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n ad_id: adId,\r\n creator_wallet: creator_wallet.toLowerCase(),\r\n app_name,\r\n user_wallet_address: user_wallet_address.toLowerCase(),\r\n }),\r\n })\r\n // Don't show errors to user - impression tracking is not critical\r\n } catch (err) {\r\n console.error('Error tracking impression:', err)\r\n // Silently fail - impression tracking should not break the ad display\r\n }\r\n }, [creator_wallet, app_name, user_wallet_address, normalizedApiUrl])\r\n\r\n // Fetch ad only once when component mounts or when creator_wallet/app_name changes\r\n useEffect(() => {\r\n let cancelled = false\r\n \r\n const loadAd = async () => {\r\n if (!creator_wallet || !app_name) {\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(`${normalizedApiUrl}/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_wallet: creator_wallet.toLowerCase(),\r\n app_name,\r\n }),\r\n })\r\n\r\n if (cancelled) return\r\n\r\n if (!response.ok) {\r\n const errorData = await response.json().catch(() => ({}))\r\n throw new Error(errorData.error || `HTTP ${response.status}: 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 setError(null) // Clear any previous errors\r\n \r\n // Track impression when ad is loaded and displayed\r\n trackImpression(data.ad.id)\r\n } else {\r\n // No ad available, but not an error - just don't show anything\r\n setAd(null)\r\n setError(null)\r\n }\r\n } catch (err) {\r\n if (cancelled) return\r\n console.error('Error fetching ad:', err)\r\n const errorMessage = err instanceof Error ? err.message : 'Failed to load ad'\r\n setError(errorMessage)\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_wallet, app_name, normalizedApiUrl, trackImpression]) // Only depend on props, not derived values\r\n\r\n // Don't show anything while loading, on error, or when no ad - only render when ad is ready\r\n if (loading || error || !ad) {\r\n return null\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 // Use an anchor tag for webview compatibility\r\n // Let the link navigate normally, track click in parallel\r\n return (\r\n <a\r\n href={ad.destination_url}\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n onClick={(e) => {\r\n console.log('[AniAds SDK] Anchor tag onClick fired', {\r\n destinationUrl: ad.destination_url,\r\n href: e.currentTarget.href\r\n })\r\n trackClick()\r\n // Don't prevent default - let the link navigate normally\r\n }}\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 textDecoration: 'none',\r\n }}\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 </a>\r\n )\r\n}\r\n\r\nexport default AniAds\r\n"],"mappings":"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","names":["ad"]}
|