diff --git a/src/components/NsiteCard.tsx b/src/components/NsiteCard.tsx index 53dc5a9d..7454b508 100644 --- a/src/components/NsiteCard.tsx +++ b/src/components/NsiteCard.tsx @@ -1,14 +1,18 @@ import type { NostrEvent } from "@nostrify/nostrify"; -import { ExternalLink, FileText, Globe } from "lucide-react"; +import { ExternalLink, FileText, Globe, Server } from "lucide-react"; import { nip19 } from "nostr-tools"; +import { ExternalFavicon } from "@/components/ExternalFavicon"; +import { Skeleton } from "@/components/ui/skeleton"; +import { useLinkPreview } from "@/hooks/useLinkPreview"; +import { cn } from "@/lib/utils"; + interface NsiteCardProps { event: NostrEvent; } /** Encode a 32-byte hex pubkey as a base36 string (50 chars, zero-padded). */ function hexToBase36(hex: string): string { - // Process the hex string in chunks to build a BigInt let n = 0n; for (let i = 0; i < hex.length; i++) { n = n * 16n + BigInt(parseInt(hex[i], 16)); @@ -22,17 +26,15 @@ function getNsiteUrl(event: NostrEvent): string { const dTag = event.tags.find(([n]) => n === "d")?.[1]; if (event.kind === 35128 && dTag) { - // Named site: .nsite.lol const pubkeyB36 = hexToBase36(event.pubkey); return `https://${pubkeyB36}${dTag}.nsite.lol`; } - // Root site (kind 15128): .nsite.lol const npub = nip19.npubEncode(event.pubkey); return `https://${npub}.nsite.lol`; } -/** Renders an nsite deployment card for kind 15128 (root site) or 35128 (named site). */ +/** Renders an nsite deployment card with a rich link preview. */ export function NsiteCard({ event }: NsiteCardProps) { const title = event.tags.find(([n]) => n === "title")?.[1]; const description = event.tags.find(([n]) => n === "description")?.[1]; @@ -45,29 +47,58 @@ export function NsiteCard({ event }: NsiteCardProps) { const siteUrl = getNsiteUrl(event); const displayName = title || (isNamed ? dTag : "Root Site"); + const { data: preview, isLoading } = useLinkPreview(siteUrl); + const image = preview?.thumbnail_url; + const previewTitle = preview?.title; + + if (isLoading) { + return ; + } + return ( -
-
- {/* Site name + type badge */} + e.stopPropagation()} + > + {/* Link preview thumbnail */} + {image && ( +
+ { + (e.currentTarget.parentElement as HTMLElement).style.display = "none"; + }} + /> +
+ )} + +
+ {/* Title with favicon */}
- - - {displayName} - - - {isNamed ? "Named Site" : "Root Site"} - + +

+ {previewTitle || displayName} +

- {/* Description */} - {description && ( -

- {description} + {/* Description — prefer event description (it's curated), fall back to OEmbed author */} + {(description || preview?.author_name) && ( +

+ {description || preview?.author_name}

)} - {/* File count + server info */} -
+ + ); +} - {/* Action buttons */} -
- - {sourceUrl && ( - - )} -
+function NsiteCardSkeleton() { + return ( +
+ +
+ + + +
);