mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-26 19:12:50 +00:00
feat(vessel-transfer): add document upload functionality to transfer requests
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
export * from './lib/base-api';
|
||||
export * from './lib/query-and-mutation';
|
||||
export * from './lib/session';
|
||||
export * from './lib/file-upload';
|
||||
export { baseQueryWithReauth, configureTokenRefresh } from './lib/base-api/base-query-with-reauth';
|
||||
|
||||
87
libs/api/src/lib/file-upload/index.ts
Normal file
87
libs/api/src/lib/file-upload/index.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { baseApi } from '../base-api';
|
||||
import { resolveTokenFromStorage } from '../session';
|
||||
|
||||
export interface UploadKeyResponse<TFileInfo = unknown> {
|
||||
presigned: string;
|
||||
fileInfo: TFileInfo;
|
||||
}
|
||||
|
||||
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 <TFileInfo = unknown>(file: File, endpoint: string): Promise<UploadKeyResponse<TFileInfo>> => {
|
||||
setIsUploading(true);
|
||||
try {
|
||||
const { presigned, fileInfo } = await getFileUploadKey({ endpoint, file }).unwrap();
|
||||
await uploadToPresigned(file, presigned);
|
||||
return { presigned, fileInfo: fileInfo as TFileInfo };
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
}
|
||||
},
|
||||
[getFileUploadKey],
|
||||
);
|
||||
|
||||
return {
|
||||
upload,
|
||||
isUploading,
|
||||
error: mutationState.error,
|
||||
reset: mutationState.reset,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user