@adtogether/web-sdk 0.1.0
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/CHANGELOG.md +3 -0
- package/LICENSE +21 -0
- package/dist/chunk-IDJUJZAL.mjs +57 -0
- package/dist/index.d.mts +27 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +83 -0
- package/dist/index.mjs +6 -0
- package/dist/react/index.d.mts +18 -0
- package/dist/react/index.d.ts +18 -0
- package/dist/react/index.js +231 -0
- package/dist/react/index.mjs +152 -0
- package/errors.txt +0 -0
- package/package.json +46 -0
- package/src/core/AdTogether.ts +65 -0
- package/src/core/types.ts +12 -0
- package/src/index.ts +2 -0
- package/src/react/AdTogetherBanner.tsx +184 -0
- package/src/react/index.ts +1 -0
- package/tsconfig.json +19 -0
- package/tsup.config.ts +9 -0
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AdTogether
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// src/core/AdTogether.ts
|
|
2
|
+
var AdTogether = class _AdTogether {
|
|
3
|
+
constructor() {
|
|
4
|
+
this.baseUrl = "https://adtogether.com";
|
|
5
|
+
}
|
|
6
|
+
static get shared() {
|
|
7
|
+
if (!_AdTogether.instance) {
|
|
8
|
+
_AdTogether.instance = new _AdTogether();
|
|
9
|
+
}
|
|
10
|
+
return _AdTogether.instance;
|
|
11
|
+
}
|
|
12
|
+
static initialize(options) {
|
|
13
|
+
const sdk = _AdTogether.shared;
|
|
14
|
+
sdk.appId = options.appId;
|
|
15
|
+
if (options.baseUrl) {
|
|
16
|
+
sdk.baseUrl = options.baseUrl;
|
|
17
|
+
}
|
|
18
|
+
console.log(`AdTogether SDK Initialized with App ID: ${options.appId}`);
|
|
19
|
+
}
|
|
20
|
+
assertInitialized() {
|
|
21
|
+
if (!this.appId) {
|
|
22
|
+
console.error("AdTogether Error: SDK has not been initialized. Please call AdTogether.initialize() before displaying ads.");
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
static async fetchAd(adUnitId) {
|
|
28
|
+
if (!_AdTogether.shared.assertInitialized()) {
|
|
29
|
+
throw new Error("AdTogether not initialized");
|
|
30
|
+
}
|
|
31
|
+
const response = await fetch(`${_AdTogether.shared.baseUrl}/api/ads/serve?country=global&adUnitId=${adUnitId}`);
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
throw new Error(`Failed to fetch ad. Status: ${response.status}`);
|
|
34
|
+
}
|
|
35
|
+
return response.json();
|
|
36
|
+
}
|
|
37
|
+
static trackImpression(adId) {
|
|
38
|
+
this.trackEvent("/api/ads/impression", adId);
|
|
39
|
+
}
|
|
40
|
+
static trackClick(adId) {
|
|
41
|
+
this.trackEvent("/api/ads/click", adId);
|
|
42
|
+
}
|
|
43
|
+
static trackEvent(endpoint, adId) {
|
|
44
|
+
if (!_AdTogether.shared.assertInitialized()) return;
|
|
45
|
+
fetch(`${_AdTogether.shared.baseUrl}${endpoint}`, {
|
|
46
|
+
method: "POST",
|
|
47
|
+
headers: {
|
|
48
|
+
"Content-Type": "application/json"
|
|
49
|
+
},
|
|
50
|
+
body: JSON.stringify({ adId })
|
|
51
|
+
}).catch(console.error);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export {
|
|
56
|
+
AdTogether
|
|
57
|
+
};
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
interface AdModel {
|
|
2
|
+
id: string;
|
|
3
|
+
title: string;
|
|
4
|
+
description: string;
|
|
5
|
+
clickUrl?: string;
|
|
6
|
+
imageUrl?: string;
|
|
7
|
+
}
|
|
8
|
+
interface AdTogetherOptions {
|
|
9
|
+
appId: string;
|
|
10
|
+
baseUrl?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
declare class AdTogether {
|
|
14
|
+
private static instance;
|
|
15
|
+
private appId?;
|
|
16
|
+
baseUrl: string;
|
|
17
|
+
private constructor();
|
|
18
|
+
static get shared(): AdTogether;
|
|
19
|
+
static initialize(options: AdTogetherOptions): void;
|
|
20
|
+
assertInitialized(): boolean;
|
|
21
|
+
static fetchAd(adUnitId: string): Promise<AdModel>;
|
|
22
|
+
static trackImpression(adId: string): void;
|
|
23
|
+
static trackClick(adId: string): void;
|
|
24
|
+
private static trackEvent;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { type AdModel, AdTogether, type AdTogetherOptions };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
interface AdModel {
|
|
2
|
+
id: string;
|
|
3
|
+
title: string;
|
|
4
|
+
description: string;
|
|
5
|
+
clickUrl?: string;
|
|
6
|
+
imageUrl?: string;
|
|
7
|
+
}
|
|
8
|
+
interface AdTogetherOptions {
|
|
9
|
+
appId: string;
|
|
10
|
+
baseUrl?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
declare class AdTogether {
|
|
14
|
+
private static instance;
|
|
15
|
+
private appId?;
|
|
16
|
+
baseUrl: string;
|
|
17
|
+
private constructor();
|
|
18
|
+
static get shared(): AdTogether;
|
|
19
|
+
static initialize(options: AdTogetherOptions): void;
|
|
20
|
+
assertInitialized(): boolean;
|
|
21
|
+
static fetchAd(adUnitId: string): Promise<AdModel>;
|
|
22
|
+
static trackImpression(adId: string): void;
|
|
23
|
+
static trackClick(adId: string): void;
|
|
24
|
+
private static trackEvent;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { type AdModel, AdTogether, type AdTogetherOptions };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AdTogether: () => AdTogether
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
27
|
+
// src/core/AdTogether.ts
|
|
28
|
+
var AdTogether = class _AdTogether {
|
|
29
|
+
constructor() {
|
|
30
|
+
this.baseUrl = "https://adtogether.com";
|
|
31
|
+
}
|
|
32
|
+
static get shared() {
|
|
33
|
+
if (!_AdTogether.instance) {
|
|
34
|
+
_AdTogether.instance = new _AdTogether();
|
|
35
|
+
}
|
|
36
|
+
return _AdTogether.instance;
|
|
37
|
+
}
|
|
38
|
+
static initialize(options) {
|
|
39
|
+
const sdk = _AdTogether.shared;
|
|
40
|
+
sdk.appId = options.appId;
|
|
41
|
+
if (options.baseUrl) {
|
|
42
|
+
sdk.baseUrl = options.baseUrl;
|
|
43
|
+
}
|
|
44
|
+
console.log(`AdTogether SDK Initialized with App ID: ${options.appId}`);
|
|
45
|
+
}
|
|
46
|
+
assertInitialized() {
|
|
47
|
+
if (!this.appId) {
|
|
48
|
+
console.error("AdTogether Error: SDK has not been initialized. Please call AdTogether.initialize() before displaying ads.");
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
static async fetchAd(adUnitId) {
|
|
54
|
+
if (!_AdTogether.shared.assertInitialized()) {
|
|
55
|
+
throw new Error("AdTogether not initialized");
|
|
56
|
+
}
|
|
57
|
+
const response = await fetch(`${_AdTogether.shared.baseUrl}/api/ads/serve?country=global&adUnitId=${adUnitId}`);
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
throw new Error(`Failed to fetch ad. Status: ${response.status}`);
|
|
60
|
+
}
|
|
61
|
+
return response.json();
|
|
62
|
+
}
|
|
63
|
+
static trackImpression(adId) {
|
|
64
|
+
this.trackEvent("/api/ads/impression", adId);
|
|
65
|
+
}
|
|
66
|
+
static trackClick(adId) {
|
|
67
|
+
this.trackEvent("/api/ads/click", adId);
|
|
68
|
+
}
|
|
69
|
+
static trackEvent(endpoint, adId) {
|
|
70
|
+
if (!_AdTogether.shared.assertInitialized()) return;
|
|
71
|
+
fetch(`${_AdTogether.shared.baseUrl}${endpoint}`, {
|
|
72
|
+
method: "POST",
|
|
73
|
+
headers: {
|
|
74
|
+
"Content-Type": "application/json"
|
|
75
|
+
},
|
|
76
|
+
body: JSON.stringify({ adId })
|
|
77
|
+
}).catch(console.error);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
81
|
+
0 && (module.exports = {
|
|
82
|
+
AdTogether
|
|
83
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
interface AdTogetherBannerProps {
|
|
4
|
+
adUnitId: string;
|
|
5
|
+
className?: string;
|
|
6
|
+
style?: React.CSSProperties;
|
|
7
|
+
onAdLoaded?: () => void;
|
|
8
|
+
onAdFailedToLoad?: (error: Error) => void;
|
|
9
|
+
/** Width of the ad element. Defaults to '100%' */
|
|
10
|
+
width?: number | string;
|
|
11
|
+
/** Height of the ad element. Defaults to 'auto' */
|
|
12
|
+
height?: number | string;
|
|
13
|
+
/** Pass 'dark' to use dark mode, 'light' for light mode, or 'auto' (default) to respect system preference */
|
|
14
|
+
theme?: 'dark' | 'light' | 'auto';
|
|
15
|
+
}
|
|
16
|
+
declare const AdTogetherBanner: React.FC<AdTogetherBannerProps>;
|
|
17
|
+
|
|
18
|
+
export { AdTogetherBanner, type AdTogetherBannerProps };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
interface AdTogetherBannerProps {
|
|
4
|
+
adUnitId: string;
|
|
5
|
+
className?: string;
|
|
6
|
+
style?: React.CSSProperties;
|
|
7
|
+
onAdLoaded?: () => void;
|
|
8
|
+
onAdFailedToLoad?: (error: Error) => void;
|
|
9
|
+
/** Width of the ad element. Defaults to '100%' */
|
|
10
|
+
width?: number | string;
|
|
11
|
+
/** Height of the ad element. Defaults to 'auto' */
|
|
12
|
+
height?: number | string;
|
|
13
|
+
/** Pass 'dark' to use dark mode, 'light' for light mode, or 'auto' (default) to respect system preference */
|
|
14
|
+
theme?: 'dark' | 'light' | 'auto';
|
|
15
|
+
}
|
|
16
|
+
declare const AdTogetherBanner: React.FC<AdTogetherBannerProps>;
|
|
17
|
+
|
|
18
|
+
export { AdTogetherBanner, type AdTogetherBannerProps };
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/react/index.ts
|
|
21
|
+
var react_exports = {};
|
|
22
|
+
__export(react_exports, {
|
|
23
|
+
AdTogetherBanner: () => AdTogetherBanner
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(react_exports);
|
|
26
|
+
|
|
27
|
+
// src/react/AdTogetherBanner.tsx
|
|
28
|
+
var import_react = require("react");
|
|
29
|
+
|
|
30
|
+
// src/core/AdTogether.ts
|
|
31
|
+
var AdTogether = class _AdTogether {
|
|
32
|
+
constructor() {
|
|
33
|
+
this.baseUrl = "https://adtogether.com";
|
|
34
|
+
}
|
|
35
|
+
static get shared() {
|
|
36
|
+
if (!_AdTogether.instance) {
|
|
37
|
+
_AdTogether.instance = new _AdTogether();
|
|
38
|
+
}
|
|
39
|
+
return _AdTogether.instance;
|
|
40
|
+
}
|
|
41
|
+
static initialize(options) {
|
|
42
|
+
const sdk = _AdTogether.shared;
|
|
43
|
+
sdk.appId = options.appId;
|
|
44
|
+
if (options.baseUrl) {
|
|
45
|
+
sdk.baseUrl = options.baseUrl;
|
|
46
|
+
}
|
|
47
|
+
console.log(`AdTogether SDK Initialized with App ID: ${options.appId}`);
|
|
48
|
+
}
|
|
49
|
+
assertInitialized() {
|
|
50
|
+
if (!this.appId) {
|
|
51
|
+
console.error("AdTogether Error: SDK has not been initialized. Please call AdTogether.initialize() before displaying ads.");
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
static async fetchAd(adUnitId) {
|
|
57
|
+
if (!_AdTogether.shared.assertInitialized()) {
|
|
58
|
+
throw new Error("AdTogether not initialized");
|
|
59
|
+
}
|
|
60
|
+
const response = await fetch(`${_AdTogether.shared.baseUrl}/api/ads/serve?country=global&adUnitId=${adUnitId}`);
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
throw new Error(`Failed to fetch ad. Status: ${response.status}`);
|
|
63
|
+
}
|
|
64
|
+
return response.json();
|
|
65
|
+
}
|
|
66
|
+
static trackImpression(adId) {
|
|
67
|
+
this.trackEvent("/api/ads/impression", adId);
|
|
68
|
+
}
|
|
69
|
+
static trackClick(adId) {
|
|
70
|
+
this.trackEvent("/api/ads/click", adId);
|
|
71
|
+
}
|
|
72
|
+
static trackEvent(endpoint, adId) {
|
|
73
|
+
if (!_AdTogether.shared.assertInitialized()) return;
|
|
74
|
+
fetch(`${_AdTogether.shared.baseUrl}${endpoint}`, {
|
|
75
|
+
method: "POST",
|
|
76
|
+
headers: {
|
|
77
|
+
"Content-Type": "application/json"
|
|
78
|
+
},
|
|
79
|
+
body: JSON.stringify({ adId })
|
|
80
|
+
}).catch(console.error);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// src/react/AdTogetherBanner.tsx
|
|
85
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
86
|
+
var AdTogetherBanner = ({
|
|
87
|
+
adUnitId,
|
|
88
|
+
className = "",
|
|
89
|
+
style = {},
|
|
90
|
+
onAdLoaded,
|
|
91
|
+
onAdFailedToLoad,
|
|
92
|
+
width = "100%",
|
|
93
|
+
height = "auto",
|
|
94
|
+
theme = "auto"
|
|
95
|
+
}) => {
|
|
96
|
+
const [adData, setAdData] = (0, import_react.useState)(null);
|
|
97
|
+
const [isLoading, setIsLoading] = (0, import_react.useState)(true);
|
|
98
|
+
const [hasError, setHasError] = (0, import_react.useState)(false);
|
|
99
|
+
const [isDarkMode, setIsDarkMode] = (0, import_react.useState)(theme === "dark");
|
|
100
|
+
const containerRef = (0, import_react.useRef)(null);
|
|
101
|
+
const impressionTrackedRef = (0, import_react.useRef)(false);
|
|
102
|
+
(0, import_react.useEffect)(() => {
|
|
103
|
+
if (theme === "auto") {
|
|
104
|
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
105
|
+
setIsDarkMode(mediaQuery.matches);
|
|
106
|
+
const handler = (e) => setIsDarkMode(e.matches);
|
|
107
|
+
mediaQuery.addEventListener("change", handler);
|
|
108
|
+
return () => mediaQuery.removeEventListener("change", handler);
|
|
109
|
+
} else {
|
|
110
|
+
setIsDarkMode(theme === "dark");
|
|
111
|
+
}
|
|
112
|
+
}, [theme]);
|
|
113
|
+
(0, import_react.useEffect)(() => {
|
|
114
|
+
let isMounted = true;
|
|
115
|
+
AdTogether.fetchAd(adUnitId).then((ad) => {
|
|
116
|
+
if (isMounted) {
|
|
117
|
+
setAdData(ad);
|
|
118
|
+
setIsLoading(false);
|
|
119
|
+
onAdLoaded?.();
|
|
120
|
+
}
|
|
121
|
+
}).catch((err) => {
|
|
122
|
+
if (isMounted) {
|
|
123
|
+
console.error("AdTogether Failed to load ad:", err);
|
|
124
|
+
setHasError(true);
|
|
125
|
+
setIsLoading(false);
|
|
126
|
+
onAdFailedToLoad?.(err);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
return () => {
|
|
130
|
+
isMounted = false;
|
|
131
|
+
};
|
|
132
|
+
}, [adUnitId, onAdLoaded, onAdFailedToLoad]);
|
|
133
|
+
(0, import_react.useEffect)(() => {
|
|
134
|
+
if (!adData || isLoading || hasError || !containerRef.current) return;
|
|
135
|
+
const observer = new IntersectionObserver(
|
|
136
|
+
(entries) => {
|
|
137
|
+
if (entries[0].isIntersecting && entries[0].intersectionRatio >= 0.5 && !impressionTrackedRef.current) {
|
|
138
|
+
impressionTrackedRef.current = true;
|
|
139
|
+
AdTogether.trackImpression(adData.id);
|
|
140
|
+
observer.disconnect();
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
{ threshold: 0.5 }
|
|
144
|
+
);
|
|
145
|
+
observer.observe(containerRef.current);
|
|
146
|
+
return () => {
|
|
147
|
+
observer.disconnect();
|
|
148
|
+
};
|
|
149
|
+
}, [adData, isLoading, hasError]);
|
|
150
|
+
const handleContainerClick = () => {
|
|
151
|
+
if (!adData) return;
|
|
152
|
+
AdTogether.trackClick(adData.id);
|
|
153
|
+
if (adData.clickUrl) {
|
|
154
|
+
window.open(adData.clickUrl, "_blank", "noopener,noreferrer");
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
if (isLoading) {
|
|
158
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
159
|
+
"div",
|
|
160
|
+
{
|
|
161
|
+
style: { width, height, ...style },
|
|
162
|
+
className
|
|
163
|
+
}
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
if (hasError || !adData) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
const bgColor = isDarkMode ? "#1F2937" : "#ffffff";
|
|
170
|
+
const borderColor = isDarkMode ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.1)";
|
|
171
|
+
const textColor = isDarkMode ? "#F9FAFB" : "#111827";
|
|
172
|
+
const descColor = isDarkMode ? "#9CA3AF" : "#6B7280";
|
|
173
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
174
|
+
"div",
|
|
175
|
+
{
|
|
176
|
+
ref: containerRef,
|
|
177
|
+
className: `adtogether-banner ${className}`,
|
|
178
|
+
onClick: handleContainerClick,
|
|
179
|
+
style: {
|
|
180
|
+
display: "flex",
|
|
181
|
+
flexDirection: "row",
|
|
182
|
+
width,
|
|
183
|
+
height,
|
|
184
|
+
backgroundColor: bgColor,
|
|
185
|
+
borderRadius: "12px",
|
|
186
|
+
border: `1px solid ${borderColor}`,
|
|
187
|
+
boxShadow: "0 4px 10px rgba(0, 0, 0, 0.05)",
|
|
188
|
+
overflow: "hidden",
|
|
189
|
+
cursor: "pointer",
|
|
190
|
+
boxSizing: "border-box",
|
|
191
|
+
...style
|
|
192
|
+
},
|
|
193
|
+
onMouseOver: (e) => {
|
|
194
|
+
e.currentTarget.style.transform = "scale(1.02)";
|
|
195
|
+
e.currentTarget.style.transition = "transform 0.2s";
|
|
196
|
+
},
|
|
197
|
+
onMouseOut: (e) => {
|
|
198
|
+
e.currentTarget.style.transform = "scale(1)";
|
|
199
|
+
},
|
|
200
|
+
children: [
|
|
201
|
+
adData.imageUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { flexShrink: 0, width: "120px", height: "100%", minHeight: "80px" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
202
|
+
"img",
|
|
203
|
+
{
|
|
204
|
+
src: adData.imageUrl,
|
|
205
|
+
alt: adData.title,
|
|
206
|
+
style: { width: "100%", height: "100%", objectFit: "cover" }
|
|
207
|
+
}
|
|
208
|
+
) }),
|
|
209
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { padding: "12px", display: "flex", flexDirection: "column", justifyContent: "center", flex: 1, minWidth: 0 }, children: [
|
|
210
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: "4px" }, children: [
|
|
211
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontWeight: "bold", fontSize: "14px", color: textColor, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, children: adData.title }),
|
|
212
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: {
|
|
213
|
+
backgroundColor: "#FBBF24",
|
|
214
|
+
color: "#000",
|
|
215
|
+
fontSize: "9px",
|
|
216
|
+
fontWeight: "bold",
|
|
217
|
+
padding: "2px 4px",
|
|
218
|
+
borderRadius: "4px",
|
|
219
|
+
marginLeft: "8px"
|
|
220
|
+
}, children: "AD" })
|
|
221
|
+
] }),
|
|
222
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: "12px", color: descColor, overflow: "hidden", display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical" }, children: adData.description })
|
|
223
|
+
] })
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
);
|
|
227
|
+
};
|
|
228
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
229
|
+
0 && (module.exports = {
|
|
230
|
+
AdTogetherBanner
|
|
231
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AdTogether
|
|
3
|
+
} from "../chunk-IDJUJZAL.mjs";
|
|
4
|
+
|
|
5
|
+
// src/react/AdTogetherBanner.tsx
|
|
6
|
+
import { useEffect, useRef, useState } from "react";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
var AdTogetherBanner = ({
|
|
9
|
+
adUnitId,
|
|
10
|
+
className = "",
|
|
11
|
+
style = {},
|
|
12
|
+
onAdLoaded,
|
|
13
|
+
onAdFailedToLoad,
|
|
14
|
+
width = "100%",
|
|
15
|
+
height = "auto",
|
|
16
|
+
theme = "auto"
|
|
17
|
+
}) => {
|
|
18
|
+
const [adData, setAdData] = useState(null);
|
|
19
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
20
|
+
const [hasError, setHasError] = useState(false);
|
|
21
|
+
const [isDarkMode, setIsDarkMode] = useState(theme === "dark");
|
|
22
|
+
const containerRef = useRef(null);
|
|
23
|
+
const impressionTrackedRef = useRef(false);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (theme === "auto") {
|
|
26
|
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
27
|
+
setIsDarkMode(mediaQuery.matches);
|
|
28
|
+
const handler = (e) => setIsDarkMode(e.matches);
|
|
29
|
+
mediaQuery.addEventListener("change", handler);
|
|
30
|
+
return () => mediaQuery.removeEventListener("change", handler);
|
|
31
|
+
} else {
|
|
32
|
+
setIsDarkMode(theme === "dark");
|
|
33
|
+
}
|
|
34
|
+
}, [theme]);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
let isMounted = true;
|
|
37
|
+
AdTogether.fetchAd(adUnitId).then((ad) => {
|
|
38
|
+
if (isMounted) {
|
|
39
|
+
setAdData(ad);
|
|
40
|
+
setIsLoading(false);
|
|
41
|
+
onAdLoaded?.();
|
|
42
|
+
}
|
|
43
|
+
}).catch((err) => {
|
|
44
|
+
if (isMounted) {
|
|
45
|
+
console.error("AdTogether Failed to load ad:", err);
|
|
46
|
+
setHasError(true);
|
|
47
|
+
setIsLoading(false);
|
|
48
|
+
onAdFailedToLoad?.(err);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
return () => {
|
|
52
|
+
isMounted = false;
|
|
53
|
+
};
|
|
54
|
+
}, [adUnitId, onAdLoaded, onAdFailedToLoad]);
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!adData || isLoading || hasError || !containerRef.current) return;
|
|
57
|
+
const observer = new IntersectionObserver(
|
|
58
|
+
(entries) => {
|
|
59
|
+
if (entries[0].isIntersecting && entries[0].intersectionRatio >= 0.5 && !impressionTrackedRef.current) {
|
|
60
|
+
impressionTrackedRef.current = true;
|
|
61
|
+
AdTogether.trackImpression(adData.id);
|
|
62
|
+
observer.disconnect();
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{ threshold: 0.5 }
|
|
66
|
+
);
|
|
67
|
+
observer.observe(containerRef.current);
|
|
68
|
+
return () => {
|
|
69
|
+
observer.disconnect();
|
|
70
|
+
};
|
|
71
|
+
}, [adData, isLoading, hasError]);
|
|
72
|
+
const handleContainerClick = () => {
|
|
73
|
+
if (!adData) return;
|
|
74
|
+
AdTogether.trackClick(adData.id);
|
|
75
|
+
if (adData.clickUrl) {
|
|
76
|
+
window.open(adData.clickUrl, "_blank", "noopener,noreferrer");
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
if (isLoading) {
|
|
80
|
+
return /* @__PURE__ */ jsx(
|
|
81
|
+
"div",
|
|
82
|
+
{
|
|
83
|
+
style: { width, height, ...style },
|
|
84
|
+
className
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
if (hasError || !adData) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const bgColor = isDarkMode ? "#1F2937" : "#ffffff";
|
|
92
|
+
const borderColor = isDarkMode ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.1)";
|
|
93
|
+
const textColor = isDarkMode ? "#F9FAFB" : "#111827";
|
|
94
|
+
const descColor = isDarkMode ? "#9CA3AF" : "#6B7280";
|
|
95
|
+
return /* @__PURE__ */ jsxs(
|
|
96
|
+
"div",
|
|
97
|
+
{
|
|
98
|
+
ref: containerRef,
|
|
99
|
+
className: `adtogether-banner ${className}`,
|
|
100
|
+
onClick: handleContainerClick,
|
|
101
|
+
style: {
|
|
102
|
+
display: "flex",
|
|
103
|
+
flexDirection: "row",
|
|
104
|
+
width,
|
|
105
|
+
height,
|
|
106
|
+
backgroundColor: bgColor,
|
|
107
|
+
borderRadius: "12px",
|
|
108
|
+
border: `1px solid ${borderColor}`,
|
|
109
|
+
boxShadow: "0 4px 10px rgba(0, 0, 0, 0.05)",
|
|
110
|
+
overflow: "hidden",
|
|
111
|
+
cursor: "pointer",
|
|
112
|
+
boxSizing: "border-box",
|
|
113
|
+
...style
|
|
114
|
+
},
|
|
115
|
+
onMouseOver: (e) => {
|
|
116
|
+
e.currentTarget.style.transform = "scale(1.02)";
|
|
117
|
+
e.currentTarget.style.transition = "transform 0.2s";
|
|
118
|
+
},
|
|
119
|
+
onMouseOut: (e) => {
|
|
120
|
+
e.currentTarget.style.transform = "scale(1)";
|
|
121
|
+
},
|
|
122
|
+
children: [
|
|
123
|
+
adData.imageUrl && /* @__PURE__ */ jsx("div", { style: { flexShrink: 0, width: "120px", height: "100%", minHeight: "80px" }, children: /* @__PURE__ */ jsx(
|
|
124
|
+
"img",
|
|
125
|
+
{
|
|
126
|
+
src: adData.imageUrl,
|
|
127
|
+
alt: adData.title,
|
|
128
|
+
style: { width: "100%", height: "100%", objectFit: "cover" }
|
|
129
|
+
}
|
|
130
|
+
) }),
|
|
131
|
+
/* @__PURE__ */ jsxs("div", { style: { padding: "12px", display: "flex", flexDirection: "column", justifyContent: "center", flex: 1, minWidth: 0 }, children: [
|
|
132
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: "4px" }, children: [
|
|
133
|
+
/* @__PURE__ */ jsx("span", { style: { fontWeight: "bold", fontSize: "14px", color: textColor, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, children: adData.title }),
|
|
134
|
+
/* @__PURE__ */ jsx("span", { style: {
|
|
135
|
+
backgroundColor: "#FBBF24",
|
|
136
|
+
color: "#000",
|
|
137
|
+
fontSize: "9px",
|
|
138
|
+
fontWeight: "bold",
|
|
139
|
+
padding: "2px 4px",
|
|
140
|
+
borderRadius: "4px",
|
|
141
|
+
marginLeft: "8px"
|
|
142
|
+
}, children: "AD" })
|
|
143
|
+
] }),
|
|
144
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: "12px", color: descColor, overflow: "hidden", display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical" }, children: adData.description })
|
|
145
|
+
] })
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
};
|
|
150
|
+
export {
|
|
151
|
+
AdTogetherBanner
|
|
152
|
+
};
|
package/errors.txt
ADDED
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@adtogether/web-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The official AdTogether Web SDK for monetizing applications.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/AdTogether/AdTogether.git",
|
|
8
|
+
"directory": "sdk/web-sdk"
|
|
9
|
+
},
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"module": "./dist/index.mjs",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"require": "./dist/index.js",
|
|
20
|
+
"import": "./dist/index.mjs"
|
|
21
|
+
},
|
|
22
|
+
"./react": {
|
|
23
|
+
"types": "./dist/react/index.d.ts",
|
|
24
|
+
"require": "./dist/react/index.js",
|
|
25
|
+
"import": "./dist/react/index.mjs"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsup",
|
|
30
|
+
"dev": "tsup --watch"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [],
|
|
33
|
+
"author": "",
|
|
34
|
+
"license": "ISC",
|
|
35
|
+
"type": "commonjs",
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"react": "^19.2.4",
|
|
38
|
+
"react-dom": "^19.2.4"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/react": "^19.2.14",
|
|
42
|
+
"@types/react-dom": "^19.2.3",
|
|
43
|
+
"tsup": "^8.5.1",
|
|
44
|
+
"typescript": "^5.3.3"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { AdModel, AdTogetherOptions } from './types';
|
|
2
|
+
|
|
3
|
+
export class AdTogether {
|
|
4
|
+
private static instance: AdTogether;
|
|
5
|
+
private appId?: string;
|
|
6
|
+
public baseUrl: string = 'https://adtogether.com';
|
|
7
|
+
|
|
8
|
+
private constructor() {}
|
|
9
|
+
|
|
10
|
+
static get shared(): AdTogether {
|
|
11
|
+
if (!AdTogether.instance) {
|
|
12
|
+
AdTogether.instance = new AdTogether();
|
|
13
|
+
}
|
|
14
|
+
return AdTogether.instance;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static initialize(options: AdTogetherOptions) {
|
|
18
|
+
const sdk = AdTogether.shared;
|
|
19
|
+
sdk.appId = options.appId;
|
|
20
|
+
if (options.baseUrl) {
|
|
21
|
+
sdk.baseUrl = options.baseUrl;
|
|
22
|
+
}
|
|
23
|
+
console.log(`AdTogether SDK Initialized with App ID: ${options.appId}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
assertInitialized() {
|
|
27
|
+
if (!this.appId) {
|
|
28
|
+
console.error('AdTogether Error: SDK has not been initialized. Please call AdTogether.initialize() before displaying ads.');
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static async fetchAd(adUnitId: string): Promise<AdModel> {
|
|
35
|
+
if (!AdTogether.shared.assertInitialized()) {
|
|
36
|
+
throw new Error('AdTogether not initialized');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const response = await fetch(`${AdTogether.shared.baseUrl}/api/ads/serve?country=global&adUnitId=${adUnitId}`);
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
throw new Error(`Failed to fetch ad. Status: ${response.status}`);
|
|
42
|
+
}
|
|
43
|
+
return response.json();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static trackImpression(adId: string) {
|
|
47
|
+
this.trackEvent('/api/ads/impression', adId);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static trackClick(adId: string) {
|
|
51
|
+
this.trackEvent('/api/ads/click', adId);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private static trackEvent(endpoint: string, adId: string) {
|
|
55
|
+
if (!AdTogether.shared.assertInitialized()) return;
|
|
56
|
+
|
|
57
|
+
fetch(`${AdTogether.shared.baseUrl}${endpoint}`, {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
headers: {
|
|
60
|
+
'Content-Type': 'application/json',
|
|
61
|
+
},
|
|
62
|
+
body: JSON.stringify({ adId }),
|
|
63
|
+
}).catch(console.error);
|
|
64
|
+
}
|
|
65
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { AdTogether } from '../core/AdTogether';
|
|
3
|
+
import { AdModel } from '../core/types';
|
|
4
|
+
|
|
5
|
+
export interface AdTogetherBannerProps {
|
|
6
|
+
adUnitId: string;
|
|
7
|
+
className?: string;
|
|
8
|
+
style?: React.CSSProperties;
|
|
9
|
+
onAdLoaded?: () => void;
|
|
10
|
+
onAdFailedToLoad?: (error: Error) => void;
|
|
11
|
+
/** Width of the ad element. Defaults to '100%' */
|
|
12
|
+
width?: number | string;
|
|
13
|
+
/** Height of the ad element. Defaults to 'auto' */
|
|
14
|
+
height?: number | string;
|
|
15
|
+
/** Pass 'dark' to use dark mode, 'light' for light mode, or 'auto' (default) to respect system preference */
|
|
16
|
+
theme?: 'dark' | 'light' | 'auto';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const AdTogetherBanner: React.FC<AdTogetherBannerProps> = ({
|
|
20
|
+
adUnitId,
|
|
21
|
+
className = '',
|
|
22
|
+
style = {},
|
|
23
|
+
onAdLoaded,
|
|
24
|
+
onAdFailedToLoad,
|
|
25
|
+
width = '100%',
|
|
26
|
+
height = 'auto',
|
|
27
|
+
theme = 'auto',
|
|
28
|
+
}) => {
|
|
29
|
+
const [adData, setAdData] = useState<AdModel | null>(null);
|
|
30
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
31
|
+
const [hasError, setHasError] = useState(false);
|
|
32
|
+
const [isDarkMode, setIsDarkMode] = useState(theme === 'dark');
|
|
33
|
+
|
|
34
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
35
|
+
const impressionTrackedRef = useRef(false);
|
|
36
|
+
|
|
37
|
+
// Handle system theme changes
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (theme === 'auto') {
|
|
40
|
+
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
41
|
+
setIsDarkMode(mediaQuery.matches);
|
|
42
|
+
|
|
43
|
+
const handler = (e: MediaQueryListEvent) => setIsDarkMode(e.matches);
|
|
44
|
+
mediaQuery.addEventListener('change', handler);
|
|
45
|
+
return () => mediaQuery.removeEventListener('change', handler);
|
|
46
|
+
} else {
|
|
47
|
+
setIsDarkMode(theme === 'dark');
|
|
48
|
+
}
|
|
49
|
+
}, [theme]);
|
|
50
|
+
|
|
51
|
+
// Fetch Ad
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
let isMounted = true;
|
|
54
|
+
|
|
55
|
+
AdTogether.fetchAd(adUnitId)
|
|
56
|
+
.then((ad) => {
|
|
57
|
+
if (isMounted) {
|
|
58
|
+
setAdData(ad);
|
|
59
|
+
setIsLoading(false);
|
|
60
|
+
onAdLoaded?.();
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
.catch((err) => {
|
|
64
|
+
if (isMounted) {
|
|
65
|
+
console.error('AdTogether Failed to load ad:', err);
|
|
66
|
+
setHasError(true);
|
|
67
|
+
setIsLoading(false);
|
|
68
|
+
onAdFailedToLoad?.(err);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return () => {
|
|
73
|
+
isMounted = false;
|
|
74
|
+
};
|
|
75
|
+
}, [adUnitId, onAdLoaded, onAdFailedToLoad]);
|
|
76
|
+
|
|
77
|
+
// Impression tracking
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (!adData || isLoading || hasError || !containerRef.current) return;
|
|
80
|
+
|
|
81
|
+
const observer = new IntersectionObserver(
|
|
82
|
+
(entries) => {
|
|
83
|
+
if (entries[0].isIntersecting && entries[0].intersectionRatio >= 0.5 && !impressionTrackedRef.current) {
|
|
84
|
+
impressionTrackedRef.current = true;
|
|
85
|
+
AdTogether.trackImpression(adData.id);
|
|
86
|
+
observer.disconnect();
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
{ threshold: 0.5 }
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
observer.observe(containerRef.current);
|
|
93
|
+
|
|
94
|
+
return () => {
|
|
95
|
+
observer.disconnect();
|
|
96
|
+
};
|
|
97
|
+
}, [adData, isLoading, hasError]);
|
|
98
|
+
|
|
99
|
+
const handleContainerClick = () => {
|
|
100
|
+
if (!adData) return;
|
|
101
|
+
AdTogether.trackClick(adData.id);
|
|
102
|
+
if (adData.clickUrl) {
|
|
103
|
+
window.open(adData.clickUrl, '_blank', 'noopener,noreferrer');
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
if (isLoading) {
|
|
108
|
+
return (
|
|
109
|
+
<div
|
|
110
|
+
style={{ width, height, ...style }}
|
|
111
|
+
className={className}
|
|
112
|
+
/>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (hasError || !adData) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const bgColor = isDarkMode ? '#1F2937' : '#ffffff';
|
|
121
|
+
const borderColor = isDarkMode ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)';
|
|
122
|
+
const textColor = isDarkMode ? '#F9FAFB' : '#111827';
|
|
123
|
+
const descColor = isDarkMode ? '#9CA3AF' : '#6B7280';
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<div
|
|
127
|
+
ref={containerRef}
|
|
128
|
+
className={`adtogether-banner ${className}`}
|
|
129
|
+
onClick={handleContainerClick}
|
|
130
|
+
style={{
|
|
131
|
+
display: 'flex',
|
|
132
|
+
flexDirection: 'row',
|
|
133
|
+
width,
|
|
134
|
+
height,
|
|
135
|
+
backgroundColor: bgColor,
|
|
136
|
+
borderRadius: '12px',
|
|
137
|
+
border: `1px solid ${borderColor}`,
|
|
138
|
+
boxShadow: '0 4px 10px rgba(0, 0, 0, 0.05)',
|
|
139
|
+
overflow: 'hidden',
|
|
140
|
+
cursor: 'pointer',
|
|
141
|
+
boxSizing: 'border-box',
|
|
142
|
+
...style,
|
|
143
|
+
}}
|
|
144
|
+
onMouseOver={(e) => {
|
|
145
|
+
e.currentTarget.style.transform = 'scale(1.02)';
|
|
146
|
+
e.currentTarget.style.transition = 'transform 0.2s';
|
|
147
|
+
}}
|
|
148
|
+
onMouseOut={(e) => {
|
|
149
|
+
e.currentTarget.style.transform = 'scale(1)';
|
|
150
|
+
}}
|
|
151
|
+
>
|
|
152
|
+
{adData.imageUrl && (
|
|
153
|
+
<div style={{ flexShrink: 0, width: '120px', height: '100%', minHeight: '80px' }}>
|
|
154
|
+
<img
|
|
155
|
+
src={adData.imageUrl}
|
|
156
|
+
alt={adData.title}
|
|
157
|
+
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
|
158
|
+
/>
|
|
159
|
+
</div>
|
|
160
|
+
)}
|
|
161
|
+
<div style={{ padding: '12px', display: 'flex', flexDirection: 'column', justifyContent: 'center', flex: 1, minWidth: 0 }}>
|
|
162
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '4px' }}>
|
|
163
|
+
<span style={{ fontWeight: 'bold', fontSize: '14px', color: textColor, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
|
164
|
+
{adData.title}
|
|
165
|
+
</span>
|
|
166
|
+
<span style={{
|
|
167
|
+
backgroundColor: '#FBBF24',
|
|
168
|
+
color: '#000',
|
|
169
|
+
fontSize: '9px',
|
|
170
|
+
fontWeight: 'bold',
|
|
171
|
+
padding: '2px 4px',
|
|
172
|
+
borderRadius: '4px',
|
|
173
|
+
marginLeft: '8px'
|
|
174
|
+
}}>
|
|
175
|
+
AD
|
|
176
|
+
</span>
|
|
177
|
+
</div>
|
|
178
|
+
<span style={{ fontSize: '12px', color: descColor, overflow: 'hidden', display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical' }}>
|
|
179
|
+
{adData.description}
|
|
180
|
+
</span>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
);
|
|
184
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './AdTogetherBanner';
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2020",
|
|
4
|
+
"module": "esnext",
|
|
5
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"declarationMap": true,
|
|
8
|
+
"sourceMap": true,
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"strict": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"forceConsistentCasingInFileNames": true,
|
|
14
|
+
"jsx": "react-jsx",
|
|
15
|
+
"moduleResolution": "bundler"
|
|
16
|
+
},
|
|
17
|
+
"include": ["src"],
|
|
18
|
+
"exclude": ["node_modules", "dist"]
|
|
19
|
+
}
|