113 lines
3.6 KiB
TypeScript
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 };
|