SkateSpots/src/components/AttendeeStack.tsx
shakespeare.diy 7439b980bb Events Phase 1: NIP-52 RSVPs, attendee stacks, date filter, host notifications
- Adopt NIP-52 RSVP (kind 31925): parseEventRsvp + RsvpStatus types
- useEventRsvps (counts by status, attendee pubkeys, my status) + useRsvp
  mutation that replaces the user's prior RSVP via a stable d-tag
- RsvpButtons (Going / Interested / Can't make it) + AttendeeStack avatar row
- EventDetailPage: "Who's going" card with RSVP control + attendee avatars
- EventCard: going count on both compact and full cards
- EventsPage: quick date-window chips (Today / Weekend / Week / Month)
- Activity bell: host RSVP notifications (new 'rsvp' activity type)
- Document RSVP schema in NIP.md

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 10:47:57 +07:00

71 lines
1.9 KiB
TypeScript

import { Link } from 'react-router-dom';
import { nip19 } from 'nostr-tools';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { useAuthor } from '@/hooks/useAuthor';
import { genUserName } from '@/lib/genUserName';
import { cn } from '@/lib/utils';
function StackAvatar({ pubkey, index }: { pubkey: string; index: number }) {
const author = useAuthor(pubkey);
const name = author.data?.metadata?.name ?? genUserName(pubkey);
const pic = author.data?.metadata?.picture;
const npub = (() => {
try {
return nip19.npubEncode(pubkey);
} catch {
return null;
}
})();
const avatar = (
<Avatar
className="h-7 w-7 border-2 border-background ring-0"
style={{ zIndex: 20 - index }}
title={name}
>
<AvatarImage src={pic} alt={name} />
<AvatarFallback className="text-[9px] font-bold">
{name.slice(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>
);
if (!npub) return avatar;
return (
<Link to={`/${npub}`} className="transition-transform hover:-translate-y-0.5">
{avatar}
</Link>
);
}
/**
* A horizontal, overlapping stack of attendee avatars with a "+N" overflow.
* Purely presentational — pass a list of pubkeys.
*/
export function AttendeeStack({
pubkeys,
max = 5,
className,
}: {
pubkeys: string[];
max?: number;
className?: string;
}) {
if (pubkeys.length === 0) return null;
const shown = pubkeys.slice(0, max);
const overflow = pubkeys.length - shown.length;
return (
<div className={cn('flex items-center', className)}>
<div className="flex -space-x-2">
{shown.map((pk, i) => (
<StackAvatar key={pk} pubkey={pk} index={i} />
))}
</div>
{overflow > 0 && (
<span className="ml-2 text-xs font-bold text-muted-foreground">+{overflow}</span>
)}
</div>
);
}