'use client' import { useScene } from '@pascal-app/core' import { ImageIcon, MessageSquare, X } from 'lucide-react' import { useCallback, useRef, useState } from 'react' import { Button } from './ui/primitives/button' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from './ui/primitives/dialog' const MAX_IMAGES = 5 const MAX_IMAGE_SIZE = 5 * 1024 * 1024 type ImagePreview = { file: File; url: string } export function FeedbackDialog({ projectId: projectIdProp, onSubmit, }: { projectId?: string onSubmit?: (data: { message: string projectId?: string sceneGraph: unknown images: File[] }) => Promise<{ success: boolean; error?: string }> }) { const projectId = projectIdProp const [open, setOpen] = useState(false) const [message, setMessage] = useState('') const [images, setImages] = useState([]) const [isDragging, setIsDragging] = useState(false) const [isSubmitting, setIsSubmitting] = useState(false) const [error, setError] = useState(null) const [sent, setSent] = useState(false) const fileInputRef = useRef(null) const dragCounter = useRef(0) const handleOpen = () => { setOpen(true) setSent(false) setError(null) setMessage('') setImages([]) setIsDragging(false) dragCounter.current = 0 } const handleClose = () => { if (isSubmitting) return setOpen(false) images.forEach((img) => { URL.revokeObjectURL(img.url) }) } const addFiles = useCallback((files: FileList | File[]) => { const incoming = Array.from(files).filter( (f) => f.type.startsWith('image/') && f.size <= MAX_IMAGE_SIZE, ) setImages((prev) => { const remaining = MAX_IMAGES - prev.length const added = incoming.slice(0, remaining).map((file) => ({ file, url: URL.createObjectURL(file), })) return [...prev, ...added] }) }, []) const removeImage = (index: number) => { setImages((prev) => { const img = prev[index] if (img) URL.revokeObjectURL(img.url) return prev.filter((_, i) => i !== index) }) } // ── Drag handlers (on the entire dialog content) ── const onDragEnter = (e: React.DragEvent) => { e.preventDefault() e.stopPropagation() dragCounter.current++ if (e.dataTransfer.types.includes('Files')) { setIsDragging(true) } } const onDragLeave = (e: React.DragEvent) => { e.preventDefault() e.stopPropagation() dragCounter.current-- if (dragCounter.current === 0) { setIsDragging(false) } } const onDragOver = (e: React.DragEvent) => { e.preventDefault() e.stopPropagation() } const onDrop = (e: React.DragEvent) => { e.preventDefault() e.stopPropagation() dragCounter.current = 0 setIsDragging(false) if (e.dataTransfer.files.length > 0) { addFiles(e.dataTransfer.files) } } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() setError(null) setIsSubmitting(true) try { if (!onSubmit) return const { nodes, rootNodeIds } = useScene.getState() const sceneGraph = { nodes, rootNodeIds } const result = await onSubmit({ message, projectId, sceneGraph, images: images.map((img) => img.file), }) if (result.success) { setSent(true) setTimeout(() => setOpen(false), 1500) } else { setError(result.error ?? 'Something went wrong') } } finally { setIsSubmitting(false) } } return ( <> {/* Drag overlay — only visible when dragging files over the dialog */} {isDragging && (

Drop images here

)} Send Feedback We'd love to hear your thoughts {sent ? (

Thanks for your feedback!

) : (