Files
BelgianBitcoinEmbassy/frontend/components/nostr/NostrAvatar.tsx
T
bbeandCursor 70e3e0633d feat: roles/permissions system and Nostr profile display on admin users
Introduce granular role-based permissions with SuperAdmin env override, admin roles UI, and permission-gated API routes. Fix admin user Nostr metadata by batching relay profile fetches, normalizing npub pubkeys to hex, and adding reusable NostrAvatar/useNostrProfile components.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 08:46:56 +02:00

63 lines
2.0 KiB
TypeScript

"use client";
import Image from "next/image";
import { useNostrProfile } from "@/hooks/useNostrProfile";
import type { NostrProfile } from "@/lib/nostr";
function computeInitials(profile: NostrProfile | null, fallback?: string): string {
const name = profile?.name || profile?.displayName;
if (name?.trim()) return name.trim().slice(0, 2).toUpperCase();
const fb = fallback?.trim();
if (fb) {
// For npub-style fallbacks skip the "npub1" prefix for nicer initials.
if (fb.startsWith("npub1") && fb.length >= 8) return fb.slice(5, 7).toUpperCase();
return fb.slice(0, 2).toUpperCase();
}
return "?";
}
export interface NostrAvatarProps {
pubkey: string | null | undefined;
/** Rendered size in pixels (square). */
size?: number;
/** Text used to derive initials when no Nostr name/picture is available. */
fallbackText?: string;
className?: string;
}
// Self-contained avatar: fetches the user's Nostr metadata from relays and
// renders their profile picture, falling back to initials.
export function NostrAvatar({
pubkey,
size = 56,
fallbackText,
className = "",
}: NostrAvatarProps) {
const { profile, loading } = useNostrProfile(pubkey);
const displayName = profile?.name || profile?.displayName;
return (
<div
className={`shrink-0 rounded-full bg-surface-container-high flex items-center justify-center overflow-hidden text-on-surface ${className}`}
style={{ width: size, height: size }}
>
{loading ? (
<span className="text-on-surface/40 text-xs"></span>
) : profile?.picture ? (
<Image
src={profile.picture}
alt={displayName ? `Avatar: ${displayName}` : "Nostr profile picture"}
width={size}
height={size}
className="object-cover w-full h-full"
unoptimized
/>
) : (
<span className="font-semibold text-sm" aria-hidden>
{computeInitials(profile, fallbackText)}
</span>
)}
</div>
);
}