Files
kovakyazilim/components/projects-grid.tsx
T
Ege Can Komur c0da5ae8d3 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.
2026-05-20 02:46:11 +03:00

88 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Image from "next/image";
import Link from "next/link";
import { ArrowUpRight, ExternalLink } from "lucide-react";
import type { ProjectRow } from "@/lib/types";
export function ProjectsGrid({ projects }: { projects: ProjectRow[] }) {
if (projects.length === 0) {
return (
<div className="rounded-2xl border border-dashed border-[var(--border)] bg-[var(--navy-50)]/40 p-12 text-center">
<p className="text-sm text-[var(--muted)]">
Proje portföyü yakında burada. Birlikte çalışmak isterseniz iletişime geçin.
</p>
</div>
);
}
return (
<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
{projects.map((p) => (
<article
key={p.$id}
className="group overflow-hidden rounded-2xl border border-[var(--border)] bg-white transition hover:shadow-xl"
>
<Link href={`/projeler/${p.slug}`} className="block">
<div className="relative aspect-video overflow-hidden bg-[var(--navy-50)]">
{p.image_url ? (
<Image
src={p.image_url}
alt={p.title}
fill
sizes="(min-width: 1024px) 33vw, (min-width: 768px) 50vw, 100vw"
className="object-cover transition group-hover:scale-105"
/>
) : (
<div className="flex h-full items-center justify-center text-[var(--navy)]/30">
<span className="text-5xl font-bold">{p.title.charAt(0)}</span>
</div>
)}
{p.category && (
<span className="absolute left-4 top-4 rounded-full bg-white/95 px-3 py-1 text-xs font-medium text-[var(--navy)] shadow-sm">
{p.category}
</span>
)}
</div>
</Link>
<div className="p-6">
<div className="flex items-start justify-between gap-3">
<Link href={`/projeler/${p.slug}`} className="block">
<h3 className="text-lg font-semibold text-[var(--navy)] transition group-hover:text-[var(--sky-600)]">
{p.title}
</h3>
</Link>
{p.live_url ? (
<a
href={p.live_url}
target="_blank"
rel="noopener noreferrer"
aria-label="Projeyi canlı aç"
className="text-[var(--sky-600)] hover:text-[var(--navy)]"
>
<ExternalLink className="size-4" />
</a>
) : (
<ArrowUpRight className="size-5 text-[var(--muted)] transition group-hover:text-[var(--sky-600)]" />
)}
</div>
<p className="mt-2 text-sm leading-relaxed text-[var(--muted)] line-clamp-3">
{p.description}
</p>
{p.technologies && p.technologies.length > 0 && (
<div className="mt-4 flex flex-wrap gap-1.5">
{p.technologies.map((t) => (
<span
key={t}
className="rounded-md bg-[var(--navy-50)] px-2 py-0.5 text-xs text-[var(--navy-700)]"
>
{t}
</span>
))}
</div>
)}
</div>
</article>
))}
</div>
);
}