Files
penx/apps/web/lib/uploadthing.ts
2025-04-20 23:34:57 +08:00

134 lines
3.4 KiB
TypeScript

import * as React from 'react'
import { generateReactHelpers } from '@uploadthing/react'
import { toast } from 'sonner'
import type {
ClientUploadedFileData,
UploadFilesOptions,
} from 'uploadthing/types'
import { z } from 'zod'
import { uploadFile } from '@penx/services/uploadFile'
import type { OurFileRouter } from '../app/api/uploadthing/route'
import { getUrl } from './utils'
export interface UploadedFile<T = unknown> extends ClientUploadedFileData<T> {}
interface UseUploadFileProps
extends Pick<
UploadFilesOptions<OurFileRouter, keyof OurFileRouter>,
'headers' | 'onUploadBegin' | 'onUploadProgress' | 'skipPolling'
> {
onUploadComplete?: (file: UploadedFile) => void
onUploadError?: (error: unknown) => void
}
export function useUploadFile({
onUploadComplete,
onUploadError,
...props
}: UseUploadFileProps = {}) {
const [uploadedFile, setUploadedFile] = React.useState<UploadedFile>()
const [uploadingFile, setUploadingFile] = React.useState<File>()
const [progress, setProgress] = React.useState<number>(0)
const [isUploading, setIsUploading] = React.useState(false)
async function uploadThing(file: File) {
setIsUploading(true)
setUploadingFile(file)
try {
const data = await uploadFile(file)
const uploadedFile = {
key: data.hash,
appUrl: getUrl(data.url),
name: file.name,
size: file.size,
type: file.type,
// url: URL.createObjectURL(file),
url: getUrl(data.url),
} as UploadedFile
setUploadedFile(uploadedFile)
onUploadComplete?.(uploadedFile)
return uploadedFile
} catch (error) {
const errorMessage = getErrorMessage(error)
const message =
errorMessage.length > 0
? errorMessage
: 'Something went wrong, please try again later.'
toast.error(message)
onUploadError?.(error)
// Mock upload for unauthenticated users
// toast.info('User not logged in. Mocking upload process.');
const mockUploadedFile = {
key: 'mock-key-0',
appUrl: `https://mock-app-url.com/${file.name}`,
name: file.name,
size: file.size,
type: file.type,
url: URL.createObjectURL(file),
} as UploadedFile
// Simulate upload progress
let progress = 0
const simulateProgress = async () => {
while (progress < 100) {
await new Promise((resolve) => setTimeout(resolve, 50))
progress += 2
setProgress(Math.min(progress, 100))
}
}
await simulateProgress()
setUploadedFile(mockUploadedFile)
return mockUploadedFile
} finally {
setProgress(0)
setIsUploading(false)
setUploadingFile(undefined)
}
}
return {
isUploading,
progress,
uploadedFile,
uploadFile: uploadThing,
uploadingFile,
}
}
export const { uploadFiles, useUploadThing } =
generateReactHelpers<OurFileRouter>()
export function getErrorMessage(err: unknown) {
const unknownError = 'Something went wrong, please try again later.'
if (err instanceof z.ZodError) {
const errors = err.issues.map((issue) => {
return issue.message
})
return errors.join('\n')
} else if (err instanceof Error) {
return err.message
} else {
return unknownError
}
}
export function showErrorToast(err: unknown) {
const errorMessage = getErrorMessage(err)
return toast.error(errorMessage)
}