ani-ads-sdk 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +54 -57
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +54 -57
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -2
package/dist/index.d.mts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -29,57 +29,16 @@ var import_jsx_runtime = require("react/jsx-runtime");
|
|
|
29
29
|
var AniAds = ({
|
|
30
30
|
creator_code,
|
|
31
31
|
user_wallet_address,
|
|
32
|
-
|
|
33
|
-
api_url = "https://your-domain.com",
|
|
32
|
+
api_url = "https://ani-ads.vercel.app",
|
|
34
33
|
// Replace with your Ani Ads API domain
|
|
35
34
|
onAdClick
|
|
36
35
|
}) => {
|
|
37
36
|
const [ad, setAd] = (0, import_react.useState)(null);
|
|
38
37
|
const [loading, setLoading] = (0, import_react.useState)(true);
|
|
39
38
|
const [error, setError] = (0, import_react.useState)(null);
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
const normalizedFormat = format.toLowerCase().replace(/[:\s]/g, "");
|
|
43
|
-
if (normalizedFormat === "11" || normalizedFormat === "1:1") {
|
|
44
|
-
return ad2.image_square || null;
|
|
45
|
-
} else if (normalizedFormat === "916" || normalizedFormat === "9:16") {
|
|
46
|
-
return ad2.image_portrait || null;
|
|
47
|
-
} else if (normalizedFormat === "169" || normalizedFormat === "16:9") {
|
|
48
|
-
return ad2.image_landscape || null;
|
|
49
|
-
}
|
|
50
|
-
return null;
|
|
39
|
+
const getImageUrl = (ad2) => {
|
|
40
|
+
return ad2.image_square || null;
|
|
51
41
|
};
|
|
52
|
-
const fetchAd = (0, import_react.useCallback)(async () => {
|
|
53
|
-
try {
|
|
54
|
-
setLoading(true);
|
|
55
|
-
setError(null);
|
|
56
|
-
const response = await fetch(`${api_url}/api/ads/sdk`, {
|
|
57
|
-
method: "POST",
|
|
58
|
-
headers: {
|
|
59
|
-
"Content-Type": "application/json"
|
|
60
|
-
},
|
|
61
|
-
body: JSON.stringify({
|
|
62
|
-
creator_code,
|
|
63
|
-
formats
|
|
64
|
-
})
|
|
65
|
-
});
|
|
66
|
-
if (!response.ok) {
|
|
67
|
-
throw new Error("Failed to fetch ads");
|
|
68
|
-
}
|
|
69
|
-
const data = await response.json();
|
|
70
|
-
if (data.ad) {
|
|
71
|
-
setAd(data.ad);
|
|
72
|
-
} else {
|
|
73
|
-
setAd(null);
|
|
74
|
-
}
|
|
75
|
-
} catch (err) {
|
|
76
|
-
console.error("Error fetching ad:", err);
|
|
77
|
-
setError(err instanceof Error ? err.message : "Failed to load ad");
|
|
78
|
-
setAd(null);
|
|
79
|
-
} finally {
|
|
80
|
-
setLoading(false);
|
|
81
|
-
}
|
|
82
|
-
}, [creator_code, formats, api_url]);
|
|
83
42
|
const handleClick = (0, import_react.useCallback)(async () => {
|
|
84
43
|
if (!ad || !user_wallet_address) return;
|
|
85
44
|
try {
|
|
@@ -99,27 +58,65 @@ var AniAds = ({
|
|
|
99
58
|
onAdClick(ad.id, ad.destination_url);
|
|
100
59
|
}
|
|
101
60
|
window.open(ad.destination_url, "_blank", "noopener,noreferrer");
|
|
102
|
-
await fetchAd();
|
|
103
61
|
}
|
|
104
62
|
} catch (err) {
|
|
105
63
|
console.error("Error tracking click:", err);
|
|
106
64
|
window.open(ad.destination_url, "_blank", "noopener,noreferrer");
|
|
107
65
|
}
|
|
108
|
-
}, [ad, creator_code, user_wallet_address, api_url, onAdClick
|
|
66
|
+
}, [ad, creator_code, user_wallet_address, api_url, onAdClick]);
|
|
109
67
|
const getBestImage = (ad2) => {
|
|
110
|
-
|
|
111
|
-
const imageUrl2 = getImageUrl(ad2, format);
|
|
112
|
-
if (imageUrl2) return imageUrl2;
|
|
113
|
-
}
|
|
114
|
-
return ad2.image_square || ad2.image_portrait || ad2.image_landscape || null;
|
|
68
|
+
return getImageUrl(ad2);
|
|
115
69
|
};
|
|
116
70
|
(0, import_react.useEffect)(() => {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
71
|
+
let cancelled = false;
|
|
72
|
+
const loadAd = async () => {
|
|
73
|
+
if (!creator_code) {
|
|
74
|
+
setLoading(false);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
setLoading(true);
|
|
79
|
+
setError(null);
|
|
80
|
+
const response = await fetch(`${api_url}/api/ads/sdk`, {
|
|
81
|
+
method: "POST",
|
|
82
|
+
headers: {
|
|
83
|
+
"Content-Type": "application/json"
|
|
84
|
+
},
|
|
85
|
+
body: JSON.stringify({
|
|
86
|
+
creator_code
|
|
87
|
+
})
|
|
88
|
+
});
|
|
89
|
+
if (cancelled) return;
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
const errorData = await response.json().catch(() => ({}));
|
|
92
|
+
throw new Error(errorData.error || `HTTP ${response.status}: Failed to fetch ads`);
|
|
93
|
+
}
|
|
94
|
+
const data = await response.json();
|
|
95
|
+
if (cancelled) return;
|
|
96
|
+
if (data.ad) {
|
|
97
|
+
setAd(data.ad);
|
|
98
|
+
setError(null);
|
|
99
|
+
} else {
|
|
100
|
+
setAd(null);
|
|
101
|
+
setError(null);
|
|
102
|
+
}
|
|
103
|
+
} catch (err) {
|
|
104
|
+
if (cancelled) return;
|
|
105
|
+
console.error("Error fetching ad:", err);
|
|
106
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to load ad";
|
|
107
|
+
setError(errorMessage);
|
|
108
|
+
setAd(null);
|
|
109
|
+
} finally {
|
|
110
|
+
if (!cancelled) {
|
|
111
|
+
setLoading(false);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
loadAd();
|
|
116
|
+
return () => {
|
|
117
|
+
cancelled = true;
|
|
118
|
+
};
|
|
119
|
+
}, [creator_code, api_url]);
|
|
123
120
|
if (loading) {
|
|
124
121
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
|
|
125
122
|
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
|
|
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 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 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 // 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(`${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])\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 // 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) {\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 }),\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 } 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_code, 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"]}
|
package/dist/index.mjs
CHANGED
|
@@ -4,57 +4,16 @@ import { jsx } from "react/jsx-runtime";
|
|
|
4
4
|
var AniAds = ({
|
|
5
5
|
creator_code,
|
|
6
6
|
user_wallet_address,
|
|
7
|
-
|
|
8
|
-
api_url = "https://your-domain.com",
|
|
7
|
+
api_url = "https://ani-ads.vercel.app",
|
|
9
8
|
// Replace with your Ani Ads API domain
|
|
10
9
|
onAdClick
|
|
11
10
|
}) => {
|
|
12
11
|
const [ad, setAd] = useState(null);
|
|
13
12
|
const [loading, setLoading] = useState(true);
|
|
14
13
|
const [error, setError] = useState(null);
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
const normalizedFormat = format.toLowerCase().replace(/[:\s]/g, "");
|
|
18
|
-
if (normalizedFormat === "11" || normalizedFormat === "1:1") {
|
|
19
|
-
return ad2.image_square || null;
|
|
20
|
-
} else if (normalizedFormat === "916" || normalizedFormat === "9:16") {
|
|
21
|
-
return ad2.image_portrait || null;
|
|
22
|
-
} else if (normalizedFormat === "169" || normalizedFormat === "16:9") {
|
|
23
|
-
return ad2.image_landscape || null;
|
|
24
|
-
}
|
|
25
|
-
return null;
|
|
14
|
+
const getImageUrl = (ad2) => {
|
|
15
|
+
return ad2.image_square || null;
|
|
26
16
|
};
|
|
27
|
-
const fetchAd = useCallback(async () => {
|
|
28
|
-
try {
|
|
29
|
-
setLoading(true);
|
|
30
|
-
setError(null);
|
|
31
|
-
const response = await fetch(`${api_url}/api/ads/sdk`, {
|
|
32
|
-
method: "POST",
|
|
33
|
-
headers: {
|
|
34
|
-
"Content-Type": "application/json"
|
|
35
|
-
},
|
|
36
|
-
body: JSON.stringify({
|
|
37
|
-
creator_code,
|
|
38
|
-
formats
|
|
39
|
-
})
|
|
40
|
-
});
|
|
41
|
-
if (!response.ok) {
|
|
42
|
-
throw new Error("Failed to fetch ads");
|
|
43
|
-
}
|
|
44
|
-
const data = await response.json();
|
|
45
|
-
if (data.ad) {
|
|
46
|
-
setAd(data.ad);
|
|
47
|
-
} else {
|
|
48
|
-
setAd(null);
|
|
49
|
-
}
|
|
50
|
-
} catch (err) {
|
|
51
|
-
console.error("Error fetching ad:", err);
|
|
52
|
-
setError(err instanceof Error ? err.message : "Failed to load ad");
|
|
53
|
-
setAd(null);
|
|
54
|
-
} finally {
|
|
55
|
-
setLoading(false);
|
|
56
|
-
}
|
|
57
|
-
}, [creator_code, formats, api_url]);
|
|
58
17
|
const handleClick = useCallback(async () => {
|
|
59
18
|
if (!ad || !user_wallet_address) return;
|
|
60
19
|
try {
|
|
@@ -74,27 +33,65 @@ var AniAds = ({
|
|
|
74
33
|
onAdClick(ad.id, ad.destination_url);
|
|
75
34
|
}
|
|
76
35
|
window.open(ad.destination_url, "_blank", "noopener,noreferrer");
|
|
77
|
-
await fetchAd();
|
|
78
36
|
}
|
|
79
37
|
} catch (err) {
|
|
80
38
|
console.error("Error tracking click:", err);
|
|
81
39
|
window.open(ad.destination_url, "_blank", "noopener,noreferrer");
|
|
82
40
|
}
|
|
83
|
-
}, [ad, creator_code, user_wallet_address, api_url, onAdClick
|
|
41
|
+
}, [ad, creator_code, user_wallet_address, api_url, onAdClick]);
|
|
84
42
|
const getBestImage = (ad2) => {
|
|
85
|
-
|
|
86
|
-
const imageUrl2 = getImageUrl(ad2, format);
|
|
87
|
-
if (imageUrl2) return imageUrl2;
|
|
88
|
-
}
|
|
89
|
-
return ad2.image_square || ad2.image_portrait || ad2.image_landscape || null;
|
|
43
|
+
return getImageUrl(ad2);
|
|
90
44
|
};
|
|
91
45
|
useEffect(() => {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
46
|
+
let cancelled = false;
|
|
47
|
+
const loadAd = async () => {
|
|
48
|
+
if (!creator_code) {
|
|
49
|
+
setLoading(false);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
setLoading(true);
|
|
54
|
+
setError(null);
|
|
55
|
+
const response = await fetch(`${api_url}/api/ads/sdk`, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers: {
|
|
58
|
+
"Content-Type": "application/json"
|
|
59
|
+
},
|
|
60
|
+
body: JSON.stringify({
|
|
61
|
+
creator_code
|
|
62
|
+
})
|
|
63
|
+
});
|
|
64
|
+
if (cancelled) return;
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
const errorData = await response.json().catch(() => ({}));
|
|
67
|
+
throw new Error(errorData.error || `HTTP ${response.status}: Failed to fetch ads`);
|
|
68
|
+
}
|
|
69
|
+
const data = await response.json();
|
|
70
|
+
if (cancelled) return;
|
|
71
|
+
if (data.ad) {
|
|
72
|
+
setAd(data.ad);
|
|
73
|
+
setError(null);
|
|
74
|
+
} else {
|
|
75
|
+
setAd(null);
|
|
76
|
+
setError(null);
|
|
77
|
+
}
|
|
78
|
+
} catch (err) {
|
|
79
|
+
if (cancelled) return;
|
|
80
|
+
console.error("Error fetching ad:", err);
|
|
81
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to load ad";
|
|
82
|
+
setError(errorMessage);
|
|
83
|
+
setAd(null);
|
|
84
|
+
} finally {
|
|
85
|
+
if (!cancelled) {
|
|
86
|
+
setLoading(false);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
loadAd();
|
|
91
|
+
return () => {
|
|
92
|
+
cancelled = true;
|
|
93
|
+
};
|
|
94
|
+
}, [creator_code, api_url]);
|
|
98
95
|
if (loading) {
|
|
99
96
|
return /* @__PURE__ */ jsx("div", { style: {
|
|
100
97
|
display: "flex",
|
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_code: string\r\n user_wallet_address: string\r\n
|
|
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 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 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 // 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(`${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])\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 // 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) {\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 }),\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 } 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_code, 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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ani-ads-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
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",
|
|
@@ -35,4 +35,3 @@
|
|
|
35
35
|
"react-dom": "^19.2.0"
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
-
|