"use client";
import { useState, useRef, useCallback } from "react";
import {
Camera,
Upload,
Image as ImageIcon,
Trash2,
X,
Check,
Loader2,
User,
AlertCircle,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { cn } from "@/lib/utils";
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
const ACCEPTED_TYPES = ["image/jpeg", "image/png", "image/webp"];
export const title = "React Dialog Block Profile Picture";
export default function DialogProfilePicture() {
const [open, setOpen] = useState(false);
const [currentPhoto, setCurrentPhoto] = useState<string | null>(null);
const [previewPhoto, setPreviewPhoto] = useState<string | null>(null);
const [isDragging, setIsDragging] = useState(false);
const [isUploading, setIsUploading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
const validateFile = (file: File): string | null => {
if (!ACCEPTED_TYPES.includes(file.type)) {
return "Please upload a JPEG, PNG, or WebP image.";
}
if (file.size > MAX_FILE_SIZE) {
return "File size must be less than 5MB.";
}
return null;
};
const handleFile = useCallback((file: File) => {
setError(null);
const validationError = validateFile(file);
if (validationError) {
setError(validationError);
return;
}
const reader = new FileReader();
reader.onload = (e) => {
setPreviewPhoto(e.target?.result as string);
};
reader.readAsDataURL(file);
}, []);
const handleDragOver = (e: React.DragEvent) => {
e.preventDefault();
setIsDragging(true);
};
const handleDragLeave = (e: React.DragEvent) => {
e.preventDefault();
setIsDragging(false);
};
const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
setIsDragging(false);
const file = e.dataTransfer.files[0];
if (file) handleFile(file);
};
const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) handleFile(file);
e.target.value = "";
};
const handleCameraCapture = () => {
// In real app: implement webcam capture
// For demo, we'll just open file picker with capture
if (fileInputRef.current) {
fileInputRef.current.setAttribute("capture", "user");
fileInputRef.current.click();
fileInputRef.current.removeAttribute("capture");
}
};
const handleRemovePhoto = () => {
setPreviewPhoto(null);
setCurrentPhoto(null);
};
const handleSave = async () => {
if (!previewPhoto) return;
setIsUploading(true);
await new Promise((resolve) => setTimeout(resolve, 1500));
setCurrentPhoto(previewPhoto);
setPreviewPhoto(null);
setIsUploading(false);
setOpen(false);
};
const handleClose = () => {
setOpen(false);
setTimeout(() => {
setPreviewPhoto(null);
setError(null);
}, 200);
};
const displayPhoto = previewPhoto || currentPhoto;
return (
<Dialog open={open} onOpenChange={setOpen}>
<div className="flex min-h-[350px] items-center justify-center">
<DialogTrigger asChild>
<Button variant="outline">
<Camera className="mr-2 h-4 w-4" />
Change Photo
</Button>
</DialogTrigger>
</div>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Camera className="h-5 w-5" />
Profile Picture
</DialogTitle>
<DialogDescription>
Upload a new photo or take one with your camera.
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
{/* Current/Preview Photo */}
<div className="flex justify-center">
<div className="relative">
<Avatar className="h-32 w-32 border-4 border-background shadow-lg">
<AvatarImage src={displayPhoto || undefined} alt="Profile" />
<AvatarFallback className="text-4xl">
<User className="h-16 w-16" />
</AvatarFallback>
</Avatar>
{displayPhoto && (
<Button
variant="destructive"
size="icon"
className="absolute -bottom-1 -right-1 h-8 w-8 rounded-full"
onClick={handleRemovePhoto}
>
<Trash2 className="h-4 w-4" />
</Button>
)}
</div>
</div>
{/* Error Alert */}
{error && (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
{/* Drop Zone */}
<div
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
className={cn(
"rounded-lg border-2 border-dashed p-8 text-center transition-colors",
isDragging
? "border-primary bg-primary/5"
: "border-muted-foreground/25 hover:border-muted-foreground/50"
)}
>
<Upload className="mx-auto h-8 w-8 text-muted-foreground" />
<p className="mt-2 text-sm font-medium">
Drag and drop your photo here
</p>
<p className="mt-1 text-xs text-muted-foreground">
JPEG, PNG, or WebP up to 5MB
</p>
</div>
{/* Upload Options */}
<div className="grid grid-cols-2 gap-3">
<input
ref={fileInputRef}
type="file"
accept="image/jpeg,image/png,image/webp"
onChange={handleFileSelect}
className="hidden"
/>
<Button
variant="outline"
onClick={() => fileInputRef.current?.click()}
className="w-full"
>
<ImageIcon className="mr-2 h-4 w-4" />
Choose File
</Button>
<Button
variant="outline"
onClick={handleCameraCapture}
className="w-full"
>
<Camera className="mr-2 h-4 w-4" />
Take Photo
</Button>
</div>
{/* Preview Indicator */}
{previewPhoto && (
<p className="text-center text-sm text-muted-foreground">
Preview — click Save to apply
</p>
)}
</div>
<DialogFooter>
<Button variant="outline" onClick={handleClose} disabled={isUploading}>
Cancel
</Button>
<Button
onClick={handleSave}
disabled={!previewPhoto || isUploading}
>
{isUploading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Uploading...
</>
) : (
<>
<Check className="mr-2 h-4 w-4" />
Save Photo
</>
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}