Admin File Upload
Different from backend — frontend builds FormData, backend handles two-phase save.
API Layer
export const createCategory = async (data: CreateCategoryPayload) => {
const formData = new FormData();
Object.entries(data).forEach(([key, value]) => {
if (value != null && !['imageUrl', 'coverUrl'].includes(key)) {
formData.append(key, value.toString());
}
});
if (data.imageUrl) formData.append('imageUrl', data.imageUrl);
if (data.coverUrl) formData.append('coverUrl', data.coverUrl);
return (await api.post('/category/create-category', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
})).data;
};
Exclude file keys from Object.entries loop — append File objects separately.
Form UI
<input type="file" accept="image/*" hidden ref={fileRef} />
<Button type="button" => fileRef.current?.click()}>Upload</Button>
{preview && <img src={preview} alt="Preview" />}
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file?.type.startsWith('image/')) { toast.error('Image only'); return; }
if (file.size > 5 * 1024 * 1024) { toast.error('Max 5MB'); return; }
setImageFile(file);
setPreview(URL.createObjectURL(file));
};
Submit
await createMutation.mutateAsync({ ...formData, imageUrl: imageFile ?? undefined });
Edit Form — Current vs New
Show existing: `${API_URL}${entity.imageUrl}`
Show new preview from FileReader/ObjectURL alongside.
JSON Fallback (products)
If no files, some endpoints accept JSON body instead of FormData.
Axios Note
No default Content-Type on axios instance — allows multipart boundary auto-set.