Refactor file upload to use generated upload mutation

Replaced direct SDK file upload calls with generated React Query mutations for better progress tracking and error handling. Streamlined logic for obtaining presigned URLs and uploading files, ensuring consistency and maintainability. Removed redundant code and improved state management during the upload process.
This commit is contained in:
2025-03-14 01:17:13 +01:00
parent 0afdbc973c
commit aaa7c68c12

View File

@@ -1,10 +1,7 @@
import { useState, useCallback, useEffect } from "react";
import { useMutation } from "@tanstack/react-query";
import {
generatePresignedUrl,
uploadFile as uploadFileSdk,
PresignedUrlResponse,
} from "@/client";
import { generatePresignedUrl, PresignedUrlResponse } from "@/client";
import { uploadFileMutation } from "@/client/@tanstack/react-query.gen";
export interface FileUploadState {
file?: File;
@@ -41,6 +38,7 @@ export function usePresignedUpload(options: UsePresignedUploadOptions = {}) {
const [uploadState, setUploadState] = useState<FileUploadState>(initialState);
// Use the presigned URL mutation
const presignedUrlMutation = useMutation({
mutationFn: async (file: File): Promise<PresignedUrlResponse> => {
const { data } = await generatePresignedUrl({
@@ -54,23 +52,29 @@ export function usePresignedUpload(options: UsePresignedUploadOptions = {}) {
if (!data || !data.upload_url || !data.file_url) {
throw new Error("Invalid response obtaining presigned URLs");
}
console.log("Presigned URL response: ", data);
return data;
},
});
// Use the generated upload file mutation
const uploadMutation = useMutation(
uploadFileMutation({
onUploadProgress: (progressEvent: any) => {
const progress =
progressEvent.loaded && progressEvent.total
? Math.round((progressEvent.loaded / progressEvent.total) * 100)
: 0;
setUploadState((prev) => ({ ...prev, progress }));
},
}),
);
const uploadFileToPresignedUrl = useCallback(
async (file: File, uploadUrl: string): Promise<void> => {
return new Promise<void>((resolve, reject) => {
try {
// Use the SDK's uploadFile method instead of direct XMLHttpRequest
// Create form data for the file
const formData = new FormData();
formData.append("file", file);
// Get the token from the upload URL
// Assuming the uploadUrl format has a token part after the last '/'
const token = uploadUrl.split("/").pop();
if (!token) {
@@ -78,35 +82,31 @@ export function usePresignedUpload(options: UsePresignedUploadOptions = {}) {
return;
}
// Create a progress tracker
const onProgress = (progressEvent: any) => {
const progress =
progressEvent.loaded && progressEvent.total
? Math.round((progressEvent.loaded / progressEvent.total) * 100)
: 0;
setUploadState((prev) => ({ ...prev, progress }));
};
// Call the SDK's upload method
uploadFileSdk({
path: { token },
body: { file },
onUploadProgress: onProgress,
})
.then(() => {
resolve();
})
.catch((error: any) => {
reject(
new Error(`Upload failed: ${error.message || "Unknown error"}`),
);
});
// Use the generated mutation instead of direct SDK call
uploadMutation.mutate(
{
path: { token },
body: { file },
},
{
onSuccess: () => {
resolve();
},
onError: (error: any) => {
reject(
new Error(
`Upload failed: ${error.message || "Unknown error"}`,
),
);
},
},
);
} catch (error) {
reject(new Error(`Network error during file upload: ${error}`));
}
});
},
[],
[uploadMutation],
);
useEffect(() => {
@@ -149,50 +149,48 @@ export function usePresignedUpload(options: UsePresignedUploadOptions = {}) {
fileUrl: undefined,
});
},
[reset, fileType, onUploadError],
[fileType, onUploadError, reset],
);
const uploadFile = useCallback(async () => {
if (!uploadState.file) {
const error = "File not selected.";
setUploadState((prev) => ({ ...prev, error }));
onUploadError?.(error);
return;
}
setUploadState((prev) => ({
...prev,
isUploading: true,
progress: 0,
error: undefined,
}));
const { file } = uploadState;
if (!file) return;
try {
const { upload_url, file_url } = await presignedUrlMutation.mutateAsync(
uploadState.file,
);
setUploadState((prev) => ({
...prev,
isUploading: true,
error: undefined,
}));
await uploadFileToPresignedUrl(uploadState.file, upload_url);
// First get the presigned URL
const presignedData = await presignedUrlMutation.mutateAsync(file);
// Then upload to that URL
await uploadFileToPresignedUrl(file, presignedData.upload_url);
// Use the file_url directly from the presigned URL response
const fileUrl = presignedData.file_url;
setUploadState((prev) => ({
...prev,
isUploading: false,
fileUrl: file_url,
progress: 100,
fileUrl,
}));
onUploadSuccess?.(file_url);
} catch (error: unknown) {
const message =
error instanceof Error
? error.message
: "Unexpected error during upload.";
onUploadSuccess?.(fileUrl);
} catch (error: any) {
const errorMessage = error.message || "Unknown error during file upload";
setUploadState((prev) => ({
...prev,
isUploading: false,
error: message,
progress: 0,
error: errorMessage,
}));
onUploadError?.(message);
onUploadError?.(errorMessage);
}
}, [
uploadState.file,
@@ -207,6 +205,5 @@ export function usePresignedUpload(options: UsePresignedUploadOptions = {}) {
selectFile,
uploadFile,
reset,
isPresignedUrlLoading: presignedUrlMutation.isPending,
};
}