feat: MediaPicker — sürükle-bırak + progress bar + kütüphane modal

Mevcut sorun:
- Her görsel için medya sayfasına git, yükle, URL kopyala, forma yapıştır → 4 adım
- Sürükle-bırak yok, progress yok, hangi dosyanın yüklendiği belirsiz

Çözüm: MediaPicker component (tek/çoklu mode)

API route'ları:
- POST /api/admin/media/upload — session auth + Appwrite Storage upload
- GET /api/admin/media/list — kütüphane modal için dosya listesi

Component özellikleri:
- Sürükle-bırak drop zone (hover state ile)
- Multiple file upload (çoklu mode)
- XHR ile gerçek progress bar (%) — Server Action ile alınamazdı
- Görsel preview (single: aspect-video, multiple: aspect-square grid)
- Hover'da × ile kaldırma
- Multiple mode'da sırasını değiştirme
- 'Kütüphaneden seç' modal — daha önce yüklenmiş görselleri grid'de göster, tıklayınca seç
- Error handling (dosya boyutu, ağ hatası vb.)
- Başarılı yüklemeyi 2 saniye gösterip kaybetme

Form alanları → MediaPicker (URL field'ları kaldırıldı):
- Blog: cover_image, seo_image
- Hizmet: hero_image
- Proje: image_url (kapak), gallery (çoklu)
- Referans: image_url
- Sektör: hero_image
- Ekip: photo_url
- SEO sayfa: og_image
- SEO global: default_og_image
- Site Settings: client_logos (çoklu)

Backward compat: form data formatı aynı kalıyor — hidden input ile URL satır satır.
admin-actions değişmedi. URL elle yapıştırmak hala mümkün (kütüphaneden URL kopyala).
This commit is contained in:
Ege Can Komur
2026-05-20 04:11:41 +03:00
parent cf46e30a7e
commit dbc55e7527
12 changed files with 627 additions and 52 deletions
+36
View File
@@ -0,0 +1,36 @@
import { NextResponse } from "next/server";
import {
ID,
MEDIA_BUCKET_ID,
storage,
} from "@/lib/appwrite-rest";
import { requireSessionSecret } from "@/lib/auth";
export const runtime = "nodejs";
export async function POST(req: Request) {
const secret = await requireSessionSecret();
const formData = await req.formData();
const file = formData.get("file");
if (!(file instanceof File) || file.size === 0) {
return NextResponse.json({ error: "Dosya seçilmedi" }, { status: 400 });
}
try {
const created = await storage.createFile(
MEDIA_BUCKET_ID,
ID.unique(),
file,
secret,
);
return NextResponse.json({
id: created.$id,
name: created.name,
size: created.sizeOriginal,
mimeType: created.mimeType,
url: storage.fileViewUrl(MEDIA_BUCKET_ID, created.$id),
});
} catch (err) {
const msg = err instanceof Error ? err.message : "Yükleme başarısız";
return NextResponse.json({ error: msg }, { status: 500 });
}
}