返回 presentation-ai
use-upload-file.ts
根目录 / src / components / plate / hooks / use-upload-file.ts
1 // @ts-nocheck
2 import * as React from "react";
3 import { toast } from "sonner";
4 import {
5 type ClientUploadedFileData,
6 type UploadFilesOptions,
7 } from "uploadthing/types";
8 import * as z from "zod";
9
10 import { type OurFileRouter } from "@/app/api/uploadthing/core";
11 import { uploadFiles } from "@/hooks/globals/useUploadthing";
12
13 export type UploadedFile<T = unknown> = ClientUploadedFileData<T>;
14
15 interface UseUploadFileProps extends Partial<
16 Pick<
17 UploadFilesOptions<OurFileRouter["editorUploader"]>,
18 "headers" | "input" | "onUploadBegin" | "onUploadProgress" | "skipPolling"
19 >
20 > {
21 onUploadComplete?: (file: UploadedFile) => void;
22 onUploadError?: (error: unknown) => void;
23 }
24
25 export function useUploadFile({
26 onUploadComplete,
27 onUploadError,
28 ...props
29 }: UseUploadFileProps = {}) {
30 const [uploadedFile, setUploadedFile] = React.useState<UploadedFile>();
31 const [uploadingFile, setUploadingFile] = React.useState<File>();
32 const [progress, setProgress] = React.useState<number>(0);
33 const [isUploading, setIsUploading] = React.useState(false);
34
35 async function uploadThing(file: File) {
36 setIsUploading(true);
37 setUploadingFile(file);
38
39 try {
40 const res = await uploadFiles("editorUploader", {
41 ...props,
42 files: [file],
43 onUploadProgress: (event) => {
44 const { progress } = event;
45 setProgress(Math.min(progress, 100));
46 props.onUploadProgress?.(event);
47 },
48 });
49
50 const nextUploadedFile = res[0];
51 setUploadedFile(nextUploadedFile);
52
53 onUploadComplete?.(nextUploadedFile ?? ({} as UploadedFile));
54
55 return nextUploadedFile;
56 } catch (error) {
57 const errorMessage = getErrorMessage(error);
58
59 const message =
60 errorMessage.length > 0
61 ? errorMessage
62 : "Something went wrong, please try again later.";
63
64 toast.error(message);
65
66 // Note: We don't call onUploadError here because we'll fall back to mock upload
67 // The toast already notifies the user of the original error
68
69 // Mock upload for unauthenticated users
70 // toast.info('User not logged in. Mocking upload process.');
71 const mockUploadedFile = {
72 key: "mock-key-0",
73 appUrl: `https://mock-app-url.com/${file.name}`,
74 name: file.name,
75 size: file.size,
76 type: file.type,
77 ufsUrl: URL.createObjectURL(file),
78 url: URL.createObjectURL(file),
79 } as UploadedFile;
80
81 // Simulate upload progress
82 let progress = 0;
83
84 const simulateProgress = async () => {
85 while (progress < 100) {
86 await new Promise((resolve) => setTimeout(resolve, 50));
87 progress += 2;
88 setProgress(Math.min(progress, 100));
89 }
90 };
91
92 await simulateProgress();
93
94 setUploadedFile(mockUploadedFile);
95
96 return mockUploadedFile;
97 } finally {
98 setProgress(0);
99 setIsUploading(false);
100 setUploadingFile(undefined);
101 }
102 }
103
104 return {
105 isUploading,
106 progress,
107 uploadedFile,
108 uploadFile: uploadThing,
109 uploadingFile,
110 };
111 }
112
113 export function showErrorToast(error: unknown) {
114 toast.error(getErrorMessage(error));
115 }
116
117 export function getErrorMessage(err: unknown) {
118 const unknownError = "Something went wrong, please try again later.";
119
120 if (err instanceof z.ZodError) {
121 const errors = err.issues.map((issue) => {
122 return issue.message;
123 });
124
125 return errors.join("\n");
126 } else if (err instanceof Error) {
127 return err.message;
128 } else {
129 return unknownError;
130 }
131 }
132
132 lines TYPESCRIPT