234 lines
6.9 KiB
TypeScript
234 lines
6.9 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useRef, ChangeEvent, useEffect } from "react";
|
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Card, CardContent } from "@/components/ui/card";
|
|
import { Camera, Upload, X, Loader2 } from "lucide-react";
|
|
import { cn } from "@/lib/utils";
|
|
import { useProfile } from "@/hooks/use-profile";
|
|
|
|
interface ProfilePictureUploadProps {
|
|
initialImage?: string;
|
|
onImageChange?: (file: File | null, imageUrl: string | null) => void;
|
|
fallbackText?: string;
|
|
size?: "sm" | "md" | "lg" | "xl";
|
|
className?: string;
|
|
disabled?: boolean;
|
|
useDatabase?: boolean; // Nova prop para usar ou não o banco de dados
|
|
}
|
|
|
|
const sizeClasses = {
|
|
sm: "w-16 h-16",
|
|
md: "w-24 h-24",
|
|
lg: "w-32 h-32",
|
|
xl: "w-40 h-40",
|
|
};
|
|
|
|
export function ProfilePictureUpload({
|
|
initialImage,
|
|
onImageChange,
|
|
fallbackText = "U",
|
|
size = "lg",
|
|
className,
|
|
disabled = false,
|
|
useDatabase = true,
|
|
}: ProfilePictureUploadProps) {
|
|
const [selectedImage, setSelectedImage] = useState<string | null>(
|
|
initialImage || null
|
|
);
|
|
const [isHovering, setIsHovering] = useState(false);
|
|
const [isUploading, setIsUploading] = useState(false);
|
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
|
|
// Usar o hook apenas se useDatabase for true
|
|
const { profileImage, saveProfileImage, removeProfileImage, isLoading } =
|
|
useDatabase
|
|
? useProfile()
|
|
: {
|
|
profileImage: null,
|
|
saveProfileImage: async () => "",
|
|
removeProfileImage: () => {},
|
|
isLoading: false,
|
|
};
|
|
|
|
// Sincronizar com a imagem do banco de dados
|
|
useEffect(() => {
|
|
if (useDatabase && profileImage && !selectedImage) {
|
|
setSelectedImage(profileImage);
|
|
}
|
|
}, [profileImage, selectedImage, useDatabase]);
|
|
|
|
const handleImageChange = async (event: ChangeEvent<HTMLInputElement>) => {
|
|
const file = event.target.files?.[0];
|
|
|
|
if (file) {
|
|
// Validar tipo de arquivo
|
|
const validTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
|
|
if (!validTypes.includes(file.type)) {
|
|
alert(
|
|
"Por favor, selecione um arquivo de imagem válido (JPG, PNG, GIF ou WebP)"
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Validar tamanho do arquivo (máximo 2MB)
|
|
const maxSize = 2 * 1024 * 1024; // 2MB em bytes
|
|
if (file.size > maxSize) {
|
|
alert("O arquivo deve ter no máximo 2MB");
|
|
return;
|
|
}
|
|
|
|
setIsUploading(true);
|
|
|
|
try {
|
|
let imageUrl: string;
|
|
|
|
if (useDatabase) {
|
|
// Salvar no banco de dados local
|
|
imageUrl = await saveProfileImage(file);
|
|
} else {
|
|
// Apenas criar URL local
|
|
imageUrl = URL.createObjectURL(file);
|
|
}
|
|
|
|
setSelectedImage(imageUrl);
|
|
|
|
// Chamar callback se fornecido
|
|
onImageChange?.(file, imageUrl);
|
|
} catch (error) {
|
|
console.error("Erro ao processar imagem:", error);
|
|
alert("Erro ao salvar a imagem. Tente novamente.");
|
|
} finally {
|
|
setIsUploading(false);
|
|
}
|
|
}
|
|
};
|
|
|
|
const handleClick = () => {
|
|
if (!disabled && !isUploading) {
|
|
fileInputRef.current?.click();
|
|
}
|
|
};
|
|
|
|
const handleRemoveImage = async (e: React.MouseEvent) => {
|
|
e.stopPropagation();
|
|
|
|
if (useDatabase) {
|
|
removeProfileImage();
|
|
}
|
|
|
|
setSelectedImage(null);
|
|
onImageChange?.(null, null);
|
|
|
|
// Limpar o input
|
|
if (fileInputRef.current) {
|
|
fileInputRef.current.value = "";
|
|
}
|
|
};
|
|
|
|
const isDisabled = disabled || isUploading || (useDatabase && isLoading);
|
|
|
|
return (
|
|
<Card className={cn("w-fit", className)}>
|
|
<CardContent className="p-6">
|
|
<div className="flex flex-col items-center space-y-4">
|
|
<div className="text-sm font-medium text-center">Profile Photo</div>
|
|
|
|
<div
|
|
className={cn(
|
|
"relative cursor-pointer transition-all duration-200",
|
|
sizeClasses[size],
|
|
isDisabled && "cursor-not-allowed opacity-50"
|
|
)}
|
|
onMouseEnter={() => !isDisabled && setIsHovering(true)}
|
|
onMouseLeave={() => setIsHovering(false)}
|
|
onClick={handleClick}
|
|
>
|
|
<Avatar
|
|
className={cn(
|
|
"w-full h-full border-2 border-dashed border-muted-foreground/20",
|
|
{
|
|
"border-primary/50": isHovering && !isDisabled,
|
|
}
|
|
)}
|
|
>
|
|
<AvatarImage
|
|
src={selectedImage || undefined}
|
|
alt="Foto de perfil"
|
|
/>
|
|
<AvatarFallback className="text-lg font-semibold bg-muted">
|
|
{isLoading || isUploading ? (
|
|
<Loader2 className="w-6 h-6 animate-spin" />
|
|
) : selectedImage ? null : (
|
|
fallbackText
|
|
)}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
|
|
{/* Overlay com ícone */}
|
|
<div
|
|
className={cn(
|
|
"absolute inset-0 flex items-center justify-center bg-black/50 rounded-full opacity-0 transition-opacity duration-200",
|
|
isHovering && !isDisabled && "opacity-100"
|
|
)}
|
|
>
|
|
{isUploading ? (
|
|
<Loader2 className="w-6 h-6 text-white animate-spin" />
|
|
) : (
|
|
<Camera className="w-6 h-6 text-white" />
|
|
)}
|
|
</div>
|
|
|
|
{/* Botão de remover imagem */}
|
|
{selectedImage && !isDisabled && (
|
|
<Button
|
|
size="sm"
|
|
variant="destructive"
|
|
className="absolute -top-2 -right-2 w-6 h-6 rounded-full p-0"
|
|
onClick={handleRemoveImage}
|
|
>
|
|
<X className="w-3 h-3" />
|
|
</Button>
|
|
)}
|
|
</div>
|
|
|
|
<div className="text-center">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={handleClick}
|
|
disabled={isDisabled}
|
|
className="mb-2"
|
|
>
|
|
{isUploading ? (
|
|
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
|
) : (
|
|
<Upload className="w-4 h-4 mr-2" />
|
|
)}
|
|
{selectedImage ? "Alterar foto" : "Adicionar foto"}
|
|
</Button>
|
|
<p className="text-xs text-muted-foreground">
|
|
JPG, PNG ou GIF. Máximo 2MB.
|
|
</p>
|
|
{useDatabase && (
|
|
<p className="text-xs text-green-600 mt-1">
|
|
💾 Salvando automaticamente
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* Input file oculto */}
|
|
<input
|
|
ref={fileInputRef}
|
|
type="file"
|
|
accept="image/jpeg,image/png,image/gif,image/webp"
|
|
onChange={handleImageChange}
|
|
className="hidden"
|
|
disabled={isDisabled}
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|