c-file-upload-2.tsx
"use client"
import {
formatBytes,
useFileUpload,
type FileWithPreview,
} from "@/hooks/use-file-upload"
import {
Alert,
AlertDescription,
AlertTitle,
} from "@/components/reui/alert"
import { cn } from "cn"
import { Button } from "@/components/ui/button"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
interface AvatarUploadProps {
maxSize?: number
className?: string
onFileChange?: (file: FileWithPreview | null) => void
defaultAvatar?: string
}
export function Pattern({
maxSize = 2 * 1024 * 1024, // 2MB
className,
onFileChange,
defaultAvatar,
}: AvatarUploadProps) {
const [
{ files, isDragging, errors },
{
removeFile,
handleDragEnter,
handleDragLeave,
handleDragOver,
handleDrop,
openFileDialog,
getInputProps,
},
] = useFileUpload({
maxFiles: 1,
maxSize,
accept: "image/*",
multiple: false,
onFilesChange: (files) => {
onFileChange?.(files[0] || null)
},
})
const currentFile = files[0]
const previewUrl = currentFile?.preview || defaultAvatar
const handleRemove = () => {
if (currentFile) {
removeFile(currentFile.id)
}
}
return (
<div className={cn("flex flex-col items-center gap-4", className)}>
{/* Avatar Preview */}
<div className="relative">
<div
className={cn(
"group/avatar relative h-24 w-24 cursor-pointer overflow-hidden rounded-full border border-dashed transition-colors",
isDragging
? "border-primary bg-primary/5"
: "border-muted-foreground/25 hover:border-muted-foreground/20",
previewUrl && "border-solid"
)}
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
onDragOver={handleDragOver}
onDrop={handleDrop}
onClick={openFileDialog}
>
<input {...getInputProps()} className="sr-only" />
{previewUrl ? (
<img
src={previewUrl}
alt="Avatar"
className="h-full w-full object-cover"
/>
) : (
<div className="flex h-full w-full items-center justify-center">
<IconPlaceholder
lucide="UserIcon"
tabler="IconUser"
hugeicons="UserIcon"
phosphor="UserIcon"
remixicon="RiUserLine"
className="text-muted-foreground size-6"
/>
</div>
)}
</div>
{/* Remove Button - only show when file is uploaded */}
{currentFile && (
<Button
size="icon"
variant="outline"
onClick={handleRemove}
className="absolute end-0.5 top-0.5 z-10 size-6 rounded-full dark:bg-zinc-800 hover:dark:bg-zinc-700"
aria-label="Remove avatar"
>
<IconPlaceholder
lucide="XIcon"
tabler="IconX"
hugeicons="MultiplicationSignIcon"
phosphor="XIcon"
remixicon="RiCloseLine"
className="size-3.5"
/>
</Button>
)}
</div>
{/* Upload Instructions */}
<div className="space-y-0.5 text-center">
<p className="text-sm font-medium">
{currentFile ? "Avatar uploaded" : "Upload avatar"}
</p>
<p className="text-muted-foreground text-xs">
PNG, JPG up to {formatBytes(maxSize)}
</p>
</div>
{/* Error Messages */}
{errors.length > 0 && (
<Alert variant="destructive" className="mt-5">
<IconPlaceholder
lucide="CircleAlertIcon"
tabler="IconAlertCircle"
hugeicons="AlertCircleIcon"
phosphor="WarningCircleIcon"
remixicon="RiErrorWarningLine"
/>
<AlertTitle>File upload error(s)</AlertTitle>
<AlertDescription>
{errors.map((error, index) => (
<p key={index} className="last:mb-0">
{error}
</p>
))}
</AlertDescription>
</Alert>
)}
</div>
)
}



























