feat: add WebP migration UI and API endpoint
- Add /api/admin/migrate-images endpoint to convert JPEG/PNG to WebP - Add WebPMigration component in admin gallery showing stats - Upload endpoint already converts new images to WebP - Migration saves ~30-50% storage space per image
This commit is contained in:
@@ -145,6 +145,61 @@ function Empty({ icon, title, desc }: any) {
|
||||
);
|
||||
}
|
||||
|
||||
/* ─── WebP Migration ─── */
|
||||
function WebPMigration({ onToast, onRefresh }: { onToast: (msg: string, type: string) => void; onRefresh: () => void }) {
|
||||
const [migrating, setMigrating] = useState(false);
|
||||
const [stats, setStats] = useState<{ totalImages: number; totalSizeMB: number } | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('/api/admin/migrate-images')
|
||||
.then(res => res.ok ? res.json() : null)
|
||||
.then(data => data && setStats({ totalImages: data.totalImages, totalSizeMB: data.totalSizeMB }))
|
||||
.catch(() => {});
|
||||
}, []);
|
||||
|
||||
const runMigration = async () => {
|
||||
setMigrating(true);
|
||||
try {
|
||||
const res = await fetch('/api/admin/migrate-images', { method: 'POST' });
|
||||
const data = await res.json();
|
||||
if (res.ok) {
|
||||
onToast(`Converted ${data.converted} images, saved ${data.totalSavedMB} MB`, 'success');
|
||||
onRefresh();
|
||||
} else {
|
||||
onToast(data.error || 'Migration failed', 'error');
|
||||
}
|
||||
} catch {
|
||||
onToast('Migration failed', 'error');
|
||||
}
|
||||
setMigrating(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
marginBottom: '16px',
|
||||
padding: '12px 16px',
|
||||
background: '#fff',
|
||||
borderRadius: '12px',
|
||||
border: `1px solid ${C.border}`,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
flexWrap: 'wrap',
|
||||
gap: '12px',
|
||||
}}>
|
||||
<div>
|
||||
<div style={{ fontWeight: 600, color: C.navy, fontSize: '14px' }}>🔄 WebP Migration</div>
|
||||
<div style={{ fontSize: '12px', color: C.textLight }}>
|
||||
{stats ? `${stats.totalImages} images · ${stats.totalSizeMB} MB total` : 'Checking...'}
|
||||
</div>
|
||||
</div>
|
||||
<Btn onClick={runMigration} disabled={migrating} size="sm">
|
||||
{migrating ? 'Migrating...' : 'Convert to WebP'}
|
||||
</Btn>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SecHead({ title, count, action }: any) {
|
||||
return (
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px', flexWrap: 'wrap', gap: '10px' }}>
|
||||
@@ -2462,6 +2517,7 @@ export default function AdminPage() {
|
||||
case 'gallery': return (
|
||||
<div>
|
||||
<SecHead title="🖼️ Image Gallery" count={images.length} />
|
||||
<WebPMigration onToast={showToast} onRefresh={loadImages} />
|
||||
<ImageGallery images={images} onDelete={deleteImage} />
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user