@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 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.7.2",
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.11.0"
36
+ "@adland/data": "0.12.0"
32
37
  },
33
38
  "devDependencies": {
34
39
  "@typescript-eslint/eslint-plugin": "^7.13.1",
@@ -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
- type Network = "testnet" | "mainnet";
13
-
14
- /**
15
- * Get the base URL for the network
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 res = await fetch(
81
- `${networkBaseUrl}/api/data/owner/${owner.toLowerCase()}/slot/${slotId}`,
82
- {
83
- method: "GET",
84
- headers: {
85
- "Content-Type": "application/json",
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
- return res.json();
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
@@ -1,6 +1,6 @@
1
1
  // Main exports
2
2
  export { Ad } from "./components/Ad";
3
- export type { AdProps } from "./components/Ad";
3
+ export type { AdProps, AdDataQueryResponse, AdDataQueryError } from "./types";
4
4
 
5
5
  // Utility exports
6
6
  export {
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
+ }
@@ -1,6 +1,13 @@
1
1
  import { AdType } from "@adland/data";
2
2
  import { ForwardRefExoticComponent, RefAttributes } from "react";
3
- import { Link, MessageCircle, LayoutGrid, LucideProps } from "lucide-react";
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
+ };
package/tsup.config.ts CHANGED
@@ -1,7 +1,10 @@
1
1
  import { defineConfig } from "tsup";
2
2
 
3
3
  export default defineConfig({
4
- entry: ["src/index.ts"],
4
+ entry: {
5
+ index: "src/index.ts",
6
+ types: "src/types.ts",
7
+ },
5
8
  format: ["cjs", "esm"],
6
9
  dts: true,
7
10
  splitting: false,