159 lines
3.8 KiB
TypeScript
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>
|
|
);
|
|
}
|