Files
frontend/apps/as/src/shared/lib/use-tus.ts
T
2026-09-21 16:56:46 +09:00

113 lines
3.6 KiB
TypeScript

"use client";
import { useCallback, useState } from "react";
import { type Upload } from "tus-js-client";
import { handleTusUpload } from "@/entities/tus";
import { useAlertStore } from "./use-alert-store";
interface UploadFileOptions {
fileKey?: string;
metadata?: Record<string, string>;
}
interface UseTusOptions {
onSuccess?: (atflCd: string, atflId: string, file: File, fileKey: string) => void;
onError?: (error: Error, file: File, fileKey: string) => void;
accept?: string;
}
interface UseTusReturn {
uploadFile: (file: File, options?: UploadFileOptions) => void;
uploadFileAsync: (file: File, options?: UploadFileOptions) => Promise<string>;
progress: Record<string, number>;
isUploading: Record<string, boolean>;
uploads: Record<string, Upload>;
}
function getDefaultFileKey(file: File) {
return `${file.name}::${file.lastModified}::${file.size}`;
}
function useTus(options: UseTusOptions = {}): UseTusReturn {
const alert = useAlertStore((state) => state.setMessage);
const [progress, setProgress] = useState<Record<string, number>>({});
const [isUploading, setIsUploading] = useState<Record<string, boolean>>({});
const [uploads, setUploads] = useState<Record<string, Upload>>({});
const validateFileType = useCallback(
(file: File): boolean => {
if (!options.accept) return true;
const allowedExtensions = options.accept.split(",");
const fileExtension = file.name.substring(file.name.lastIndexOf(".")).toLowerCase();
if (!allowedExtensions.includes(fileExtension)) {
alert(`허용되지 않은 파일 형식입니다. (${fileExtension})`);
return false;
}
return true;
},
[options.accept, alert],
);
const uploadFileAsync = useCallback(
(file: File, uploadOptions?: UploadFileOptions): Promise<string> => {
if (!validateFileType(file)) {
return Promise.reject(new Error("Invalid file type"));
}
const fileKey = uploadOptions?.fileKey ?? getDefaultFileKey(file);
setIsUploading((prev) => ({ ...prev, [fileKey]: true }));
setProgress((prev) => ({ ...prev, [fileKey]: 0 }));
return new Promise<string>((resolve, reject) => {
handleTusUpload(file, {
metadata: uploadOptions?.metadata,
handleUploadStart: (upload) => {
setUploads((prev) => ({ ...prev, [fileKey]: upload }));
},
handleProgress: (percentage) => {
setProgress((prev) => ({ ...prev, [fileKey]: percentage }));
},
handleSuccess: (_upload, atflCd, atflId) => {
setIsUploading((prev) => ({ ...prev, [fileKey]: false }));
setProgress((prev) => ({ ...prev, [fileKey]: 100 }));
options.onSuccess?.(atflCd, atflId, file, fileKey);
resolve(atflCd);
},
handleError: (error) => {
setIsUploading((prev) => ({ ...prev, [fileKey]: false }));
alert("파일 업로드 중 오류가 발생했습니다.");
options.onError?.(error, file, fileKey);
reject(error);
},
});
});
},
[validateFileType, options, alert],
);
const uploadFile = useCallback(
(file: File, uploadOptions?: UploadFileOptions) => {
void uploadFileAsync(file, uploadOptions);
},
[uploadFileAsync],
);
return {
uploadFile,
uploadFileAsync,
progress,
isUploading,
uploads,
};
}
export { getDefaultFileKey, useTus };
export type { UploadFileOptions, UseTusOptions, UseTusReturn };