"use client"; import { getCatppuccinPrefixColor, getPrefixLabel } from "@/lib/types"; import { formatRelativeTime } from "@/lib/utils"; // ============================================================================ // Interfaces // ============================================================================ export interface UserProfile { handle: string; avatar?: string; did?: string; // Stats assignedCount: number; // nodes currently assigned to this user closedCount: number; // nodes with status "closed" that are assigned to or created by this user claimedCount: number; // nodes this user has claimed commentCount: number; // total comments posted by this user likeCount: number; // total likes given by this user // Projects projects: { prefix: string; prefixLabel: string; color: string; count: number; }[]; // Recent activity (already sorted newest-first, capped at 20) recentActivity: { type: "assigned" | "closed" | "commented" | "claimed" | "liked"; nodeId: string; nodeTitle: string; time: string; // ISO 8601 detail?: string; // comment preview, etc. }[]; // Last active timestamp lastActiveAt?: string; // ISO 8601 } interface ProfilePanelProps { isOpen: boolean; onClose: () => void; profile: UserProfile | null; onNodeNavigate: (nodeId: string) => void; } // ============================================================================ // Activity constants // ============================================================================ const ACTIVITY_ICONS: Record = { assigned: "📋", closed: "✅", commented: "💬", claimed: "🙋", liked: "❤️", }; const ACTIVITY_VERBS: Record = { assigned: "Assigned to", closed: "Closed", commented: "Commented on", claimed: "Claimed", liked: "Liked a comment on", }; // ============================================================================ // ProfilePanel component // ============================================================================ export function ProfilePanel({ isOpen, onClose, profile, onNodeNavigate, }: ProfilePanelProps) { return ( <> {/* Desktop: right sidebar */} {/* Mobile: bottom drawer */}

Profile

{profile && ( )}
); } // ============================================================================ // ProfileContent — internal layout component // ============================================================================ function ProfileContent({ profile, onNodeNavigate, }: { profile: UserProfile; onNodeNavigate: (nodeId: string) => void; }) { // Check if user has zero activity const hasZeroActivity = profile.assignedCount === 0 && profile.closedCount === 0 && profile.claimedCount === 0 && profile.commentCount === 0 && profile.likeCount === 0 && profile.projects.length === 0 && profile.recentActivity.length === 0; return ( <> {/* 1. Header — avatar + handle + last active */}
{/* Avatar: 48px circle */} {profile.avatar ? ( {profile.handle} ) : (
{profile.handle.charAt(0).toUpperCase()}
)}
{profile.handle}
{profile.lastActiveAt && (
Active {formatRelativeTime(profile.lastActiveAt)}
)}
{hasZeroActivity ? (

No activity yet

This user hasn't contributed to any projects yet

) : ( <> {/* 2. Stats grid — 2x2 grid of key metrics */}
{/* 3. Projects section — list of projects with counts */}

Projects

{profile.projects.length === 0 ? (

No projects

) : (
{profile.projects.map((p) => ( {p.prefixLabel} ({p.count}) ))}
)}
{/* 4. Recent activity — chronological list */}

Recent Activity

{profile.recentActivity.length === 0 ? (

No activity

) : (
{profile.recentActivity.map((event, i) => (
{ACTIVITY_ICONS[event.type]}
{ACTIVITY_VERBS[event.type]}{" "} {event.detail && (

{event.detail}

)}
{formatRelativeTime(event.time)}
))}
)}
)} ); } // ============================================================================ // StatCard — small internal component for stats grid // ============================================================================ function StatCard({ label, value, color, }: { label: string; value: number; color: string; }) { return (
{value}
{label}
); }