dbc55e7527
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).
142 lines
4.3 KiB
TypeScript
142 lines
4.3 KiB
TypeScript
import { Save } from "lucide-react";
|
||
import {
|
||
Checkbox,
|
||
Field,
|
||
FormActions,
|
||
FormShell,
|
||
GhostLink,
|
||
PageHeader,
|
||
PrimaryButton,
|
||
Textarea,
|
||
} from "@/components/admin/form";
|
||
import { MediaPicker } from "@/components/admin/media-picker";
|
||
import { saveIndustry } from "@/lib/admin-actions";
|
||
import type { FaqItem, IndustryRow } from "@/lib/types";
|
||
|
||
function faqToText(items?: string[] | null): string {
|
||
if (!items) return "";
|
||
const parsed: FaqItem[] = [];
|
||
for (const raw of items) {
|
||
try {
|
||
const obj = JSON.parse(raw) as Partial<FaqItem>;
|
||
if (obj.q && obj.a) parsed.push({ q: obj.q, a: obj.a });
|
||
} catch {
|
||
/* ignore */
|
||
}
|
||
}
|
||
return parsed.map((it) => `${it.q}\n${it.a}`).join("\n---\n");
|
||
}
|
||
|
||
export function IndustryForm({ row }: { row?: IndustryRow }) {
|
||
return (
|
||
<div>
|
||
<PageHeader
|
||
title={row ? "Sektörü düzenle" : "Yeni sektör"}
|
||
backHref="/admin/sektorler"
|
||
description="Örn: 'Avukat web tasarımı', 'Doktor web tasarımı', 'İnşaat firması web tasarımı'"
|
||
/>
|
||
<form action={saveIndustry}>
|
||
{row && <input type="hidden" name="id" value={row.$id} />}
|
||
<FormShell>
|
||
<div className="grid gap-5 md:grid-cols-2">
|
||
<Field
|
||
label="Başlık"
|
||
name="title"
|
||
required
|
||
defaultValue={row?.title}
|
||
placeholder="Avukat Web Tasarımı"
|
||
/>
|
||
<Field
|
||
label="Slug"
|
||
name="slug"
|
||
defaultValue={row?.slug}
|
||
placeholder="avukat-web-tasarimi"
|
||
/>
|
||
<MediaPicker
|
||
label="Hero görsel"
|
||
name="hero_image"
|
||
defaultValue={row?.hero_image}
|
||
help="Sektör sayfasının üst kısmında gösterilir."
|
||
/>
|
||
<Field
|
||
label="Sıra"
|
||
name="order"
|
||
type="number"
|
||
defaultValue={row?.order ?? 0}
|
||
/>
|
||
</div>
|
||
|
||
<div className="mt-5 space-y-5">
|
||
<Textarea
|
||
label="Alt başlık / kısa açıklama"
|
||
name="subtitle"
|
||
rows={2}
|
||
defaultValue={row?.subtitle}
|
||
placeholder="Avukatlar için KVKK uyumlu, randevu sistemli, SEO odaklı modern web siteleri."
|
||
/>
|
||
|
||
<Textarea
|
||
label="İçerik (Markdown)"
|
||
name="content"
|
||
rows={10}
|
||
defaultValue={row?.content}
|
||
placeholder="## Sektör özellikleri\n\nAvukatlar için..."
|
||
/>
|
||
|
||
<Textarea
|
||
label="Özellikler"
|
||
name="features"
|
||
rows={3}
|
||
defaultValue={row?.features?.join(", ")}
|
||
placeholder="KVKK uyumlu form, Randevu sistemi, Blog modülü, Çoklu dil"
|
||
help="Virgülle ayırın."
|
||
/>
|
||
|
||
<Textarea
|
||
label="SSS"
|
||
name="faq"
|
||
rows={8}
|
||
defaultValue={faqToText(row?.faq)}
|
||
placeholder={"Avukatlar için web sitesi neden önemli?\nKVKK uyumu için..."}
|
||
help='Her blok "---" ile ayrılır. İlk satır soru, kalanı cevap.'
|
||
/>
|
||
</div>
|
||
|
||
<h3 className="mt-8 text-sm font-semibold uppercase tracking-wider text-[var(--muted)]">
|
||
SEO
|
||
</h3>
|
||
<div className="mt-3 space-y-5">
|
||
<Field
|
||
label="SEO başlığı"
|
||
name="seo_title"
|
||
defaultValue={row?.seo_title}
|
||
placeholder="Avukat Web Tasarımı | Kocaeli — KVKK Uyumlu Modern Site"
|
||
/>
|
||
<Textarea
|
||
label="SEO açıklaması"
|
||
name="seo_description"
|
||
rows={2}
|
||
defaultValue={row?.seo_description}
|
||
/>
|
||
</div>
|
||
|
||
<div className="mt-5">
|
||
<Checkbox
|
||
label="Öne çıkar"
|
||
name="featured"
|
||
defaultChecked={row?.featured ?? false}
|
||
/>
|
||
</div>
|
||
|
||
<FormActions>
|
||
<GhostLink href="/admin/sektorler">İptal</GhostLink>
|
||
<PrimaryButton>
|
||
<Save className="size-4" /> Kaydet
|
||
</PrimaryButton>
|
||
</FormActions>
|
||
</FormShell>
|
||
</form>
|
||
</div>
|
||
);
|
||
}
|