202 lines
6.1 KiB
TypeScript
202 lines
6.1 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
|
|
}
|
|
|
|
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)
|
|
|
|
const { profileImage, saveProfileImage, removeProfileImage, isLoading } = useDatabase
|
|
? useProfile()
|
|
: { profileImage: null, saveProfileImage: async () => "", removeProfileImage: () => {}, isLoading: false }
|
|
|
|
useEffect(() => {
|
|
if (useDatabase && profileImage && !selectedImage) {
|
|
setSelectedImage(profileImage)
|
|
}
|
|
}, [profileImage, selectedImage, useDatabase])
|
|
|
|
const handleImageChange = async (event: ChangeEvent<HTMLInputElement>) => {
|
|
const file = event.target.files?.[0]
|
|
|
|
if (file) {
|
|
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
|
|
}
|
|
|
|
const maxSize = 2 * 1024 * 1024
|
|
if (file.size > maxSize) {
|
|
alert('O arquivo deve ter no máximo 2MB')
|
|
return
|
|
}
|
|
|
|
setIsUploading(true)
|
|
|
|
try {
|
|
let imageUrl: string
|
|
|
|
if (useDatabase) {
|
|
imageUrl = await saveProfileImage(file)
|
|
} else {
|
|
imageUrl = URL.createObjectURL(file)
|
|
}
|
|
|
|
setSelectedImage(imageUrl)
|
|
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)
|
|
|
|
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>
|
|
|
|
<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>
|
|
|
|
{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
|
|
ref={fileInputRef}
|
|
type="file"
|
|
accept="image/jpeg,image/png,image/gif,image/webp"
|
|
onChange={handleImageChange}
|
|
className="hidden"
|
|
disabled={isDisabled}
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|