@adland/react 0.7.2 → 0.9.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 +18 -0
- package/package.json +7 -2
- package/src/components/Ad.tsx +45 -51
- package/src/components/content/FarcasterProfileAdContent.tsx +80 -0
- package/src/components/content/TokenAdContent.tsx +79 -0
- package/src/index.ts +1 -1
- package/src/types.ts +32 -0
- package/src/utils/constants.ts +15 -1
- package/src/utils/index.ts +22 -0
- package/tsup.config.ts +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @adland/react
|
|
2
2
|
|
|
3
|
+
## 0.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 447cc7b: add profile & token type
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [447cc7b]
|
|
12
|
+
- @adland/data@0.12.0
|
|
13
|
+
|
|
14
|
+
## 0.8.0
|
|
15
|
+
|
|
16
|
+
### Minor Changes
|
|
17
|
+
|
|
18
|
+
- df8ba42: handle errors from api for no ad
|
|
19
|
+
- d6712f4: fix wrong api url ad path
|
|
20
|
+
|
|
3
21
|
## 0.7.2
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adland/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -15,6 +15,11 @@
|
|
|
15
15
|
"import": "./dist/index.js",
|
|
16
16
|
"require": "./dist/index.cjs"
|
|
17
17
|
},
|
|
18
|
+
"./types": {
|
|
19
|
+
"types": "./dist/types.d.ts",
|
|
20
|
+
"import": "./dist/types.js",
|
|
21
|
+
"require": "./dist/types.cjs"
|
|
22
|
+
},
|
|
18
23
|
"./package.json": "./package.json"
|
|
19
24
|
},
|
|
20
25
|
"peerDependencies": {
|
|
@@ -28,7 +33,7 @@
|
|
|
28
33
|
"@types/react-dom": "^18.3.1",
|
|
29
34
|
"lucide-react": "0.561.0",
|
|
30
35
|
"tsup": "^8.0.1",
|
|
31
|
-
"@adland/data": "0.
|
|
36
|
+
"@adland/data": "0.12.0"
|
|
32
37
|
},
|
|
33
38
|
"devDependencies": {
|
|
34
39
|
"@typescript-eslint/eslint-plugin": "^7.13.1",
|
package/src/components/Ad.tsx
CHANGED
|
@@ -8,48 +8,11 @@ import BasicAdBody from "./BasicAdBody";
|
|
|
8
8
|
import LinkAdContent from "./content/LinkAdContent";
|
|
9
9
|
import MiniappAdContent from "./content/MiniappAdContent";
|
|
10
10
|
import CastAdContent from "./content/CastAdContent";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
* Uses relative URL in browser (for local dev), otherwise defaults to production
|
|
17
|
-
*/
|
|
18
|
-
const getBaseUrl = (network: Network): string => {
|
|
19
|
-
if (typeof window !== "undefined") {
|
|
20
|
-
// In browser - use relative URL for local development
|
|
21
|
-
return "";
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (network === "testnet") {
|
|
25
|
-
return "https://testnet.adland.space";
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (network === "mainnet") {
|
|
29
|
-
return "https://app.adland.space";
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return "";
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
export interface AdProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
36
|
-
/**
|
|
37
|
-
* The owner/creator of the ad
|
|
38
|
-
*/
|
|
39
|
-
owner: string;
|
|
40
|
-
/**
|
|
41
|
-
* Unique identifier for the ad
|
|
42
|
-
*/
|
|
43
|
-
slotId: string;
|
|
44
|
-
/**
|
|
45
|
-
* Network to use for tracking requests (currently only "testnet" is supported)
|
|
46
|
-
*/
|
|
47
|
-
network?: Network;
|
|
48
|
-
/**
|
|
49
|
-
* Optional base URL override. If not provided, uses relative URL in browser or production URL in SSR
|
|
50
|
-
*/
|
|
51
|
-
baseUrl?: string;
|
|
52
|
-
}
|
|
11
|
+
import TokenAdContent from "./content/TokenAdContent";
|
|
12
|
+
import FarcasterProfileAdContent from "./content/FarcasterProfileAdContent";
|
|
13
|
+
import { AdDataQueryError, AdProps } from "../types";
|
|
14
|
+
import { getBaseUrl } from "../utils";
|
|
15
|
+
import { adlandApiUrl } from "../utils/constants";
|
|
53
16
|
|
|
54
17
|
/**
|
|
55
18
|
* Simple Ad component with built-in view and click tracking
|
|
@@ -77,16 +40,26 @@ export function Ad({
|
|
|
77
40
|
} = useFetch<AdData>(
|
|
78
41
|
`ad-data-${owner}-${slotId}`,
|
|
79
42
|
async () => {
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
},
|
|
43
|
+
const url = `${adlandApiUrl}/ad/owner/${owner.toLowerCase()}/slot/${slotId}`;
|
|
44
|
+
|
|
45
|
+
const res = await fetch(url, {
|
|
46
|
+
method: "GET",
|
|
47
|
+
headers: {
|
|
48
|
+
"Content-Type": "application/json",
|
|
87
49
|
},
|
|
88
|
-
);
|
|
89
|
-
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (!res.ok) {
|
|
53
|
+
throw new Error(AdDataQueryError.ERROR);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const data = await res.json();
|
|
57
|
+
|
|
58
|
+
if (data.error) {
|
|
59
|
+
throw new Error(data.error);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return data;
|
|
90
63
|
},
|
|
91
64
|
{
|
|
92
65
|
enabled: !!owner && !!slotId,
|
|
@@ -94,6 +67,8 @@ export function Ad({
|
|
|
94
67
|
);
|
|
95
68
|
const networkBaseUrl = baseUrl ?? getBaseUrl(network);
|
|
96
69
|
|
|
70
|
+
console.log({ error, adData });
|
|
71
|
+
|
|
97
72
|
const send = useCallback(
|
|
98
73
|
(type: "view" | "click") => {
|
|
99
74
|
const trackEndpoint = `${networkBaseUrl}/api/analytics/track`;
|
|
@@ -165,6 +140,19 @@ export function Ad({
|
|
|
165
140
|
return <LoadingAdContent />;
|
|
166
141
|
}
|
|
167
142
|
if (error) {
|
|
143
|
+
// @ts-ignore
|
|
144
|
+
if (error instanceof Error) {
|
|
145
|
+
if (error.message === AdDataQueryError.NO_AD) {
|
|
146
|
+
return (
|
|
147
|
+
<EmtpyAdContent
|
|
148
|
+
data={{ url: networkBaseUrl + `${owner}/${slotId}` }}
|
|
149
|
+
/>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
if (error.message === AdDataQueryError.ERROR) {
|
|
153
|
+
return <ErrorAdContent error={error} />;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
168
156
|
return <ErrorAdContent error={error} />;
|
|
169
157
|
}
|
|
170
158
|
if (adData) {
|
|
@@ -175,6 +163,12 @@ export function Ad({
|
|
|
175
163
|
{adData.type === "cast" && <CastAdContent data={adData} />}
|
|
176
164
|
|
|
177
165
|
{adData.type === "miniapp" && <MiniappAdContent data={adData} />}
|
|
166
|
+
|
|
167
|
+
{adData.type === "token" && <TokenAdContent data={adData} />}
|
|
168
|
+
|
|
169
|
+
{adData.type === "farcasterProfile" && (
|
|
170
|
+
<FarcasterProfileAdContent data={adData} />
|
|
171
|
+
)}
|
|
178
172
|
</>
|
|
179
173
|
);
|
|
180
174
|
} else {
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { FarcasterProfileAd } from "@adland/data";
|
|
2
|
+
import sdk from "@farcaster/miniapp-sdk";
|
|
3
|
+
import BasicAdBody from "../BasicAdBody";
|
|
4
|
+
import { adCardIcon } from "../../utils/constants";
|
|
5
|
+
|
|
6
|
+
// Farcaster Profile Ad Component
|
|
7
|
+
const FarcasterProfileAdContent = ({ data }: { data: FarcasterProfileAd }) => {
|
|
8
|
+
const Icon = adCardIcon["farcasterProfile"];
|
|
9
|
+
const { data: profileData, metadata: profileMetadata } = data;
|
|
10
|
+
const fid = profileData.fid;
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<BasicAdBody
|
|
14
|
+
name={"PROFILE"}
|
|
15
|
+
onClick={() => {
|
|
16
|
+
sdk.actions.viewProfile({ fid: parseInt(fid) });
|
|
17
|
+
}}
|
|
18
|
+
>
|
|
19
|
+
<div className="flex flex-row items-center gap-2 flex-1 min-w-0">
|
|
20
|
+
{profileMetadata?.pfpUrl ? (
|
|
21
|
+
<img
|
|
22
|
+
src={profileMetadata.pfpUrl}
|
|
23
|
+
alt={profileMetadata.username || "Profile picture"}
|
|
24
|
+
className="w-10 h-10 md:w-12 md:h-12 rounded-full object-cover flex-shrink-0"
|
|
25
|
+
/>
|
|
26
|
+
) : (
|
|
27
|
+
<Icon className="w-5 h-5 md:w-7 md:h-7 flex-shrink-0" />
|
|
28
|
+
)}
|
|
29
|
+
<div className="flex flex-col gap-1 min-w-0 flex-1">
|
|
30
|
+
{!Boolean(profileMetadata) && (
|
|
31
|
+
<div className="flex flex-row items-center gap-2">
|
|
32
|
+
<Icon className="w-5 h-5 md:w-7 md:h-7 flex-shrink-0" />
|
|
33
|
+
<p className="font-bold text-primary">PROFILE</p>
|
|
34
|
+
</div>
|
|
35
|
+
)}
|
|
36
|
+
<div className="flex flex-row items-center gap-1">
|
|
37
|
+
{profileMetadata?.username ? (
|
|
38
|
+
<p className="text-sm font-bold text-primary truncate">
|
|
39
|
+
{profileMetadata.username}
|
|
40
|
+
</p>
|
|
41
|
+
) : (
|
|
42
|
+
<p className="text-sm font-bold text-primary">FID: {fid}</p>
|
|
43
|
+
)}
|
|
44
|
+
{profileMetadata?.pro && (
|
|
45
|
+
<span className="text-xs bg-primary text-primary-foreground px-1 rounded">
|
|
46
|
+
PRO
|
|
47
|
+
</span>
|
|
48
|
+
)}
|
|
49
|
+
</div>
|
|
50
|
+
{profileMetadata?.bio ? (
|
|
51
|
+
<p className="text-xs text-muted-foreground/80 line-clamp-1">
|
|
52
|
+
{profileMetadata.bio}
|
|
53
|
+
</p>
|
|
54
|
+
) : (
|
|
55
|
+
(profileMetadata?.followers !== undefined ||
|
|
56
|
+
profileMetadata?.following !== undefined) && (
|
|
57
|
+
<p className="text-xs text-muted-foreground/80">
|
|
58
|
+
{profileMetadata.followers !== undefined && (
|
|
59
|
+
<span>
|
|
60
|
+
{profileMetadata.followers.toLocaleString()} followers
|
|
61
|
+
</span>
|
|
62
|
+
)}
|
|
63
|
+
{profileMetadata.followers !== undefined &&
|
|
64
|
+
profileMetadata.following !== undefined &&
|
|
65
|
+
" • "}
|
|
66
|
+
{profileMetadata.following !== undefined && (
|
|
67
|
+
<span>
|
|
68
|
+
{profileMetadata.following.toLocaleString()} following
|
|
69
|
+
</span>
|
|
70
|
+
)}
|
|
71
|
+
</p>
|
|
72
|
+
)
|
|
73
|
+
)}
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</BasicAdBody>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export default FarcasterProfileAdContent;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { TokenAd } from "@adland/data";
|
|
2
|
+
import sdk from "@farcaster/miniapp-sdk";
|
|
3
|
+
import BasicAdBody from "../BasicAdBody";
|
|
4
|
+
import { adCardIcon } from "../../utils/constants";
|
|
5
|
+
|
|
6
|
+
// Token Ad Component
|
|
7
|
+
const TokenAdContent = ({ data }: { data: TokenAd }) => {
|
|
8
|
+
const Icon = adCardIcon["token"];
|
|
9
|
+
const { data: tokenData, metadata: tokenMetadata } = data;
|
|
10
|
+
|
|
11
|
+
// Build block explorer URL based on chainId
|
|
12
|
+
const getTokenUrl = () => {
|
|
13
|
+
const address = tokenData.address;
|
|
14
|
+
const chainId = tokenData.chainId;
|
|
15
|
+
|
|
16
|
+
// Common block explorers by chainId
|
|
17
|
+
const explorers: Record<number, string> = {
|
|
18
|
+
1: "https://etherscan.io/token/",
|
|
19
|
+
8453: "https://basescan.org/token/",
|
|
20
|
+
42161: "https://arbiscan.io/token/",
|
|
21
|
+
10: "https://optimistic.etherscan.io/token/",
|
|
22
|
+
137: "https://polygonscan.com/token/",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const baseUrl = explorers[chainId];
|
|
26
|
+
if (baseUrl) {
|
|
27
|
+
return `${baseUrl}${address}`;
|
|
28
|
+
}
|
|
29
|
+
// Fallback to Etherscan if chain not recognized
|
|
30
|
+
return `https://etherscan.io/token/${address}`;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<BasicAdBody
|
|
35
|
+
name={"TOKEN"}
|
|
36
|
+
onClick={() => {
|
|
37
|
+
sdk.actions.viewToken({ token: tokenData.address });
|
|
38
|
+
}}
|
|
39
|
+
>
|
|
40
|
+
<div className="flex flex-row items-center gap-2 flex-1 min-w-0">
|
|
41
|
+
{tokenMetadata?.logoURI ? (
|
|
42
|
+
<img
|
|
43
|
+
src={tokenMetadata.logoURI}
|
|
44
|
+
alt={tokenMetadata.name || "Token logo"}
|
|
45
|
+
className="w-10 h-10 md:w-12 md:h-12 rounded object-cover flex-shrink-0"
|
|
46
|
+
/>
|
|
47
|
+
) : (
|
|
48
|
+
<Icon className="w-5 h-5 md:w-7 md:h-7 flex-shrink-0" />
|
|
49
|
+
)}
|
|
50
|
+
<div className="flex flex-col gap-1 min-w-0 flex-1">
|
|
51
|
+
{!Boolean(tokenMetadata) && (
|
|
52
|
+
<div className="flex flex-row items-center gap-2">
|
|
53
|
+
<Icon className="w-5 h-5 md:w-7 md:h-7 flex-shrink-0" />
|
|
54
|
+
<p className="font-bold text-primary">TOKEN</p>
|
|
55
|
+
</div>
|
|
56
|
+
)}
|
|
57
|
+
{tokenMetadata?.symbol ? (
|
|
58
|
+
<p className="text-sm font-bold text-primary truncate">
|
|
59
|
+
{tokenMetadata.symbol.toUpperCase()}
|
|
60
|
+
</p>
|
|
61
|
+
) : (
|
|
62
|
+
<p className="text-sm font-bold text-primary">TOKEN</p>
|
|
63
|
+
)}
|
|
64
|
+
{tokenMetadata?.name ? (
|
|
65
|
+
<p className="text-xs text-muted-foreground/80 line-clamp-1">
|
|
66
|
+
{tokenMetadata.name}
|
|
67
|
+
</p>
|
|
68
|
+
) : (
|
|
69
|
+
<p className="text-xs text-muted-foreground/80 line-clamp-1">
|
|
70
|
+
{tokenData.address.slice(0, 6)}...{tokenData.address.slice(-4)}
|
|
71
|
+
</p>
|
|
72
|
+
)}
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</BasicAdBody>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export default TokenAdContent;
|
package/src/index.ts
CHANGED
package/src/types.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { AdData } from "@adland/data";
|
|
2
|
+
|
|
3
|
+
export type Network = "testnet" | "mainnet";
|
|
4
|
+
|
|
5
|
+
export interface AdProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
/**
|
|
7
|
+
* The owner/creator of the ad
|
|
8
|
+
*/
|
|
9
|
+
owner: string;
|
|
10
|
+
/**
|
|
11
|
+
* Unique identifier for the ad
|
|
12
|
+
*/
|
|
13
|
+
slotId: string;
|
|
14
|
+
/**
|
|
15
|
+
* Network to use for tracking requests (currently only "testnet" is supported)
|
|
16
|
+
*/
|
|
17
|
+
network?: Network;
|
|
18
|
+
/**
|
|
19
|
+
* Optional base URL override. If not provided, uses relative URL in browser or production URL in SSR
|
|
20
|
+
*/
|
|
21
|
+
baseUrl?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export enum AdDataQueryError {
|
|
25
|
+
NO_AD = "NO_AD",
|
|
26
|
+
ERROR = "ERROR",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface AdDataQueryResponse {
|
|
30
|
+
error?: AdDataQueryError;
|
|
31
|
+
data?: AdData;
|
|
32
|
+
}
|
package/src/utils/constants.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { AdType } from "@adland/data";
|
|
2
2
|
import { ForwardRefExoticComponent, RefAttributes } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Link,
|
|
5
|
+
MessageCircle,
|
|
6
|
+
LayoutGrid,
|
|
7
|
+
LucideProps,
|
|
8
|
+
Coins,
|
|
9
|
+
User,
|
|
10
|
+
} from "lucide-react";
|
|
4
11
|
|
|
5
12
|
export const adCardIcon: Record<
|
|
6
13
|
AdType,
|
|
@@ -11,4 +18,11 @@ export const adCardIcon: Record<
|
|
|
11
18
|
link: Link,
|
|
12
19
|
cast: MessageCircle,
|
|
13
20
|
miniapp: LayoutGrid,
|
|
21
|
+
token: Coins,
|
|
22
|
+
farcasterProfile: User,
|
|
14
23
|
};
|
|
24
|
+
|
|
25
|
+
export const adlandApiUrl =
|
|
26
|
+
process.env.NODE_ENV === "development"
|
|
27
|
+
? "http://localhost:3069"
|
|
28
|
+
: "https://api.adland.space";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Network } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Get the base URL for the network
|
|
5
|
+
* Uses relative URL in browser (for local dev), otherwise defaults to production
|
|
6
|
+
*/
|
|
7
|
+
export const getBaseUrl = (network: Network): string => {
|
|
8
|
+
if (typeof window !== "undefined") {
|
|
9
|
+
// In browser - use relative URL for local development
|
|
10
|
+
return "";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (network === "testnet") {
|
|
14
|
+
return "https://testnet.adland.space";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (network === "mainnet") {
|
|
18
|
+
return "https://app.adland.space";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return "";
|
|
22
|
+
};
|