feat: render quoted badge definitions as compact showcase instead of link preview

Kind 30009 badge definition embeds now show a smaller version of the
badge showcase (centered image with rotating light rays, name, and
description) instead of the generic link-preview card with cover image.
This commit is contained in:
Alex Gleason
2026-03-22 23:21:51 -05:00
parent c12e9128b3
commit 89bc522ff7
+100 -1
View File
@@ -1,12 +1,13 @@
import { useMemo } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { nip19 } from 'nostr-tools';
import { Award, MessageSquareOff } from 'lucide-react';
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';
import { getAvatarShape } from '@/lib/avatarShape';
import { Skeleton } from '@/components/ui/skeleton';
import { EmojifiedText } from '@/components/CustomEmoji';
import { NoteCard } from '@/components/NoteCard';
import { MessageSquareOff } from 'lucide-react';
import { parseBadgeDefinition } from '@/components/BadgeContent';
import { useAddrEvent, type AddrCoords } from '@/hooks/useEvent';
import { useAuthor } from '@/hooks/useAuthor';
import { genUserName } from '@/lib/genUserName';
@@ -80,9 +81,107 @@ export function EmbeddedNaddr({ addr, className }: EmbeddedNaddrProps) {
);
}
// Badge definitions get a compact showcase instead of a link-preview card
if (event.kind === 30009) {
return <EmbeddedBadgeCard event={event} className={className} />;
}
return <EmbeddedNaddrCard event={event} className={className} />;
}
/** Compact badge showcase for kind 30009 embeds — smaller version of the feed BadgeContent. */
function EmbeddedBadgeCard({ event, className }: { event: NostrEvent; className?: string }) {
const navigate = useNavigate();
const badge = useMemo(() => parseBadgeDefinition(event), [event]);
const naddrId = useMemo(() => {
const dTag = event.tags.find(([n]) => n === 'd')?.[1] ?? '';
return nip19.naddrEncode({ kind: event.kind, pubkey: event.pubkey, identifier: dTag });
}, [event]);
if (!badge) return <EmbeddedNaddrCard event={event} className={className} />;
const heroImage = badge.image
?? badge.thumbs.find((t) => t.dimensions === '512x512')?.url
?? badge.thumbs[0]?.url;
return (
<div
className={cn(
'group block rounded-2xl border border-border overflow-hidden',
'hover:bg-secondary/40 transition-colors cursor-pointer',
className,
)}
role="link"
tabIndex={0}
onClick={(e) => {
e.stopPropagation();
navigate(`/${naddrId}`);
}}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
e.stopPropagation();
navigate(`/${naddrId}`);
}
}}
>
{/* Compact badge showcase */}
<div className="relative isolate flex flex-col items-center py-6 overflow-hidden">
{/* Rotating light rays — scaled down from the feed version */}
<div
className="absolute -z-10 pointer-events-none"
aria-hidden="true"
style={{
width: 240,
height: 240,
top: '50%',
left: '50%',
transform: 'translate(-50%, -55%)',
}}
>
<div
className="w-full h-full animate-badge-spotlight"
style={{
background: `repeating-conic-gradient(
hsl(var(--primary) / 0.08) 0deg 6deg,
transparent 6deg 18deg
)`,
maskImage: 'radial-gradient(circle, black 15%, transparent 70%)',
WebkitMaskImage: 'radial-gradient(circle, black 15%, transparent 70%)',
}}
/>
</div>
{/* Badge image */}
<div className="relative z-[1]">
{heroImage ? (
<img
src={heroImage}
alt={badge.name}
className="size-20 rounded-xl object-cover drop-shadow-lg"
loading="lazy"
decoding="async"
/>
) : (
<div className="size-20 rounded-xl bg-gradient-to-br from-primary/10 via-primary/5 to-transparent flex items-center justify-center">
<Award className="size-8 text-primary/30" />
</div>
)}
</div>
{/* Badge info */}
<div className="relative z-[1] mt-3 text-center px-4 max-w-xs">
<p className="text-sm font-semibold leading-snug">{badge.name}</p>
{badge.description && (
<p className="text-xs text-muted-foreground mt-0.5 line-clamp-1">{badge.description}</p>
)}
</div>
</div>
</div>
);
}
function EmbeddedNaddrCard({ event, className }: { event: NostrEvent; className?: string }) {
const navigate = useNavigate();
const author = useAuthor(event.pubkey);