import { useState, useCallback } from "react"; export interface PendingAttachment { file: File; objectKey: string; uploadProgress: number; uploadStatus: "pending" | "uploading" | "complete" | "error"; error?: string; } interface UploadResponse { id?: number; object_key?: string; } /** * Hook for uploading files via direct multipart POST. * * Callers provide the target URL; the hook builds a FormData body and uses * XMLHttpRequest so that upload progress events are available. */ export function useFileUpload() { const [uploading, setUploading] = useState(false); const upload = useCallback( ( file: File, url: string, onProgress?: (progress: number) => void, ): Promise => { setUploading(true); return (async () => { try { const form = new FormData(); form.append("file", file); const result = await new Promise( (resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open("POST", url); xhr.withCredentials = true; xhr.upload.onprogress = (e) => { if (e.lengthComputable) { onProgress?.(Math.round((e.loaded / e.total) * 100)); } }; xhr.onload = () => { if (xhr.status >= 200 && xhr.status < 300) { try { resolve(JSON.parse(xhr.responseText) as UploadResponse); } catch { resolve({}); } } else { reject(new Error(`Upload failed: HTTP ${xhr.status}`)); } }; xhr.onerror = () => reject(new Error("Upload failed")); xhr.send(form); }, ); return { file, objectKey: result.object_key ?? "", uploadProgress: 100, uploadStatus: "complete" as const, }; } catch (e) { return { file, objectKey: "", uploadProgress: 0, uploadStatus: "error" as const, error: e instanceof Error ? e.message : "Upload failed", }; } finally { setUploading(false); } })(); }, [], ); return { upload, uploading }; }