mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-26 19:12:50 +00:00
106 lines
2.9 KiB
TypeScript
106 lines
2.9 KiB
TypeScript
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<void> {
|
|
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<UploadKeyResponse> => {
|
|
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,
|
|
};
|
|
}
|