feat: hizmet ve proje detay sayfaları + galeri sistemi
Yeni Appwrite kolonları: - services: content (markdown), features[], faq[] (JSON-encoded), hero_image - projects: gallery[], content (markdown), client_name, industry, duration, service_slug Public sayfalar: - /hizmetler/[slug]: hero + features checklist + markdown content + FAQ accordion + ilgili projeler (service_slug eşleşmesi) - /projeler/[slug]: hero + meta tablosu (müşteri/sektör/süre/yıl) + kapak görseli + markdown vaka çalışması + lightbox galeri + diğer projeler Yeni componentler: - components/gallery.tsx: lightbox galeri (keyboard nav, prev/next, ESC kapat) - components/faq-list.tsx: accordion FAQ (tek seferde tek açık) Admin formları: - Hizmet formu: hero_image, content (markdown), features (virgülle), FAQ (her blok '---' ile ayrılır, ilk satır soru, kalanı cevap) - Proje formu: gallery (her satıra bir URL), content (markdown), client_name, industry, duration, service_slug (dropdown — hizmetlerden seçim) Linkler: - ServicesGrid kartları → /hizmetler/[slug] - ProjectsGrid kartları → /projeler/[slug] (live_url butonu ayrı, target=_blank) 29 route üretiliyor.
This commit is contained in:
@@ -10,7 +10,7 @@ import {
|
||||
Textarea,
|
||||
} from "@/components/admin/form";
|
||||
import { saveService } from "@/lib/admin-actions";
|
||||
import type { ServiceRow } from "@/lib/types";
|
||||
import type { FaqItem, ServiceRow } from "@/lib/types";
|
||||
|
||||
const ICON_OPTIONS = [
|
||||
"Globe",
|
||||
@@ -24,6 +24,20 @@ const ICON_OPTIONS = [
|
||||
"Layers",
|
||||
];
|
||||
|
||||
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 ServiceForm({ service }: { service?: ServiceRow }) {
|
||||
return (
|
||||
<div>
|
||||
@@ -56,20 +70,56 @@ export function ServiceForm({ service }: { service?: ServiceRow }) {
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<span className="mt-1 block text-xs text-[var(--muted)]">
|
||||
Lucide icon adı.
|
||||
</span>
|
||||
</label>
|
||||
<Field
|
||||
label="Hero görsel URL"
|
||||
name="hero_image"
|
||||
type="url"
|
||||
defaultValue={service?.hero_image}
|
||||
help="Detay sayfası başında gösterilir (opsiyonel)."
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
|
||||
<div className="mt-5 space-y-5">
|
||||
<Textarea
|
||||
label="Açıklama"
|
||||
label="Kısa açıklama (kart için)"
|
||||
name="description"
|
||||
required
|
||||
defaultValue={service?.description}
|
||||
rows={4}
|
||||
rows={3}
|
||||
help="Listede ve anasayfa kartında gösterilir."
|
||||
/>
|
||||
|
||||
<Textarea
|
||||
label="Detay içerik (Markdown)"
|
||||
name="content"
|
||||
defaultValue={service?.content}
|
||||
rows={10}
|
||||
placeholder={"## Yaklaşım\n\nMarkdown desteklenir…"}
|
||||
help="Hizmet detay sayfasında ana içerik olarak gösterilir."
|
||||
/>
|
||||
|
||||
<Textarea
|
||||
label="Özellikler"
|
||||
name="features"
|
||||
defaultValue={service?.features?.join(", ")}
|
||||
rows={3}
|
||||
placeholder="SEO uyumlu kod, Mobil responsive, Hızlı yüklenme, …"
|
||||
help="Virgülle ayırın. Detay sayfasında checklist olarak gösterilir."
|
||||
/>
|
||||
|
||||
<Textarea
|
||||
label="SSS"
|
||||
name="faq"
|
||||
defaultValue={faqToText(service?.faq)}
|
||||
rows={8}
|
||||
placeholder={
|
||||
"Soru 1?\nCevap 1 burada.\n---\nSoru 2?\nCevap 2 burada."
|
||||
}
|
||||
help="Her soru/cevap blokunu '---' ile ayırın. İlk satır soru, kalanı cevap."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-5">
|
||||
<Checkbox
|
||||
label="Öne çıkar (Anasayfada göster)"
|
||||
|
||||
Reference in New Issue
Block a user