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
+12 -13
View File
@@ -9,6 +9,7 @@ import {
PrimaryButton,
Textarea,
} from "@/components/admin/form";
import { MediaPicker } from "@/components/admin/media-picker";
import { saveProject } from "@/lib/admin-actions";
import { listServices } from "@/lib/data";
import type { ProjectRow } from "@/lib/types";
@@ -63,12 +64,6 @@ export async function ProjectForm({ project }: { project?: ProjectRow }) {
type="number"
defaultValue={project?.year ?? new Date().getFullYear()}
/>
<Field
label="Kapak görseli URL"
name="image_url"
type="url"
defaultValue={project?.image_url}
/>
<Field
label="Canlı URL"
name="live_url"
@@ -85,6 +80,13 @@ export async function ProjectForm({ project }: { project?: ProjectRow }) {
</div>
<div className="mt-5 space-y-5">
<MediaPicker
label="Kapak görseli"
name="image_url"
defaultValue={project?.image_url}
help="Liste ve detay sayfasının üstündeki ana görsel."
/>
<Textarea
label="Kısa açıklama (kart için)"
name="description"
@@ -104,15 +106,12 @@ export async function ProjectForm({ project }: { project?: ProjectRow }) {
help="Proje detay sayfasında uzun anlatım olarak gösterilir."
/>
<Textarea
<MediaPicker
label="Galeri görselleri"
name="gallery"
defaultValue={project?.gallery?.join("\n")}
rows={5}
placeholder={
"https://example.com/image1.jpg\nhttps://example.com/image2.jpg"
}
help="Her satıra bir URL. Medya kütüphanesinden URL'leri kopyalayın."
multiple
defaultValue={project?.gallery ?? []}
help="Detay sayfasında lightbox galeri olarak gösterilir. Birden fazla görsel yükleyebilirsiniz."
/>
<Textarea