mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-13 00:46:54 +02:00
feat: improve badges display with images and limited count (#128)
- Add badge image display to BadgeDefinitionRenderer feed items - Shows badge image/icon (16x16) with name and description - Falls back to Award icon if no image available - Limit ProfileBadgesRenderer to show max 5 badges with "& n more" pattern - Prevents overcrowded feeds when users have many badges - Maintains clickability to see full list in detail view - Rename "Badge definition" to "Badge" for clearer user-facing text - Updated constants/kinds.ts and nostr-kinds-schema.yaml - Simplifies terminology (kind 30009 is just "Badge" not "Badge definition") Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -7,35 +7,55 @@ import {
|
||||
getBadgeIdentifier,
|
||||
getBadgeName,
|
||||
getBadgeDescription,
|
||||
getBadgeImageUrl,
|
||||
} from "@/lib/nip58-helpers";
|
||||
import { Award } from "lucide-react";
|
||||
|
||||
/**
|
||||
* Renderer for Kind 30009 - Badge (NIP-58)
|
||||
* Simple feed view with name and description
|
||||
* Feed view with image, 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 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>
|
||||
<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>
|
||||
)}
|
||||
|
||||
{/* 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>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</BaseEventContainer>
|
||||
);
|
||||
|
||||
@@ -74,10 +74,13 @@ function BadgeItem({ badgeAddress }: { badgeAddress: string }) {
|
||||
|
||||
/**
|
||||
* Renderer for Kind 30008 - Profile Badges (NIP-58)
|
||||
* Shows all badge thumbnails, clickable to open detail view
|
||||
* Shows limited badge thumbnails with "& n more" pattern, clickable to open detail view
|
||||
*/
|
||||
export function ProfileBadgesRenderer({ event }: BaseEventProps) {
|
||||
const badgePairs = getProfileBadgePairs(event);
|
||||
const MAX_VISIBLE_BADGES = 5;
|
||||
const visibleBadges = badgePairs.slice(0, MAX_VISIBLE_BADGES);
|
||||
const remainingCount = Math.max(0, badgePairs.length - MAX_VISIBLE_BADGES);
|
||||
|
||||
if (badgePairs.length === 0) {
|
||||
return (
|
||||
@@ -101,11 +104,16 @@ export function ProfileBadgesRenderer({ event }: BaseEventProps) {
|
||||
{badgePairs.length} {badgePairs.length === 1 ? "badge" : "badges"}
|
||||
</ClickableEventTitle>
|
||||
|
||||
{/* All Badge Thumbnails */}
|
||||
{/* Limited Badge Thumbnails */}
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
{badgePairs.map((pair, idx) => (
|
||||
{visibleBadges.map((pair, idx) => (
|
||||
<BadgeItem key={idx} badgeAddress={pair.badgeAddress} />
|
||||
))}
|
||||
{remainingCount > 0 && (
|
||||
<span className="text-sm text-muted-foreground">
|
||||
& {remainingCount} more
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</BaseEventContainer>
|
||||
|
||||
@@ -1096,8 +1096,8 @@ export const EVENT_KINDS: Record<number | string, EventKind> = {
|
||||
},
|
||||
30009: {
|
||||
kind: 30009,
|
||||
name: "Badge Definition",
|
||||
description: "Badge Definition",
|
||||
name: "Badge",
|
||||
description: "Badge",
|
||||
nip: "58",
|
||||
icon: Award,
|
||||
},
|
||||
|
||||
@@ -1402,7 +1402,7 @@ kinds:
|
||||
- *etag
|
||||
|
||||
30009:
|
||||
description: Badge Definition
|
||||
description: Badge
|
||||
in_use: true
|
||||
content:
|
||||
type: free
|
||||
|
||||
Reference in New Issue
Block a user