Files
thrilltrack-explorer/src-old/components/common/ProfileBadge.tsx

157 lines
3.7 KiB
TypeScript

import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Badge } from '@/components/ui/badge';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { User, Shield, ShieldCheck, Crown } from 'lucide-react';
import { getRoleLabel } from '@/lib/moderation/constants';
interface ProfileBadgeProps {
/** Username to display */
username?: string;
/** Display name (fallback to username) */
displayName?: string;
/** Avatar image URL */
avatarUrl?: string;
/** User role */
role?: 'admin' | 'moderator' | 'user' | 'superuser';
/** Show role badge */
showRole?: boolean;
/** Size variant */
size?: 'sm' | 'md' | 'lg';
/** Whether to show as a link */
clickable?: boolean;
/** Custom click handler */
onClick?: () => void;
}
const sizeClasses = {
sm: {
avatar: 'h-6 w-6',
text: 'text-xs',
badge: 'h-4 text-[10px] px-1',
},
md: {
avatar: 'h-8 w-8',
text: 'text-sm',
badge: 'h-5 text-xs px-1.5',
},
lg: {
avatar: 'h-10 w-10',
text: 'text-base',
badge: 'h-6 text-sm px-2',
},
};
const roleIcons = {
superuser: Crown,
admin: ShieldCheck,
moderator: Shield,
user: User,
};
const roleColors = {
superuser: 'bg-purple-500/10 text-purple-700 dark:text-purple-400 border-purple-500/20',
admin: 'bg-red-500/10 text-red-700 dark:text-red-400 border-red-500/20',
moderator: 'bg-blue-500/10 text-blue-700 dark:text-blue-400 border-blue-500/20',
user: 'bg-muted text-muted-foreground border-border',
};
/**
* Reusable user profile badge component
*
* Displays user avatar, name, and optional role badge
* Used consistently across moderation queue and admin panels
*
* @example
* ```tsx
* <ProfileBadge
* username="johndoe"
* displayName="John Doe"
* avatarUrl="/avatars/john.jpg"
* role="moderator"
* showRole
* size="md"
* />
* ```
*/
export function ProfileBadge({
username,
displayName,
avatarUrl,
role = 'user',
showRole = false,
size = 'md',
clickable = false,
onClick,
}: ProfileBadgeProps) {
const sizes = sizeClasses[size];
const name = displayName || username || 'Anonymous';
const initials = name
.split(' ')
.map(n => n[0])
.join('')
.toUpperCase()
.slice(0, 2);
const RoleIcon = roleIcons[role];
const content = (
<div
className={`flex items-center gap-2 ${clickable ? 'cursor-pointer hover:opacity-80 transition-opacity' : ''}`}
onClick={onClick}
>
<Avatar className={sizes.avatar}>
<AvatarImage src={avatarUrl} alt={name} />
<AvatarFallback className="text-[10px]">{initials}</AvatarFallback>
</Avatar>
<div className="flex flex-col min-w-0">
<span className={`font-medium truncate ${sizes.text}`}>
{name}
</span>
{username && displayName && (
<span className="text-xs text-muted-foreground truncate">
@{username}
</span>
)}
</div>
{showRole && role !== 'user' && (
<Badge
variant="outline"
className={`${sizes.badge} ${roleColors[role]} flex items-center gap-1`}
>
<RoleIcon className="h-3 w-3" />
<span className="hidden sm:inline">{getRoleLabel(role)}</span>
</Badge>
)}
</div>
);
if (showRole && role !== 'user') {
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
{content}
</TooltipTrigger>
<TooltipContent>
<p className="text-xs">
{getRoleLabel(role)}
{username && ` • @${username}`}
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
return content;
}