2025-10-21 13:02:56 -03:00

159 lines
3.8 KiB
TypeScript

import { useState, useEffect } from "react";
import { User } from "lucide-react";
interface AvatarProps {
/** URL do avatar, objeto com avatar_url, ou userId para buscar */
src?:
| string
| { avatar_url?: string | null }
| { profile?: { avatar_url?: string | null } }
| { id?: string };
/** Nome completo para gerar iniciais */
name?: string;
/** Tamanho do avatar */
size?: "xs" | "sm" | "md" | "lg" | "xl";
/** Cor do gradiente (se não tiver imagem) */
color?:
| "blue"
| "green"
| "purple"
| "orange"
| "pink"
| "teal"
| "indigo"
| "red";
/** Classe CSS adicional */
className?: string;
/** Se deve mostrar borda */
border?: boolean;
}
const sizeClasses = {
xs: "w-6 h-6 text-xs",
sm: "w-8 h-8 text-xs",
md: "w-10 h-10 text-sm",
lg: "w-12 h-12 text-base",
xl: "w-16 h-16 text-xl",
};
const colorClasses = {
blue: "from-blue-400 to-blue-600",
green: "from-green-400 to-green-600",
purple: "from-purple-400 to-purple-600",
orange: "from-orange-400 to-orange-600",
pink: "from-pink-400 to-pink-600",
teal: "from-teal-400 to-teal-600",
indigo: "from-indigo-400 to-indigo-600",
red: "from-red-400 to-red-600",
};
/**
* Componente Avatar
* - Mostra imagem se disponível
* - Mostra iniciais como fallback
* - Suporta diferentes tamanhos e cores
*/
export function Avatar({
src,
name = "",
size = "md",
color = "blue",
className = "",
border = false,
}: AvatarProps) {
const [imageUrl, setImageUrl] = useState<string | null>(null);
const [imageError, setImageError] = useState(false);
// Extrai URL do avatar
useEffect(() => {
if (!src) {
setImageUrl(null);
return;
}
if (typeof src === "string") {
setImageUrl(src);
} else if ("avatar_url" in src && src.avatar_url) {
setImageUrl(src.avatar_url);
} else if ("profile" in src && src.profile?.avatar_url) {
setImageUrl(src.profile.avatar_url);
} else if ("id" in src && src.id) {
// Gera URL pública do Supabase Storage
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
setImageUrl(
`${SUPABASE_URL}/storage/v1/object/public/avatars/${src.id}/avatar`
);
} else {
setImageUrl(null);
}
setImageError(false);
}, [src]);
// Gera iniciais do nome
const getInitials = (fullName: string): string => {
if (!fullName) return "?";
const parts = fullName.trim().split(" ");
if (parts.length === 1) {
return parts[0].substring(0, 2).toUpperCase();
}
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
};
const initials = getInitials(name);
const shouldShowImage = imageUrl && !imageError;
return (
<div
className={`
${sizeClasses[size]}
rounded-full
flex items-center justify-center
overflow-hidden
${border ? "ring-2 ring-white shadow-lg" : ""}
${
shouldShowImage
? "bg-gray-100"
: `bg-gradient-to-br ${colorClasses[color]}`
}
${className}
`}
>
{shouldShowImage ? (
<img
src={imageUrl}
alt={name || "Avatar"}
className="w-full h-full object-cover"
onError={() => setImageError(true)}
/>
) : (
<span className="text-white font-semibold select-none">{initials}</span>
)}
</div>
);
}
/**
* Avatar com ícone padrão (para casos sem nome)
*/
export function AvatarIcon({
size = "md",
className = "",
}: Pick<AvatarProps, "size" | "className">) {
return (
<div
className={`
${sizeClasses[size]}
rounded-full
bg-gray-200
flex items-center justify-center
${className}
`}
>
<User className="w-1/2 h-1/2 text-gray-500" />
</div>
);
}