merge: merged the refactoring into ui-redesign

This commit is contained in:
Logan Reeder
2025-07-19 10:58:19 -06:00
234 changed files with 17119 additions and 6120 deletions

View File

@@ -1,42 +1,43 @@
import type { _File, CreateFolderParams, DeleteFileParams, UpdateFileParams, UploadFileParams } from "@/lib/types";
import type {
CreateFileSchema,
DeleteFileSchema,
GetFileByIdSchema,
GetFilesSchema,
UpdateFileSchema,
UploadFileSchema,
} from "@nimbus/shared";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { clientEnv } from "@/lib/env/client-env";
import axios, { type AxiosError } from "axios";
import { useAccountProvider } from "@/components/providers/account-provider";
import type { DriveProviderClient } from "@/utils/client";
import { toast } from "sonner";
const API_BASE = `${clientEnv.NEXT_PUBLIC_BACKEND_URL}/api/files`;
const defaultAxiosConfig = {
headers: {
"Content-Type": "application/json",
},
withCredentials: true,
signal: new AbortController().signal,
};
export function useGetFiles(parentId: string, pageSize: number, returnedValues: string[], nextPageToken?: string) {
export function useGetFiles({ parentId, pageSize, pageToken, returnedValues }: GetFilesSchema) {
const { clientPromise, providerId, accountId } = useAccountProvider();
return useQuery({
queryKey: ["files", parentId, nextPageToken, pageSize],
queryKey: ["files", providerId, accountId, parentId, pageSize, pageToken],
queryFn: async () => {
const response = await axios.get(API_BASE, {
params: { parentId, pageSize, returnedValues, pageToken: nextPageToken },
...defaultAxiosConfig,
const BASE_FILE_CLIENT = await getBaseFileClient(clientPromise);
const response = await BASE_FILE_CLIENT.$get({
query: { parentId, pageSize: pageSize.toString(), pageToken, returnedValues },
});
return response.data;
return await response.json();
},
staleTime: 5 * 60 * 1000, // 5 minutes
retry: 2,
});
}
export function useGetFile(fileId: string, returnedValues: string[]) {
export function useGetFile({ fileId, returnedValues }: GetFileByIdSchema) {
const { clientPromise, providerId, accountId } = useAccountProvider();
return useQuery({
queryKey: ["file", fileId, returnedValues],
queryKey: ["file", providerId, accountId, fileId, returnedValues],
queryFn: async () => {
const response = await axios.get(`${API_BASE}/${fileId}`, {
params: { returnedValues },
...defaultAxiosConfig,
const BASE_FILE_CLIENT = await getBaseFileClient(clientPromise);
const response = await BASE_FILE_CLIENT[":id"].$get({
param: { fileId },
query: { returnedValues },
});
return response.data;
return await response.json();
},
staleTime: 5 * 60 * 1000, // 5 minutes
retry: 2,
@@ -45,13 +46,14 @@ export function useGetFile(fileId: string, returnedValues: string[]) {
export function useDeleteFile() {
const queryClient = useQueryClient();
const { clientPromise } = useAccountProvider();
return useMutation({
mutationFn: async ({ fileId }: DeleteFileParams) => {
const response = await axios.delete(API_BASE, {
params: { fileId },
...defaultAxiosConfig,
mutationFn: async ({ fileId }: DeleteFileSchema) => {
const BASE_FILE_CLIENT = await getBaseFileClient(clientPromise);
const response = await BASE_FILE_CLIENT.$delete({
query: { fileId },
});
return response.data;
return await response.json();
},
// Handles optimistic updates
onMutate: async deletedFile => {
@@ -78,13 +80,14 @@ export function useDeleteFile() {
export function useUpdateFile() {
const queryClient = useQueryClient();
const { clientPromise } = useAccountProvider();
return useMutation({
mutationFn: async ({ fileId, ...dataToUpdate }: UpdateFileParams) => {
const response = await axios.put(`${API_BASE}`, dataToUpdate, {
params: { fileId },
...defaultAxiosConfig,
mutationFn: async ({ fileId, name }: UpdateFileSchema) => {
const BASE_FILE_CLIENT = await getBaseFileClient(clientPromise);
const response = await BASE_FILE_CLIENT.$put({
query: { fileId, name },
});
return response.data;
return await response.json();
},
// Handles optimistic updates
onMutate: async ({ fileId, ...dataToUpdate }) => {
@@ -113,23 +116,24 @@ export function useUpdateFile() {
export function useCreateFolder() {
const queryClient = useQueryClient();
const { clientPromise } = useAccountProvider();
return useMutation({
mutationFn: async ({ name, parentId }: CreateFolderParams) => {
const response = await axios.post(API_BASE, null, {
...defaultAxiosConfig,
params: {
mutationFn: async ({ name, mimeType, parent }: CreateFileSchema) => {
const BASE_FILE_CLIENT = await getBaseFileClient(clientPromise);
const response = await BASE_FILE_CLIENT.$post({
query: {
name,
mimeType: "folder",
parent: parentId,
mimeType,
parent,
},
});
return response.data;
return await response.json();
},
onSuccess: async () => {
toast.success("Folder created successfully");
await queryClient.invalidateQueries({ queryKey: ["files"] });
},
onError: (error: AxiosError) => {
onError: error => {
console.error("Error creating folder:", error);
const errorMessage = error.message || "Failed to create folder";
toast.error(errorMessage);
@@ -139,40 +143,140 @@ export function useCreateFolder() {
export function useUploadFile() {
const queryClient = useQueryClient();
const { clientPromise } = useAccountProvider();
return useMutation({
mutationFn: async ({ file, parentId, onProgress, returnedValues }: UploadFileParams) => {
// ? Maybe look into Tanstack Form for this implementation
const formData = new FormData();
formData.append("file", file);
const response = await axios.post(`${API_BASE}/upload`, formData, {
headers: {
"Content-Type": "multipart/form-data",
// mutationFn: async ({ file, parentId, onProgress }: UploadFileParams) => {
mutationFn: async ({ file, parentId }: UploadFileSchema) => {
const BASE_FILE_CLIENT = await getBaseFileClient(clientPromise);
const response = await BASE_FILE_CLIENT.upload.$post({
form: {
file,
},
withCredentials: true,
params: {
query: {
parentId,
returnedValues,
},
onUploadProgress: progressEvent => {
if (onProgress && progressEvent.total) {
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
onProgress(percentCompleted);
}
},
// onUploadProgress: progressEvent => {
// if (onProgress && progressEvent.total) {
// const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
// onProgress(percentCompleted);
// }
// },
});
return response.data;
return await response.json();
},
onSuccess: async () => {
// Invalidate the files query to refetch the updated list
await queryClient.invalidateQueries({ queryKey: ["files"] });
},
onError: (error: AxiosError<{ message?: string }>) => {
onError: error => {
console.error("Error uploading file:", error);
const errorMessage = error.message || "Failed to upload file";
toast.error(errorMessage);
},
});
}
export function useUploadFolder() {}
export function useDownloadFile() {
const { clientPromise } = useAccountProvider();
return useMutation({
mutationFn: async ({
fileId,
exportMimeType,
fileName,
onProgress,
}: {
fileId: string;
exportMimeType?: string;
fileName?: string;
onProgress?: (progress: number) => void;
}) => {
const BASE_FILE_CLIENT = await getBaseFileClient(clientPromise);
const response = await BASE_FILE_CLIENT.download.$get({
query: {
fileId,
exportMimeType,
},
});
if (!response.ok) {
const errorData = (await response.json().catch(() => ({}))) as { message?: string };
const errorMessage = errorData?.message || "Failed to download file";
throw new Error(errorMessage);
}
// Get the filename from the Content-Disposition header
const contentDisposition = response.headers.get("Content-Disposition");
let filename = fileName || "download";
if (contentDisposition) {
const filenameMatch = contentDisposition.match(/filename="(.+)"/);
if (filenameMatch && filenameMatch[1]) {
filename = filenameMatch[1];
}
}
// Track download progress if the response supports it
const contentLength = response.headers.get("Content-Length");
const total = contentLength ? Number.parseInt(contentLength, 10) : 0;
let loaded = 0;
if (response.body && total > 0) {
const reader = response.body.getReader();
const chunks: Uint8Array[] = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
loaded += value.length;
// Update progress
if (onProgress && total > 0) {
const progress = Math.round((loaded / total) * 100);
onProgress(progress);
}
}
// Combine chunks into blob
const blob = new Blob(chunks);
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
} else {
// Fallback for responses without progress tracking
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
}
return { success: true, filename };
},
onSuccess: data => {
toast.success(`${data.filename} downloaded successfully`);
},
onError: error => {
console.error("Download error:", error);
toast.error(error instanceof Error ? error.message : "Failed to download file");
},
});
}
async function getBaseFileClient(clientPromise: Promise<DriveProviderClient>) {
const client = await clientPromise;
const BASE_FILE_CLIENT = client.api.files;
return BASE_FILE_CLIENT;
}