āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā š shadcn/directory/brennenrocks/utilcn/storage/upload-file ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
import { UploadFile } from '@/lib/upload-file';
export function MyPage() {
return (
<div>
<h2>Upload your files</h2>
<UploadFile />
</div>
);
}
import { useUploadFile } from '@/hooks/use-upload-file';
export function CustomUploadComponent() {
const { uploadFile } = useUploadFile();
const handleUpload = (file: File) => {
uploadFile({
file,
onSuccess: (url) => console.log('Uploaded to:', url),
onError: (err) => console.error('Upload failed:', err),
onProgress: (percent) => console.log(`Progress: ${percent}%`),
});
};
return (
<input
type="file"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) handleUpload(file);
}}
/>
);
}
The useUploadFile hook returns an object with an uploadFile function that accepts:
| Parameter | Type | Description |
|--------------|-----------------------------------|---------------------------------------|
| file | File | The file to upload |
| onProgress | (percent: number) => void | Optional progress callback |
| onSuccess | (fileUrl: string) => void | Optional success callback with file URL |
| onError | (error: Error) => void | Optional error callback |
'use client';
import { type ChangeEvent, useState } from 'react';
import { useUploadFile } from '@/hooks/use-upload-file';
export function UploadFile() {
const [progress, setProgress] = useState(0);
const [isUploading, setIsUploading] = useState(false);
const { uploadFile } = useUploadFile();
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) {
return;
}
setIsUploading(true);
uploadFile({
file,
onProgress: (p: number) => setProgress(p),
onSuccess: (url: string) => {
console.log('Uploaded to:', url);
setIsUploading(false);
setProgress(0);
},
onError: (err: Error) => {
console.error(`Upload failed: ${err.message}`);
setIsUploading(false);
setProgress(0);
},
});
};
return (
<div className="flex flex-col gap-4">
<input
className="file:mr-4 file:rounded-md file:border-0 file:bg-secondary file:px-4 file:py-2 file:font-semibold file:text-secondary-foreground file:text-sm hover:file:bg-secondary/80"
onChange={handleFileChange}
type="file"
/>
{isUploading && (
<div className="mt-4 flex items-center gap-2">
<div className="h-2 w-full rounded-full bg-secondary">
<div
className="h-2 rounded-full bg-primary transition-all duration-300"
style={{ width: `${progress}%` }}
/>
</div>
<span className="text-muted-foreground text-sm">{progress}%</span>
</div>
)}
</div>
);
}
import { useCallback } from 'react';
type UploadArgs = {
file: File;
onProgress?: (percent: number) => void;
onSuccess?: (fileUrl: string) => void;
onError?: (error: Error) => void;
};
export function useUploadFile() {
const uploadFile = useCallback(
async ({ file, onProgress, onSuccess, onError }: UploadArgs) => {
try {
const presignRes = await fetch('http://localhost:8080/uploadFile', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fileName: file.name,
contentLength: file.size,
}),
});
if (!presignRes.ok) {
throw new Error('Failed to get presigned URL');
}
const presign = await presignRes.json();
await new Promise<void>((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('PUT', presign.uploadUrl);
xhr.setRequestHeader('Content-Type', file.type);
xhr.upload.onprogress = (evt) => {
if (evt.lengthComputable && onProgress) {
const PERCENTAGE_MULTIPLIER = 100;
const percent = Math.round(
(evt.loaded * PERCENTAGE_MULTIPLIER) / evt.total,
);
onProgress(percent);
}
};
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve();
} else {
reject(new Error('Upload failed'));
}
};
xhr.onerror = () => reject(new Error('Upload failed'));
xhr.send(file);
});
const fileUrl = presign.fileUrl as string;
onSuccess?.(fileUrl);
return fileUrl;
} catch (error) {
const uploadError =
error instanceof Error ? error : new Error('Upload failed');
onError?.(uploadError);
throw uploadError;
}
},
[],
);
return { uploadFile };
}
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā