@adland/react 0.6.2 → 0.7.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 +11 -0
- package/package.json +2 -2
- package/src/components/Ad.tsx +7 -78
- package/src/components/BasicAdBody.tsx +27 -0
- package/src/components/content/CastAdContent.tsx +56 -0
- package/src/components/content/LinkAdContent.tsx +49 -0
- package/src/components/content/MiniappAdContent.tsx +48 -0
- package/src/utils/relativeTime.ts +44 -0
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adland/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"@types/react-dom": "^18.3.1",
|
|
29
29
|
"lucide-react": "0.561.0",
|
|
30
30
|
"tsup": "^8.0.1",
|
|
31
|
-
"@adland/data": "0.
|
|
31
|
+
"@adland/data": "0.9.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@typescript-eslint/eslint-plugin": "^7.13.1",
|
package/src/components/Ad.tsx
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { useEffect, useRef, useCallback } from "react";
|
|
2
2
|
import { sendTrackRequest } from "../utils/sdk";
|
|
3
3
|
import sdk from "@farcaster/miniapp-sdk";
|
|
4
|
-
import { adCardIcon } from "../utils/constants";
|
|
5
4
|
import { Loader, SquareDashed } from "lucide-react";
|
|
6
5
|
import { type AdData } from "@adland/data";
|
|
7
6
|
import { useFetch } from "../hooks/useFetch";
|
|
7
|
+
import BasicAdBody from "./BasicAdBody";
|
|
8
|
+
import LinkAdContent from "./content/LinkAdContent";
|
|
9
|
+
import MiniappAdContent from "./content/MiniappAdContent";
|
|
10
|
+
import CastAdContent from "./content/CastAdContent";
|
|
8
11
|
|
|
9
12
|
type Network = "testnet" | "mainnet";
|
|
10
13
|
|
|
@@ -167,13 +170,11 @@ export function Ad({
|
|
|
167
170
|
if (adData) {
|
|
168
171
|
return (
|
|
169
172
|
<>
|
|
170
|
-
{adData.type === "link" && <LinkAdContent data={adData
|
|
173
|
+
{adData.type === "link" && <LinkAdContent data={adData} />}
|
|
171
174
|
|
|
172
|
-
{adData.type === "cast" && <CastAdContent data={adData
|
|
175
|
+
{adData.type === "cast" && <CastAdContent data={adData} />}
|
|
173
176
|
|
|
174
|
-
{adData.type === "miniapp" &&
|
|
175
|
-
<MiniAppAdContent data={adData.data} />
|
|
176
|
-
)}
|
|
177
|
+
{adData.type === "miniapp" && <MiniappAdContent data={adData} />}
|
|
177
178
|
</>
|
|
178
179
|
);
|
|
179
180
|
} else {
|
|
@@ -188,23 +189,6 @@ export function Ad({
|
|
|
188
189
|
);
|
|
189
190
|
}
|
|
190
191
|
|
|
191
|
-
const BasicAdBody = ({
|
|
192
|
-
children,
|
|
193
|
-
...rest
|
|
194
|
-
}: { children: React.ReactNode } & React.HTMLAttributes<HTMLDivElement>) => {
|
|
195
|
-
return (
|
|
196
|
-
<div
|
|
197
|
-
className="border p-2 md:p-4 flex hover:cursor-pointer hover:bg-primary/10 flex-row rounded-md justify-between items-center gap-2 relative border-b-2 border-b-primary"
|
|
198
|
-
{...rest}
|
|
199
|
-
>
|
|
200
|
-
{children}
|
|
201
|
-
<div className="inline-flex items-center border p-1 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent bg-primary text-primary-foreground hover:bg-primary/80">
|
|
202
|
-
AD
|
|
203
|
-
</div>
|
|
204
|
-
</div>
|
|
205
|
-
);
|
|
206
|
-
};
|
|
207
|
-
|
|
208
192
|
function ErrorAdContent({ error }: { error: unknown }) {
|
|
209
193
|
return (
|
|
210
194
|
<BasicAdBody>
|
|
@@ -243,58 +227,3 @@ function EmtpyAdContent({ data }: { data: { url: string } }) {
|
|
|
243
227
|
</BasicAdBody>
|
|
244
228
|
);
|
|
245
229
|
}
|
|
246
|
-
|
|
247
|
-
// Link Ad Component
|
|
248
|
-
function LinkAdContent({ data }: { data: { url: string } }) {
|
|
249
|
-
const Icon = adCardIcon["link"];
|
|
250
|
-
return (
|
|
251
|
-
<BasicAdBody
|
|
252
|
-
onClick={() => {
|
|
253
|
-
sdk.actions.openUrl(data.url);
|
|
254
|
-
}}
|
|
255
|
-
>
|
|
256
|
-
<div className="flex flex-row items-center gap-2">
|
|
257
|
-
<Icon className="w-5 h-5 md:w-7 md:h-7" />
|
|
258
|
-
<p className="font-bold text-primary">LINK</p>
|
|
259
|
-
</div>
|
|
260
|
-
</BasicAdBody>
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Cast Ad Component
|
|
265
|
-
function CastAdContent({ data }: { data: { hash: string } }) {
|
|
266
|
-
const Icon = adCardIcon["cast"];
|
|
267
|
-
return (
|
|
268
|
-
<BasicAdBody
|
|
269
|
-
onClick={() => {
|
|
270
|
-
sdk.actions.viewCast({
|
|
271
|
-
hash: data.hash,
|
|
272
|
-
});
|
|
273
|
-
}}
|
|
274
|
-
>
|
|
275
|
-
<div className="flex flex-row items-center gap-2">
|
|
276
|
-
<Icon className="w-5 h-5 md:w-7 md:h-7" />
|
|
277
|
-
<p className="font-bold text-primary">CAST</p>
|
|
278
|
-
</div>
|
|
279
|
-
</BasicAdBody>
|
|
280
|
-
);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// MiniApp Ad Component
|
|
284
|
-
function MiniAppAdContent({ data }: { data: { url: string } }) {
|
|
285
|
-
const Icon = adCardIcon["miniapp"];
|
|
286
|
-
return (
|
|
287
|
-
<BasicAdBody
|
|
288
|
-
onClick={() => {
|
|
289
|
-
sdk.actions.openMiniApp({
|
|
290
|
-
url: data.url,
|
|
291
|
-
});
|
|
292
|
-
}}
|
|
293
|
-
>
|
|
294
|
-
<div className="flex flex-row items-center gap-2">
|
|
295
|
-
<Icon className="w-5 h-5 md:w-7 md:h-7" />
|
|
296
|
-
<p className="font-bold text-primary">MINIAPP</p>
|
|
297
|
-
</div>
|
|
298
|
-
</BasicAdBody>
|
|
299
|
-
);
|
|
300
|
-
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const BasicAdBody = ({
|
|
2
|
+
name,
|
|
3
|
+
children,
|
|
4
|
+
...rest
|
|
5
|
+
}: {
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
name?: string;
|
|
8
|
+
} & React.HTMLAttributes<HTMLDivElement>) => {
|
|
9
|
+
return (
|
|
10
|
+
<div
|
|
11
|
+
className="border p-2 md:p-4 flex hover:cursor-pointer hover:bg-primary/10 flex-row rounded-md justify-between items-center gap-2 relative border-b-2 border-b-primary"
|
|
12
|
+
{...rest}
|
|
13
|
+
>
|
|
14
|
+
{children}
|
|
15
|
+
<div className="flex flex-row items-center border-2 border-primary">
|
|
16
|
+
{name && (
|
|
17
|
+
<div className="text-xs font-semibold text-primary px-1">{name}</div>
|
|
18
|
+
)}
|
|
19
|
+
<div className="inline-flex items-center border p-1 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent bg-primary text-primary-foreground hover:bg-primary/80">
|
|
20
|
+
AD
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default BasicAdBody;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { adCardIcon } from "../../utils/constants";
|
|
2
|
+
import { formatRelativeTime } from "../../utils/relativeTime";
|
|
3
|
+
import sdk from "@farcaster/miniapp-sdk";
|
|
4
|
+
import BasicAdBody from "../BasicAdBody";
|
|
5
|
+
import { CastAd } from "@adland/data";
|
|
6
|
+
|
|
7
|
+
// Cast Ad Component
|
|
8
|
+
const CastAdContent = ({ data }: { data: CastAd }) => {
|
|
9
|
+
const { data: castData, metadata: castMetadata } = data;
|
|
10
|
+
const Icon = adCardIcon["cast"];
|
|
11
|
+
return (
|
|
12
|
+
<BasicAdBody
|
|
13
|
+
name={"CAST"}
|
|
14
|
+
onClick={() => {
|
|
15
|
+
sdk.actions.viewCast({
|
|
16
|
+
hash: castData.hash,
|
|
17
|
+
});
|
|
18
|
+
}}
|
|
19
|
+
>
|
|
20
|
+
<div className="flex flex-row items-center gap-2">
|
|
21
|
+
{castMetadata?.pfpUrl ? (
|
|
22
|
+
<img
|
|
23
|
+
src={castMetadata.pfpUrl}
|
|
24
|
+
alt="Cast pfp"
|
|
25
|
+
className="w-10 h-10 rounded-md"
|
|
26
|
+
/>
|
|
27
|
+
) : (
|
|
28
|
+
<Icon className="w-5 h-5 md:w-7 md:h-7" />
|
|
29
|
+
)}
|
|
30
|
+
<div className="flex flex-col gap-0">
|
|
31
|
+
{castMetadata?.username ? (
|
|
32
|
+
<div className="flex flex-row items-center gap-1">
|
|
33
|
+
<p className="font-bold text-primary">{castMetadata.username}</p>
|
|
34
|
+
{castMetadata.timestamp && (
|
|
35
|
+
<p className="text-xs text-muted-foreground/80">
|
|
36
|
+
{formatRelativeTime(
|
|
37
|
+
new Date(castMetadata.timestamp).getTime(),
|
|
38
|
+
)}
|
|
39
|
+
</p>
|
|
40
|
+
)}
|
|
41
|
+
</div>
|
|
42
|
+
) : (
|
|
43
|
+
<p className="font-bold text-primary">CAST</p>
|
|
44
|
+
)}
|
|
45
|
+
{castMetadata?.text ? (
|
|
46
|
+
<p className="text-black">{castMetadata.text}</p>
|
|
47
|
+
) : (
|
|
48
|
+
<p className="text-muted-foreground/80">cast</p>
|
|
49
|
+
)}
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
</BasicAdBody>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export default CastAdContent;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import sdk from "@farcaster/miniapp-sdk";
|
|
2
|
+
import BasicAdBody from "../BasicAdBody";
|
|
3
|
+
import { adCardIcon } from "../../utils/constants";
|
|
4
|
+
import { LinkAd } from "@adland/data";
|
|
5
|
+
|
|
6
|
+
// Link Ad Component
|
|
7
|
+
const LinkAdContent = ({ data }: { data: LinkAd }) => {
|
|
8
|
+
const Icon = adCardIcon["link"];
|
|
9
|
+
const { data: linkData, metadata: linkMetadata } = data;
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<BasicAdBody
|
|
13
|
+
name={"LINK"}
|
|
14
|
+
onClick={() => {
|
|
15
|
+
sdk.actions.openUrl(linkData.url);
|
|
16
|
+
}}
|
|
17
|
+
>
|
|
18
|
+
<div className="flex flex-row items-center gap-2 flex-1 min-w-0">
|
|
19
|
+
{linkMetadata?.image && (
|
|
20
|
+
<img
|
|
21
|
+
src={linkMetadata.image}
|
|
22
|
+
alt={linkMetadata.title || "Link preview"}
|
|
23
|
+
className="w-10 h-10 md:w-12 md:h-12 rounded object-cover flex-shrink-0"
|
|
24
|
+
/>
|
|
25
|
+
)}
|
|
26
|
+
<div className="flex flex-col gap-1 min-w-0 flex-1">
|
|
27
|
+
{!Boolean(linkMetadata) && (
|
|
28
|
+
<div className="flex flex-row items-center gap-2">
|
|
29
|
+
<Icon className="w-5 h-5 md:w-7 md:h-7 flex-shrink-0" />
|
|
30
|
+
<p className="font-bold text-primary">LINK</p>
|
|
31
|
+
</div>
|
|
32
|
+
)}
|
|
33
|
+
{linkMetadata?.title && (
|
|
34
|
+
<p className="text-sm text-muted-foreground truncate">
|
|
35
|
+
{linkMetadata.title}
|
|
36
|
+
</p>
|
|
37
|
+
)}
|
|
38
|
+
{linkMetadata?.description && (
|
|
39
|
+
<p className="text-xs text-muted-foreground/80 line-clamp-1">
|
|
40
|
+
{linkMetadata.description}
|
|
41
|
+
</p>
|
|
42
|
+
)}
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</BasicAdBody>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export default LinkAdContent;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { MiniAppAd } from "@adland/data";
|
|
2
|
+
import sdk from "@farcaster/miniapp-sdk";
|
|
3
|
+
import BasicAdBody from "../BasicAdBody";
|
|
4
|
+
import { adCardIcon } from "../../utils/constants";
|
|
5
|
+
|
|
6
|
+
const MiniappAdContent = ({ data }: { data: MiniAppAd }) => {
|
|
7
|
+
const Icon = adCardIcon["miniapp"];
|
|
8
|
+
const { data: miniappData, metadata: miniappMetadata } = data;
|
|
9
|
+
console.log({ miniappMetadata });
|
|
10
|
+
return (
|
|
11
|
+
<BasicAdBody
|
|
12
|
+
name={"MINIAPP"}
|
|
13
|
+
onClick={() => {
|
|
14
|
+
sdk.actions.openMiniApp({
|
|
15
|
+
url: miniappData.url,
|
|
16
|
+
});
|
|
17
|
+
}}
|
|
18
|
+
>
|
|
19
|
+
<div className="flex flex-row items-center gap-2">
|
|
20
|
+
{miniappMetadata?.icon ? (
|
|
21
|
+
<img
|
|
22
|
+
src={miniappMetadata.icon}
|
|
23
|
+
alt="Miniapp icon"
|
|
24
|
+
className="w-10 h-10 rounded-md"
|
|
25
|
+
/>
|
|
26
|
+
) : (
|
|
27
|
+
<Icon className="w-5 h-5 md:w-7 md:h-7" />
|
|
28
|
+
)}
|
|
29
|
+
<div className="flex flex-col gap-0">
|
|
30
|
+
{miniappMetadata?.title ? (
|
|
31
|
+
<p className="font-bold text-primary">{miniappMetadata.title}</p>
|
|
32
|
+
) : (
|
|
33
|
+
<p className="font-bold text-primary">MINIAPP</p>
|
|
34
|
+
)}
|
|
35
|
+
{miniappMetadata?.description ? (
|
|
36
|
+
<p className="text-xs text-muted-foreground/80">
|
|
37
|
+
{miniappMetadata.description}
|
|
38
|
+
</p>
|
|
39
|
+
) : (
|
|
40
|
+
<p className="text-xs text-muted-foreground/80">miniapp</p>
|
|
41
|
+
)}
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</BasicAdBody>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default MiniappAdContent;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats a timestamp as a relative time string (e.g., "2 days ago", "55 minutes ago")
|
|
3
|
+
* @param timestamp - Unix timestamp in seconds or milliseconds
|
|
4
|
+
* @returns Formatted relative time string
|
|
5
|
+
*/
|
|
6
|
+
export function formatRelativeTime(timestamp: number): string {
|
|
7
|
+
// Convert to milliseconds if timestamp is in seconds (less than year 2000 in ms)
|
|
8
|
+
const timestampMs = timestamp < 946684800000 ? timestamp * 1000 : timestamp;
|
|
9
|
+
const now = Date.now();
|
|
10
|
+
const diffMs = now - timestampMs;
|
|
11
|
+
const diffSeconds = Math.floor(diffMs / 1000);
|
|
12
|
+
const diffMinutes = Math.floor(diffSeconds / 60);
|
|
13
|
+
const diffHours = Math.floor(diffMinutes / 60);
|
|
14
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
15
|
+
const diffWeeks = Math.floor(diffDays / 7);
|
|
16
|
+
const diffMonths = Math.floor(diffDays / 30);
|
|
17
|
+
const diffYears = Math.floor(diffDays / 365);
|
|
18
|
+
|
|
19
|
+
if (diffSeconds < 60) {
|
|
20
|
+
return diffSeconds <= 1 ? "just now" : `${diffSeconds} seconds ago`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (diffMinutes < 60) {
|
|
24
|
+
return diffMinutes === 1 ? "1 minute ago" : `${diffMinutes} minutes ago`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (diffHours < 24) {
|
|
28
|
+
return diffHours === 1 ? "1 hour ago" : `${diffHours} hours ago`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (diffDays < 7) {
|
|
32
|
+
return diffDays === 1 ? "1 day ago" : `${diffDays} days ago`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (diffWeeks < 4) {
|
|
36
|
+
return diffWeeks === 1 ? "1 week ago" : `${diffWeeks} weeks ago`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (diffMonths < 12) {
|
|
40
|
+
return diffMonths === 1 ? "1 month ago" : `${diffMonths} months ago`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return diffYears === 1 ? "1 year ago" : `${diffYears} years ago`;
|
|
44
|
+
}
|