refactor: Simplify badge renderers

Simplify NIP-58 badge renderers based on feedback:
- Rename "badge definition" to "badge" in comments and docs
- Remove image and ID from feed view (show only name + description)
- Remove award statistics fetching/display from detail view
- Remove badge address section from detail view

Feed view now shows minimal info (name, description) while detail
view focuses on badge metadata and image variants without external queries.
This commit is contained in:
Claude
2026-01-17 17:17:31 +00:00
parent be7322b88d
commit 053817fc5c
3 changed files with 18 additions and 135 deletions

View File

@@ -8,10 +8,6 @@ import {
} from "@/lib/nip58-helpers";
import { UserName } from "../UserName";
import { Award } from "lucide-react";
import { useMemo } from "react";
import { useLiveTimeline } from "@/hooks/useLiveTimeline";
import { getSeenRelays } from "applesauce-core/helpers/relays";
import { relayListCache } from "@/services/relay-list-cache";
interface BadgeDefinitionDetailRendererProps {
event: NostrEvent;
@@ -50,8 +46,8 @@ function ImageVariant({
}
/**
* Detail renderer for Kind 30009 - Badge Definition (NIP-58)
* Shows comprehensive badge information including all image variants
* Detail renderer for Kind 30009 - Badge (NIP-58)
* Shows badge information including all image variants
*/
export function BadgeDefinitionDetailRenderer({
event,
@@ -65,61 +61,6 @@ export function BadgeDefinitionDetailRenderer({
// Use name if available, fallback to identifier
const displayTitle = name || identifier || "Badge";
// Build relay list for fetching badge awards (kind 8)
const relays = useMemo(() => {
const relaySet = new Set<string>();
// Add seen relays from the badge definition event
const seenRelays = getSeenRelays(event);
if (seenRelays) {
for (const relay of seenRelays) {
relaySet.add(relay);
}
}
// Add issuer's outbox relays
const outboxRelays = relayListCache.getOutboxRelaysSync(event.pubkey);
if (outboxRelays) {
for (const relay of outboxRelays.slice(0, 3)) {
relaySet.add(relay);
}
}
return Array.from(relaySet);
}, [event]);
// Query for awards (kind 8) that reference this badge definition
const awardsFilter = useMemo(() => {
if (!identifier) {
return { kinds: [8], ids: [] }; // No match if no identifier
}
return {
kinds: [8],
"#a": [`30009:${event.pubkey}:${identifier}`],
};
}, [event.pubkey, identifier]);
// Fetch awards from relays
const { events: awards } = useLiveTimeline(
`badge-awards-${event.id}`,
awardsFilter,
relays,
{ limit: 100 },
);
// Count unique recipients
const uniqueRecipients = useMemo(() => {
if (!awards || awards.length === 0) return 0;
const recipients = new Set<string>();
for (const award of awards) {
const pTags = award.tags.filter((tag) => tag[0] === "p" && tag[1]);
for (const pTag of pTags) {
recipients.add(pTag[1]);
}
}
return recipients.size;
}, [awards]);
return (
<div className="flex flex-col gap-6 p-6 max-w-4xl mx-auto">
{/* Header Section */}
@@ -164,26 +105,6 @@ export function BadgeDefinitionDetailRenderer({
</code>
</div>
)}
{/* Awards Count */}
{awards && awards.length > 0 && (
<div className="flex flex-col gap-1">
<h3 className="text-muted-foreground">Times Awarded</h3>
<span className="text-sm">
{awards.length} award{awards.length !== 1 ? "s" : ""}
</span>
</div>
)}
{/* Recipients Count */}
{uniqueRecipients > 0 && (
<div className="flex flex-col gap-1">
<h3 className="text-muted-foreground">Recipients</h3>
<span className="text-sm">
{uniqueRecipients} user{uniqueRecipients !== 1 ? "s" : ""}
</span>
</div>
)}
</div>
{/* Image Variants Section */}
@@ -209,18 +130,6 @@ export function BadgeDefinitionDetailRenderer({
</div>
</div>
)}
{/* Award Address for Reference */}
{identifier && (
<div className="flex flex-col gap-2 p-4 bg-muted/30 rounded-lg">
<h3 className="text-sm font-medium text-muted-foreground">
Badge Address (for awarding)
</h3>
<code className="text-xs font-mono break-all">
30009:{event.pubkey}:{identifier}
</code>
</div>
)}
</div>
);
}

View File

@@ -7,61 +7,35 @@ import {
getBadgeIdentifier,
getBadgeName,
getBadgeDescription,
getBadgeImageUrl,
} from "@/lib/nip58-helpers";
import { Award } from "lucide-react";
/**
* Renderer for Kind 30009 - Badge Definition (NIP-58)
* Clean feed view with badge image, name, and description
* Renderer for Kind 30009 - Badge (NIP-58)
* Simple feed view with name and description
*/
export function BadgeDefinitionRenderer({ event }: BaseEventProps) {
const identifier = getBadgeIdentifier(event);
const name = getBadgeName(event);
const description = getBadgeDescription(event);
const imageUrl = getBadgeImageUrl(event);
// Use name if available, fallback to identifier
const displayTitle = name || identifier || "Badge";
return (
<BaseEventContainer event={event}>
<div className="flex gap-3">
{/* Badge Image */}
{imageUrl ? (
<img
src={imageUrl}
alt={displayTitle}
className="size-16 rounded-lg object-cover flex-shrink-0"
loading="lazy"
/>
) : (
<div className="size-16 rounded-lg bg-muted flex items-center justify-center flex-shrink-0">
<Award className="size-8 text-muted-foreground" />
</div>
<div className="flex flex-col gap-1">
<ClickableEventTitle
event={event}
className="text-base font-semibold text-foreground"
>
{displayTitle}
</ClickableEventTitle>
{description && (
<p className="text-sm text-muted-foreground line-clamp-2">
{description}
</p>
)}
{/* Badge Info */}
<div className="flex flex-col gap-1 flex-1 min-w-0">
<ClickableEventTitle
event={event}
className="text-base font-semibold text-foreground"
>
{displayTitle}
</ClickableEventTitle>
{description && (
<p className="text-sm text-muted-foreground line-clamp-2">
{description}
</p>
)}
{identifier && (
<code className="text-xs text-muted-foreground font-mono truncate">
{identifier}
</code>
)}
</div>
</div>
</BaseEventContainer>
);

View File

@@ -202,7 +202,7 @@ const kindRenderers: Record<number, React.ComponentType<BaseEventProps>> = {
30005: VideoCurationSetRenderer, // Video Curation Sets (NIP-51)
30006: PictureCurationSetRenderer, // Picture Curation Sets (NIP-51)
30007: KindMuteSetRenderer, // Kind Mute Sets (NIP-51)
30009: BadgeDefinitionRenderer, // Badge Definition (NIP-58)
30009: BadgeDefinitionRenderer, // Badge (NIP-58)
30015: InterestSetRenderer, // Interest Sets (NIP-51)
30023: Kind30023Renderer, // Long-form Article
30030: EmojiSetRenderer, // Emoji Sets (NIP-30)
@@ -296,7 +296,7 @@ const detailRenderers: Record<
30005: VideoCurationSetDetailRenderer, // Video Curation Sets Detail (NIP-51)
30006: PictureCurationSetDetailRenderer, // Picture Curation Sets Detail (NIP-51)
30007: KindMuteSetDetailRenderer, // Kind Mute Sets Detail (NIP-51)
30009: BadgeDefinitionDetailRenderer, // Badge Definition Detail (NIP-58)
30009: BadgeDefinitionDetailRenderer, // Badge Detail (NIP-58)
30015: InterestSetDetailRenderer, // Interest Sets Detail (NIP-51)
30023: Kind30023DetailRenderer, // Long-form Article Detail
30030: EmojiSetDetailRenderer, // Emoji Sets Detail (NIP-30)