diff --git a/apps/admin/src/components/MediaLibrary.tsx b/apps/admin/src/components/MediaLibrary.tsx index 11a7b8d..620cc3c 100644 --- a/apps/admin/src/components/MediaLibrary.tsx +++ b/apps/admin/src/components/MediaLibrary.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useState, useRef } from 'react'; import { Box, Button, Stack, Typography, Paper, TextField, MenuItem } from '@mui/material'; type MediaItem = { @@ -138,6 +138,42 @@ export default function MediaLibrary({ } catch {} }; + const fileInputRef = useRef(null); + + const handleFileUpload = async (files: FileList | null) => { + if (!files || files.length === 0) return; + + setUploading(true); + setUploadingCount(files.length); + setError(''); + + for (let i = 0; i < files.length; i++) { + const file = files[i]; + if (!file.type.startsWith('image/')) { + setError(`Skipped ${file.name}: not an image`); + continue; + } + + try { + const fd = new FormData(); + fd.append('image', file, file.name); + const res = await fetch('/api/media/image', { method: 'POST', body: fd }); + if (!res.ok) throw new Error(await res.text()); + } catch (err: any) { + setError(err?.message || `Failed to upload ${file.name}`); + } + } + + setUploading(false); + setUploadingCount(0); + await load(); + + // Reset file input + if (fileInputRef.current) { + fileInputRef.current.value = ''; + } + }; + return ( @@ -155,6 +191,22 @@ export default function MediaLibrary({ Name Largest + handleFileUpload(e.target.files)} + /> + Tip: paste an image (Cmd/Ctrl+V) to upload{uploading ? ` — uploading ${uploadingCount}…` : ''}