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:
Ege Can Komur
2026-05-20 02:46:11 +03:00
parent edd0af76dc
commit c0da5ae8d3
13 changed files with 792 additions and 47 deletions
@@ -12,5 +12,5 @@ export default async function EditProjectPage({
const { id } = await params;
const project = await getRow<ProjectRow>(TABLES.projects, id);
if (!project) notFound();
return <ProjectForm project={project} />;
return await ProjectForm({ project });
}
+60 -5
View File
@@ -10,9 +10,12 @@ import {
Textarea,
} from "@/components/admin/form";
import { saveProject } from "@/lib/admin-actions";
import { listServices } from "@/lib/data";
import type { ProjectRow } from "@/lib/types";
export function ProjectForm({ project }: { project?: ProjectRow }) {
export async function ProjectForm({ project }: { project?: ProjectRow }) {
const services = await listServices();
return (
<div>
<PageHeader
@@ -26,6 +29,34 @@ export function ProjectForm({ project }: { project?: ProjectRow }) {
<Field label="Başlık" name="title" required defaultValue={project?.title} />
<Field label="Slug" name="slug" defaultValue={project?.slug} />
<Field label="Kategori" name="category" defaultValue={project?.category} />
<label className="block">
<span className="text-sm font-medium text-[var(--navy)]">
İlgili hizmet
</span>
<select
name="service_slug"
defaultValue={project?.service_slug ?? ""}
className="mt-1.5 w-full rounded-xl border border-[var(--border)] bg-white px-4 py-2.5 text-sm outline-none focus:border-[var(--sky)] focus:ring-2 focus:ring-[var(--sky)]/20"
>
<option value=""> Yok </option>
{services.map((s) => (
<option key={s.slug} value={s.slug}>
{s.title}
</option>
))}
</select>
<span className="mt-1 block text-xs text-[var(--muted)]">
Bu projenin ait olduğu hizmet detay sayfasında "ilgili projeler" olarak görünür.
</span>
</label>
<Field label="Müşteri" name="client_name" defaultValue={project?.client_name} />
<Field label="Sektör" name="industry" defaultValue={project?.industry} />
<Field
label="Süre"
name="duration"
defaultValue={project?.duration}
placeholder="örn: 3 ay"
/>
<Field
label="Yıl"
name="year"
@@ -33,7 +64,7 @@ export function ProjectForm({ project }: { project?: ProjectRow }) {
defaultValue={project?.year ?? new Date().getFullYear()}
/>
<Field
label="Görsel URL"
label="Kapak görseli URL"
name="image_url"
type="url"
defaultValue={project?.image_url}
@@ -52,15 +83,39 @@ export function ProjectForm({ project }: { project?: ProjectRow }) {
help="Virgülle ayırın."
/>
</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={project?.description}
rows={6}
rows={3}
/>
<Textarea
label="Vaka çalışması içeriği (Markdown)"
name="content"
defaultValue={project?.content}
rows={12}
placeholder={
"## Müşteri\n\n## Problem\n\n## Çözüm\n\n## Sonuç"
}
help="Proje detay sayfasında uzun anlatım olarak gösterilir."
/>
<Textarea
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."
/>
</div>
<div className="mt-5">
<Checkbox
label="Öne çıkar (Anasayfada göster)"
+2 -2
View File
@@ -1,5 +1,5 @@
import { ProjectForm } from "../form";
export default function NewProjectPage() {
return <ProjectForm />;
export default async function NewProjectPage() {
return await ProjectForm({});
}