import { useCallback, useState } from "react"; import { baseApi } from "../base-api"; import { resolveTokenFromStorage } from "../session"; export interface UploadFileInfo { bucket: string; fileName: string; contentType: string; originalname: string; size: number; } export interface UploadKeyResponse { fileInfo: UploadFileInfo; presigned: string; } const fileUploadApi = baseApi.injectEndpoints({ endpoints: (builder) => ({ getFileUploadKey: builder.mutation< UploadKeyResponse, { endpoint: string; file: File } >({ query: ({ endpoint, file }) => ({ url: endpoint, method: "POST", body: { fileName: file.name, contentType: file.type || "application/octet-stream", size: file.size, originalname: file.name, }, }), }), }), overrideExisting: false, }); export const { useGetFileUploadKeyMutation } = fileUploadApi; export const STORAGE_UPLOAD_ERROR = "STORAGE_UPLOAD_ERROR"; export const STORAGE_UPLOAD_TOO_LARGE = "STORAGE_UPLOAD_TOO_LARGE"; export function isStorageUploadError(error: unknown): error is Error & { message: typeof STORAGE_UPLOAD_ERROR | typeof STORAGE_UPLOAD_TOO_LARGE; } { return ( error instanceof Error && (error.message === STORAGE_UPLOAD_ERROR || error.message === STORAGE_UPLOAD_TOO_LARGE) ); } // Bare fetch on purpose: this PUT targets the presigned storage URL directly, not // VITE_BASE_API_URL, so it must not go through baseApi/fetchBaseQuery. async function uploadToPresigned( file: File, presignedUrl: string, ): Promise { if (!presignedUrl) throw new Error(STORAGE_UPLOAD_ERROR); const token = resolveTokenFromStorage(); const res = await fetch(presignedUrl, { method: "PUT", body: file, headers: { "Content-Type": file.type || "application/octet-stream", ...(token ? { Authorization: `Bearer ${token}` } : {}), }, }); if (res.status === 413) throw new Error(STORAGE_UPLOAD_TOO_LARGE); if (!res.ok) throw new Error(STORAGE_UPLOAD_ERROR); } export function useDocumentUpload() { const [getFileUploadKey, mutationState] = useGetFileUploadKeyMutation(); // Spans both steps (key request + presigned PUT) — mutationState.isLoading alone // would drop to false once the key request resolves, before the file PUT finishes. const [isUploading, setIsUploading] = useState(false); const upload = useCallback( async (file: File, endpoint = "/documents/get-file-upload-key"): Promise => { setIsUploading(true); try { const { presigned, fileInfo } = await getFileUploadKey({ endpoint, file, }).unwrap(); await uploadToPresigned(file, presigned); return { presigned, fileInfo }; } finally { setIsUploading(false); } }, [getFileUploadKey], ); return { upload, isUploading, error: mutationState.error, reset: mutationState.reset, }; }