hooks/use-file-dropzone.ts
Rama main · réplica de lectura del repositorio original.
import { useCallback, useState } from 'react'
import { toast } from 'sonner'
import { UploadedFile } from '@/lib/types'
import { MAX_ATTACHMENTS_PER_MESSAGE } from '@/lib/utils/attachment-limits'
type UseFileDropzoneProps = {
uploadedFiles: UploadedFile[]
setUploadedFiles: React.Dispatch<React.SetStateAction<UploadedFile[]>>
maxFiles?: number
allowedTypes?: string[]
chatId: string
}
export function useFileDropzone({
uploadedFiles,
setUploadedFiles,
chatId,
maxFiles = MAX_ATTACHMENTS_PER_MESSAGE,
allowedTypes = ['image/png', 'image/jpeg', 'application/pdf']
}: UseFileDropzoneProps) {
const [isDragging, setIsDragging] = useState(false)
const handleDragOver = useCallback((e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault()
setIsDragging(true)
}, [])
const handleDragLeave = useCallback((e: React.DragEvent<HTMLDivElement>) => {
if (!e.currentTarget.contains(e.relatedTarget as Node)) {
setIsDragging(false)
}
}, [])
const handleDrop = useCallback(
async (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault()
setIsDragging(false)
const rawFiles = Array.from(e.dataTransfer.files)
const allowed = rawFiles.filter(file => allowedTypes.includes(file.type))
const rejected = rawFiles.filter(file => !allowed.includes(file))
if (rejected.length > 0) {
toast.error(
'Some files were not accepted: ' +
rejected.map(f => f.name).join(', ')
)
}
const total = uploadedFiles.length + allowed.length
if (total > maxFiles) {
toast.error(`You can attach up to ${maxFiles} files per message.`)
return
}
const initialFiles: UploadedFile[] = allowed.map(file => ({
file,
status: 'uploading'
}))
setUploadedFiles(prev => [...prev, ...initialFiles].slice(0, maxFiles))
await Promise.all(
initialFiles.map(async uf => {
const formData = new FormData()
formData.append('file', uf.file!)
formData.append('chatId', chatId)
try {
const res = await fetch('/api/upload', {
method: 'POST',
body: formData
})
if (!res.ok) throw new Error('Upload failed')
const { file: uploaded } = await res.json()
setUploadedFiles(prev =>
prev.map(f =>
f.file === uf.file
? {
...f,
status: 'uploaded',
url: uploaded.url,
name: uploaded.filename,
key: uploaded.key,
mediaType: uploaded.mediaType,
libraryFileId: uploaded.id
}
: f
)
)
} catch (err) {
toast.error(`Failed to upload ${uf.file?.name ?? 'file'}`)
setUploadedFiles(prev =>
prev.map(f =>
f.file === uf.file ? { ...f, status: 'error' } : f
)
)
}
})
)
},
[allowedTypes, maxFiles, uploadedFiles, setUploadedFiles, chatId]
)
return {
isDragging,
handleDragOver,
handleDragLeave,
handleDrop
}
}