mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 08:11:12 -05:00
157 lines
3.7 KiB
TypeScript
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;
|
|
}
|