// components/ui/UserAvatar.tsx 'use client'; import * as React from 'react'; import clsx from 'clsx'; const AVATAR_COLORS = [ 'bg-orange-500', 'bg-indigo-500', 'bg-emerald-500', 'bg-sky-500', 'bg-rose-500', 'bg-amber-500', 'bg-violet-500', ]; function getAvatarColor(seed: string) { let hash = 0; for (let i = 0; i < seed.length; i++) { hash = (hash * 31 + seed.charCodeAt(i)) | 0; } const index = Math.abs(hash) % AVATAR_COLORS.length; return AVATAR_COLORS[index]; } type Size = 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl'; const sizeClasses: Record = { sm: 'h-6 w-6 text-[10px]', md: 'h-8 w-8 text-xs', lg: 'h-10 w-10 text-sm', xl: 'h-12 w-12 text-md', '2xl': 'h-14 w-14 text-lg', '3xl': 'h-16 w-16 text-xl', }; export type UserAvatarProps = { /** Für Initialen + Farbberechnung, z.B. Arbeitsname */ name?: string | null; /** Optionales Bild – wenn gesetzt, wird das Bild angezeigt */ avatarUrl?: string | null; /** Größe des Avatars */ size?: Size; }; export default function UserAvatar({ name, avatarUrl, size = 'md', }: UserAvatarProps) { const displayName = (name ?? '').trim(); const initial = displayName.charAt(0)?.toUpperCase() || '?'; const colorClass = getAvatarColor(displayName || initial || 'x'); // Wenn Bild-URL gesetzt, aber das Laden fehlschlägt → Fallback auf Initialen const [hasImageError, setHasImageError] = React.useState(false); React.useEffect(() => { // Wenn sich avatarUrl ändert, Fehlerzustand zurücksetzen setHasImageError(false); }, [avatarUrl]); const showImage = !!avatarUrl && !hasImageError; if (showImage) { return ( {displayName setHasImageError(true)} className={clsx('rounded-full object-cover', sizeClasses[size])} /> ); } // Fallback: Initialen mit pseudo-zufälliger Hintergrundfarbe return (
{initial}
); }