diff --git a/app/(site)/hakkimizda/page.tsx b/app/(site)/hakkimizda/page.tsx
index b107ce0..5097b52 100644
--- a/app/(site)/hakkimizda/page.tsx
+++ b/app/(site)/hakkimizda/page.tsx
@@ -2,6 +2,8 @@ import type { Metadata } from "next";
import Image from "next/image";
import { SectionTitle } from "@/components/section-title";
import { CheckCircle2 } from "lucide-react";
+import { TeamGrid } from "@/components/team-grid";
+import { listTeamMembers } from "@/lib/data";
import { buildMetadata } from "@/lib/seo";
export async function generateMetadata(): Promise {
@@ -35,7 +37,9 @@ const values = [
},
];
-export default function AboutPage() {
+export default async function AboutPage() {
+ const team = await listTeamMembers();
+
return (
<>
@@ -76,6 +80,21 @@ export default function AboutPage() {
+ {team.length > 0 && (
+
+ )}
+
{[
diff --git a/app/(site)/page.tsx b/app/(site)/page.tsx
index 1a462c7..545c148 100644
--- a/app/(site)/page.tsx
+++ b/app/(site)/page.tsx
@@ -11,6 +11,8 @@ import { LogoCloud } from "@/components/logo-cloud";
import { QuickLeadForm } from "@/components/quick-lead-form";
import { WhyUs } from "@/components/why-us";
import { ProcessSteps } from "@/components/process-steps";
+import { Guarantee } from "@/components/guarantee";
+import { HomepageFaq } from "@/components/homepage-faq";
import { OrganizationLd } from "@/components/json-ld";
import {
getSiteSettings,
@@ -118,6 +120,8 @@ export default async function Home() {
+
+
@@ -165,6 +169,8 @@ export default async function Home() {
)}
+
+
diff --git a/app/(site)/projeler/[slug]/page.tsx b/app/(site)/projeler/[slug]/page.tsx
index 7a7a951..9622ec2 100644
--- a/app/(site)/projeler/[slug]/page.tsx
+++ b/app/(site)/projeler/[slug]/page.tsx
@@ -7,6 +7,23 @@ import { marked } from "marked";
import { getProjectBySlug, listProjects } from "@/lib/data";
import { buildMetadata } from "@/lib/seo";
import { Gallery } from "@/components/gallery";
+import { TrendingUp } from "lucide-react";
+import type { ProjectMetric } from "@/lib/types";
+
+function parseMetrics(items?: string[] | null): ProjectMetric[] {
+ if (!items) return [];
+ const out: ProjectMetric[] = [];
+ for (const raw of items) {
+ try {
+ const obj = JSON.parse(raw) as Partial
;
+ if (obj.value && obj.label) out.push({ value: obj.value, label: obj.label });
+ } catch {
+ const [value, label] = raw.split("|").map((s) => s.trim());
+ if (value && label) out.push({ value, label });
+ }
+ }
+ return out;
+}
export async function generateMetadata({
params,
@@ -41,6 +58,8 @@ export default async function ProjectDetailPage({
? (marked.parse(project.content, { async: false }) as string)
: "";
+ const metrics = parseMetrics(project.metrics);
+
const meta: { icon: React.ReactNode; label: string; value: string }[] = [];
if (project.client_name)
meta.push({ icon: , label: "Müşteri", value: project.client_name });
@@ -140,6 +159,30 @@ export default async function ProjectDetailPage({
/>
)}
+
+ {metrics.length > 0 && (
+
+
+
+ Sonuçlar
+
+
+ {metrics.map((m, i) => (
+
+
+ {m.value}
+
+
+ {m.label}
+
+
+ ))}
+
+
+ )}
diff --git a/app/(site)/sektor/[slug]/page.tsx b/app/(site)/sektor/[slug]/page.tsx
new file mode 100644
index 0000000..e21028e
--- /dev/null
+++ b/app/(site)/sektor/[slug]/page.tsx
@@ -0,0 +1,210 @@
+import Image from "next/image";
+import Link from "next/link";
+import type { Metadata } from "next";
+import { notFound } from "next/navigation";
+import { ArrowRight, ArrowLeft, CheckCircle2 } from "lucide-react";
+import { marked } from "marked";
+import {
+ getIndustryBySlug,
+ listProjects,
+ listServices,
+ getSiteSettings,
+} from "@/lib/data";
+import { buildMetadata } from "@/lib/seo";
+import { ProjectsGrid } from "@/components/projects-grid";
+import { ServicesGrid } from "@/components/services-grid";
+import { TrustBand } from "@/components/trust-band";
+import { Guarantee } from "@/components/guarantee";
+import { QuickLeadForm } from "@/components/quick-lead-form";
+import { FaqList } from "@/components/faq-list";
+import { FaqLd } from "@/components/json-ld";
+import { SectionTitle } from "@/components/section-title";
+import type { FaqItem } from "@/lib/types";
+
+export async function generateMetadata({
+ params,
+}: {
+ params: Promise<{ slug: string }>;
+}): Promise {
+ const { slug } = await params;
+ const industry = await getIndustryBySlug(slug);
+ if (!industry) return { title: "Sektör bulunamadı" };
+ return buildMetadata(`/sektor/${slug}`, {
+ title: industry.seo_title || industry.title,
+ description: industry.seo_description || industry.subtitle || undefined,
+ });
+}
+
+function parseFaq(items?: string[] | null): FaqItem[] {
+ if (!items) return [];
+ const out: FaqItem[] = [];
+ for (const raw of items) {
+ try {
+ const obj = JSON.parse(raw) as Partial;
+ if (obj.q && obj.a) out.push({ q: obj.q, a: obj.a });
+ } catch {
+ /* ignore */
+ }
+ }
+ return out;
+}
+
+export default async function IndustryPage({
+ params,
+}: {
+ params: Promise<{ slug: string }>;
+}) {
+ const { slug } = await params;
+ const industry = await getIndustryBySlug(slug);
+ if (!industry) notFound();
+
+ const [services, projects, settings] = await Promise.all([
+ listServices(),
+ listProjects({ limit: 6 }),
+ getSiteSettings(),
+ ]);
+
+ const faqItems = parseFaq(industry.faq);
+ const html = industry.content
+ ? (marked.parse(industry.content, { async: false }) as string)
+ : "";
+
+ return (
+ <>
+
+
+
+
+
+
+
Anasayfa
+
+
+
+
+
+ Sektöre özel çözüm
+
+
+ {industry.title}
+
+ {industry.subtitle && (
+
+ {industry.subtitle}
+
+ )}
+
+
+
+ Ücretsiz keşif görüşmesi
+
+
+
+ Ücretsiz site analizi
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {industry.features && industry.features.length > 0 && (
+
+
+
+
+ {industry.features.map((f) => (
+
+
+ {f}
+
+ ))}
+
+
+
+ )}
+
+ {html && (
+
+ )}
+
+
+
+ {projects.length > 0 && (
+
+ )}
+
+ {services.length > 0 && (
+
+ )}
+
+ {faqItems.length > 0 && (
+
+ )}
+ >
+ );
+}
diff --git a/app/(site)/site-analizi/page.tsx b/app/(site)/site-analizi/page.tsx
new file mode 100644
index 0000000..53624a7
--- /dev/null
+++ b/app/(site)/site-analizi/page.tsx
@@ -0,0 +1,137 @@
+import type { Metadata } from "next";
+import { Search, Gauge, Smartphone, TrendingUp, Lock, FileText } from "lucide-react";
+import { AuditForm } from "@/components/audit-form";
+import { buildMetadata } from "@/lib/seo";
+
+export async function generateMetadata(): Promise {
+ return buildMetadata("/site-analizi", {
+ title: "Ücretsiz Site Analizi",
+ description:
+ "Web sitenizin SEO, hız, mobil uyumluluk ve dönüşüm performansını ölçen ücretsiz analiz raporu. 24 saat içinde e-postanıza.",
+ });
+}
+
+const CHECKS = [
+ {
+ icon: Gauge,
+ title: "Sayfa hızı (Core Web Vitals)",
+ description: "LCP, INP, CLS skorları + iyileştirme önerileri.",
+ },
+ {
+ icon: Smartphone,
+ title: "Mobil uyumluluk",
+ description: "Mobile-friendly testi + responsive sorunları.",
+ },
+ {
+ icon: TrendingUp,
+ title: "SEO temelleri",
+ description: "Meta etiketler, schema, sitemap, internal linking analizi.",
+ },
+ {
+ icon: Lock,
+ title: "Güvenlik & SSL",
+ description: "HTTPS, güvenlik başlıkları, açık güvenlik açıkları taraması.",
+ },
+ {
+ icon: FileText,
+ title: "İçerik kalitesi",
+ description: "Heading yapısı, alt text, içerik uzunluğu, okunabilirlik.",
+ },
+ {
+ icon: Search,
+ title: "Rakip karşılaştırması",
+ description: "Sektörünüzdeki 2-3 rakiple anahtar metriklerin kıyaslanması.",
+ },
+];
+
+export default function AuditPage() {
+ return (
+ <>
+
+
+
+
+
+
+
+ ✦ Ücretsiz — taahhüt yok
+
+
+ Web sitenizin satış kapasitesini 24 saatte ölçelim
+
+
+ Mevcut sitenizin SEO, hız, mobil uyumluluk ve dönüşüm
+ performansını analiz edip, neyin daha çok müşteri getireceğini
+ net bir raporla gösteriyoruz.
+
+
+
+
+
+ ✓
+
+ Sitenizin Google'da neden yeterince görünmediği
+
+
+
+ ✓
+
+ Ziyaretçi neden müşteriye dönüşmüyor — somut nedenler
+
+
+
+ ✓
+
+ Rakiplerinize göre hangi alanlarda gerideniz
+
+
+
+ ✓
+
+ Öncelikli iyileştirme listesi (etki/efor matrisli)
+
+
+
+
+
+
+
+
+
+
+
+ Raporda neler var?
+
+
+ Otomatik araç çıktısı değil — uzmanlarımızın değerlendirdiği,
+ önceliklendirilmiş ve eyleme dökülebilir bir doküman.
+
+
+
+ {CHECKS.map((c) => {
+ const Icon = c.icon;
+ return (
+
+
+
+
+
+ {c.title}
+
+
+ {c.description}
+
+
+ );
+ })}
+
+
+
+ >
+ );
+}
diff --git a/app/admin/(protected)/ekip/[id]/edit/page.tsx b/app/admin/(protected)/ekip/[id]/edit/page.tsx
new file mode 100644
index 0000000..60820ed
--- /dev/null
+++ b/app/admin/(protected)/ekip/[id]/edit/page.tsx
@@ -0,0 +1,16 @@
+import { notFound } from "next/navigation";
+import { getRow } from "@/lib/data";
+import { TABLES } from "@/lib/appwrite-rest";
+import type { TeamMemberRow } from "@/lib/types";
+import { TeamMemberForm } from "../../form";
+
+export default async function EditTeamPage({
+ params,
+}: {
+ params: Promise<{ id: string }>;
+}) {
+ const { id } = await params;
+ const row = await getRow(TABLES.teamMembers, id);
+ if (!row) notFound();
+ return ;
+}
diff --git a/app/admin/(protected)/ekip/form.tsx b/app/admin/(protected)/ekip/form.tsx
new file mode 100644
index 0000000..d3be29a
--- /dev/null
+++ b/app/admin/(protected)/ekip/form.tsx
@@ -0,0 +1,71 @@
+import { Save } from "lucide-react";
+import {
+ Field,
+ FormActions,
+ FormShell,
+ GhostLink,
+ PageHeader,
+ PrimaryButton,
+ Textarea,
+} from "@/components/admin/form";
+import { saveTeamMember } from "@/lib/admin-actions";
+import type { TeamMemberRow } from "@/lib/types";
+
+export function TeamMemberForm({ row }: { row?: TeamMemberRow }) {
+ return (
+
+ );
+}
diff --git a/app/admin/(protected)/ekip/new/page.tsx b/app/admin/(protected)/ekip/new/page.tsx
new file mode 100644
index 0000000..051efd0
--- /dev/null
+++ b/app/admin/(protected)/ekip/new/page.tsx
@@ -0,0 +1,5 @@
+import { TeamMemberForm } from "../form";
+
+export default function NewTeamPage() {
+ return ;
+}
diff --git a/app/admin/(protected)/ekip/page.tsx b/app/admin/(protected)/ekip/page.tsx
new file mode 100644
index 0000000..6caf0f0
--- /dev/null
+++ b/app/admin/(protected)/ekip/page.tsx
@@ -0,0 +1,79 @@
+import Link from "next/link";
+import { Edit, Plus } from "lucide-react";
+import { PageHeader } from "@/components/admin/form";
+import { DeleteButton } from "@/components/admin/delete-button";
+import { listTeamMembers } from "@/lib/data";
+import { deleteTeamMember } from "@/lib/admin-actions";
+
+export default async function TeamAdminPage() {
+ const members = await listTeamMembers();
+ return (
+
+
+ Yeni üye
+
+ }
+ />
+
+ {members.length === 0 && (
+
+ Henüz ekip üyesi yok.
+
+ )}
+ {members.map((m) => (
+
+
+
+ {m.photo_url ? (
+ // eslint-disable-next-line @next/next/no-img-element
+
+ ) : (
+
+ {m.name.charAt(0)}
+
+ )}
+
+
{m.name}
+
+ {m.role ?? "—"} {m.order != null && `• Sıra: ${m.order}`}
+
+
+
+
+
+ Düzenle
+
+
+
+
+
+
+
+ {m.bio && (
+
+ {m.bio}
+
+ )}
+
+ ))}
+
+
+ );
+}
diff --git a/app/admin/(protected)/projeler/form.tsx b/app/admin/(protected)/projeler/form.tsx
index ab09afd..f97f3ed 100644
--- a/app/admin/(protected)/projeler/form.tsx
+++ b/app/admin/(protected)/projeler/form.tsx
@@ -114,6 +114,33 @@ export async function ProjectForm({ project }: { project?: ProjectRow }) {
}
help="Her satıra bir URL. Medya kütüphanesinden URL'leri kopyalayın."
/>
+
+ {
+ if (!project?.metrics) return "";
+ return project.metrics
+ .map((raw) => {
+ try {
+ const m = JSON.parse(raw) as {
+ value?: string;
+ label?: string;
+ };
+ return m.value && m.label ? `${m.value} | ${m.label}` : "";
+ } catch {
+ return "";
+ }
+ })
+ .filter(Boolean)
+ .join("\n");
+ })()}
+ placeholder={
+ "+150% | Organik trafik artışı\n2x | Dönüşüm oranı\n-40% | Sayfa yüklenme süresi"
+ }
+ help='Her satır "Değer | Etiket". Proje detay sayfasında büyük metric kartları olarak gösterilir.'
+ />
diff --git a/app/admin/(protected)/sektorler/[id]/edit/page.tsx b/app/admin/(protected)/sektorler/[id]/edit/page.tsx
new file mode 100644
index 0000000..404ac56
--- /dev/null
+++ b/app/admin/(protected)/sektorler/[id]/edit/page.tsx
@@ -0,0 +1,16 @@
+import { notFound } from "next/navigation";
+import { getRow } from "@/lib/data";
+import { TABLES } from "@/lib/appwrite-rest";
+import type { IndustryRow } from "@/lib/types";
+import { IndustryForm } from "../../form";
+
+export default async function EditIndustryPage({
+ params,
+}: {
+ params: Promise<{ id: string }>;
+}) {
+ const { id } = await params;
+ const row = await getRow
(TABLES.industries, id);
+ if (!row) notFound();
+ return ;
+}
diff --git a/app/admin/(protected)/sektorler/form.tsx b/app/admin/(protected)/sektorler/form.tsx
new file mode 100644
index 0000000..0a89ca1
--- /dev/null
+++ b/app/admin/(protected)/sektorler/form.tsx
@@ -0,0 +1,140 @@
+import { Save } from "lucide-react";
+import {
+ Checkbox,
+ Field,
+ FormActions,
+ FormShell,
+ GhostLink,
+ PageHeader,
+ PrimaryButton,
+ Textarea,
+} from "@/components/admin/form";
+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;
+ 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 (
+
+
+
+ {row && }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SEO
+
+
+
+
+
+
+
+
+
+
+
+ İptal
+
+ Kaydet
+
+
+
+
+
+ );
+}
diff --git a/app/admin/(protected)/sektorler/new/page.tsx b/app/admin/(protected)/sektorler/new/page.tsx
new file mode 100644
index 0000000..152ab36
--- /dev/null
+++ b/app/admin/(protected)/sektorler/new/page.tsx
@@ -0,0 +1,5 @@
+import { IndustryForm } from "../form";
+
+export default function NewIndustryPage() {
+ return ;
+}
diff --git a/app/admin/(protected)/sektorler/page.tsx b/app/admin/(protected)/sektorler/page.tsx
new file mode 100644
index 0000000..a1ee78d
--- /dev/null
+++ b/app/admin/(protected)/sektorler/page.tsx
@@ -0,0 +1,83 @@
+import Link from "next/link";
+import { Edit, ExternalLink, Plus } from "lucide-react";
+import { PageHeader } from "@/components/admin/form";
+import { DeleteButton } from "@/components/admin/delete-button";
+import { listIndustries } from "@/lib/data";
+import { deleteIndustry } from "@/lib/admin-actions";
+
+export default async function IndustriesAdminPage() {
+ const items = await listIndustries();
+ return (
+
+
+ Yeni sektör
+
+ }
+ />
+
+
+
+
+ Sıra
+ Başlık
+ Slug
+ Öne çıkan
+ İşlem
+
+
+
+ {items.length === 0 && (
+
+
+ Henüz sektör eklenmemiş.
+
+
+ )}
+ {items.map((it) => (
+
+ {it.order ?? 0}
+ {it.title}
+ {it.slug}
+
+ {it.featured && (
+
+ Öne çıkan
+
+ )}
+
+
+
+
+
+
+
+ Düzenle
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/app/admin/(protected)/site/page.tsx b/app/admin/(protected)/site/page.tsx
index cc07bfe..ab440f8 100644
--- a/app/admin/(protected)/site/page.tsx
+++ b/app/admin/(protected)/site/page.tsx
@@ -11,6 +11,7 @@ import {
import { getSiteSettings } from "@/lib/data";
import { saveSiteSettings } from "@/lib/admin-actions";
import type {
+ FaqItem,
ProcessStep,
StatItem,
TrustItem,
@@ -88,6 +89,20 @@ function processToText(items?: string[] | null): string {
return parsed.map((p) => `${p.title}\n${p.description}`).join("\n---\n");
}
+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;
+ 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");
+}
+
function Section({
title,
description,
@@ -457,6 +472,48 @@ export default async function SiteSettingsPage() {
help='Her blok "---" ile ayrılır. İlk satır başlık, sonrası açıklama.'
/>
+
+
+
+
diff --git a/components/admin/sidebar.tsx b/components/admin/sidebar.tsx
index 33be753..a3083c2 100644
--- a/components/admin/sidebar.tsx
+++ b/components/admin/sidebar.tsx
@@ -13,6 +13,8 @@ import {
Search,
Inbox,
Image as ImageIcon,
+ Users as UsersIcon,
+ Building2,
type LucideIcon,
} from "lucide-react";
@@ -24,6 +26,8 @@ const items: Item[] = [
{ href: "/admin/blog", label: "Blog", icon: Newspaper },
{ href: "/admin/hizmetler", label: "Hizmetler", icon: Layers },
{ href: "/admin/projeler", label: "Projeler", icon: Briefcase },
+ { href: "/admin/sektorler", label: "Sektörler", icon: Building2 },
+ { href: "/admin/ekip", label: "Ekip", icon: UsersIcon },
{ href: "/admin/referanslar", label: "Referanslar", icon: MessageSquareQuote },
{ href: "/admin/seo", label: "SEO", icon: Search },
{ href: "/admin/iletisim", label: "Mesajlar", icon: Inbox },
diff --git a/components/audit-form.tsx b/components/audit-form.tsx
new file mode 100644
index 0000000..8832094
--- /dev/null
+++ b/components/audit-form.tsx
@@ -0,0 +1,115 @@
+"use client";
+
+import { useActionState } from "react";
+import { submitContact, type ContactFormState } from "@/app/actions";
+import {
+ Search,
+ ArrowRight,
+ CheckCircle2,
+ AlertCircle,
+ Loader2,
+} from "lucide-react";
+
+const initial: ContactFormState = { ok: false, message: "" };
+
+export function AuditForm() {
+ const [state, action, pending] = useActionState(submitContact, initial);
+
+ return (
+
+
+
+
+
+
+ Web sitenizin adresi *
+
+
+
+
+
+
+
+
+ Telefon (opsiyonel)
+
+
+
+
+
+ {pending ? (
+ <>
+
+ Talep gönderiliyor…
+ >
+ ) : (
+ <>
+
+ Ücretsiz analiz raporumu istiyorum
+
+ >
+ )}
+
+
+
+ Raporunuz 24 saat içinde e-posta adresinize gönderilir. Bilgileriniz gizli tutulur.
+
+
+ {state.message && (
+
+ {state.ok ? (
+
+ ) : (
+
+ )}
+
{state.message}
+
+ )}
+
+ );
+}
diff --git a/components/footer.tsx b/components/footer.tsx
index 4b83aa6..612ed87 100644
--- a/components/footer.tsx
+++ b/components/footer.tsx
@@ -113,6 +113,10 @@ export async function Footer() {
© {new Date().getFullYear()} {siteConfig.name}. Tüm hakları saklıdır.
+
+ Ücretsiz Site Analizi
+
+
•
Çerez Politikası
diff --git a/components/guarantee.tsx b/components/guarantee.tsx
new file mode 100644
index 0000000..a9b4419
--- /dev/null
+++ b/components/guarantee.tsx
@@ -0,0 +1,79 @@
+import { ShieldCheck, CheckCircle2 } from "lucide-react";
+import Link from "next/link";
+import { ArrowRight } from "lucide-react";
+import type { SiteSettingsRow } from "@/lib/types";
+
+const DEFAULT_ITEMS = [
+ "İlk tasarım taslağı ücretsiz",
+ "Ön ödeme garanti altında — müşteri devam etmek istemezse iade",
+ "1 yıl ücretsiz teknik destek",
+ "Kaynak kodlar size aittir, bağımlılık yok",
+];
+
+export function Guarantee({
+ settings,
+}: {
+ settings?: SiteSettingsRow | null;
+}) {
+ const title =
+ settings?.guarantee_title ??
+ "İlk taslak ücretsiz, memnun değilseniz devam etmiyoruz";
+ const description =
+ settings?.guarantee_description ??
+ "Görüşme + ihtiyaç analizinin ardından ilk tasarım taslağı ücretsizdir. Beğenmezseniz devam etmek zorunda değilsiniz. İşimizden eminiz, ama karar sizin.";
+ const items =
+ settings?.guarantee_items && settings.guarantee_items.length > 0
+ ? settings.guarantee_items
+ : DEFAULT_ITEMS;
+
+ return (
+
+
+
+
+
+
+
+ Risk almazsınız
+
+
+ {title}
+
+
+ {description}
+
+
+
+ {items.map((it, i) => (
+
+
+ {it}
+
+ ))}
+
+
+
+ Ücretsiz keşif görüşmesi
+
+
+
+
+
+ );
+}
diff --git a/components/homepage-faq.tsx b/components/homepage-faq.tsx
new file mode 100644
index 0000000..b280235
--- /dev/null
+++ b/components/homepage-faq.tsx
@@ -0,0 +1,51 @@
+import Link from "next/link";
+import { ArrowRight } from "lucide-react";
+import { FaqList } from "@/components/faq-list";
+import { SectionTitle } from "@/components/section-title";
+import type { FaqItem, SiteSettingsRow } from "@/lib/types";
+
+function parseFaq(items?: string[] | null): FaqItem[] {
+ if (!items) return [];
+ const out: FaqItem[] = [];
+ for (const raw of items) {
+ try {
+ const obj = JSON.parse(raw) as Partial
;
+ if (obj.q && obj.a) out.push({ q: obj.q, a: obj.a });
+ } catch {
+ /* ignore */
+ }
+ }
+ return out;
+}
+
+export function HomepageFaq({
+ settings,
+}: {
+ settings?: SiteSettingsRow | null;
+}) {
+ const items = parseFaq(settings?.homepage_faq);
+ if (items.length === 0) return null;
+
+ return (
+
+
+
+
+
+ Sorunuz mu var?
+
+
+
+
+
+
+ );
+}
diff --git a/components/team-grid.tsx b/components/team-grid.tsx
new file mode 100644
index 0000000..fc9f0bd
--- /dev/null
+++ b/components/team-grid.tsx
@@ -0,0 +1,57 @@
+import Image from "next/image";
+import { LinkedinIcon } from "@/components/social-icons";
+import type { TeamMemberRow } from "@/lib/types";
+
+export function TeamGrid({ members }: { members: TeamMemberRow[] }) {
+ if (members.length === 0) return null;
+ return (
+
+ {members.map((m) => (
+
+
+ {m.photo_url ? (
+
+ ) : (
+
+ {m.name.charAt(0)}
+
+ )}
+
+
+
+ {m.name}
+
+ {m.role && (
+
{m.role}
+ )}
+ {m.bio && (
+
+ {m.bio}
+
+ )}
+ {m.linkedin_url && (
+
+
+ LinkedIn'de bağlan
+
+ )}
+
+
+ ))}
+
+ );
+}
diff --git a/lib/admin-actions.ts b/lib/admin-actions.ts
index b47c570..25f626f 100644
--- a/lib/admin-actions.ts
+++ b/lib/admin-actions.ts
@@ -180,6 +180,17 @@ export async function deleteService(formData: FormData) {
// ─── Projects ────────────────────────────────────────────────────
+function parseMetricsInput(raw: string): string[] {
+ return raw
+ .split("\n")
+ .map((line) => {
+ const [value, label] = line.split("|").map((s) => s.trim());
+ if (!value || !label) return null;
+ return JSON.stringify({ value, label });
+ })
+ .filter((x): x is string => x !== null);
+}
+
export async function saveProject(formData: FormData) {
const secret = await requireSessionSecret();
const id = str(formData.get("id"));
@@ -212,6 +223,10 @@ export async function saveProject(formData: FormData) {
industry: str(formData.get("industry")),
duration: str(formData.get("duration")),
service_slug: str(formData.get("service_slug")),
+ metrics: (() => {
+ const m = parseMetricsInput(String(formData.get("metrics") ?? ""));
+ return m.length > 0 ? m : null;
+ })(),
};
if (id) {
@@ -497,6 +512,97 @@ export async function deleteSeoPage(formData: FormData) {
revalidatePath("/admin/seo");
}
+// ─── Team Members ────────────────────────────────────────────────
+
+export async function saveTeamMember(formData: FormData) {
+ const secret = await requireSessionSecret();
+ const id = str(formData.get("id"));
+ const name = str(formData.get("name"));
+ if (!name) throw new Error("İsim zorunlu");
+
+ const data = {
+ name,
+ role: str(formData.get("role")),
+ bio: str(formData.get("bio")),
+ photo_url: str(formData.get("photo_url")),
+ linkedin_url: str(formData.get("linkedin_url")),
+ order: num(formData.get("order")) ?? 0,
+ };
+
+ if (id) {
+ await tablesDB.updateRow(DATABASE_ID, TABLES.teamMembers, id, data, secret);
+ } else {
+ await tablesDB.createRow(
+ DATABASE_ID,
+ TABLES.teamMembers,
+ ID.unique(),
+ data,
+ secret,
+ );
+ }
+ revalidatePath("/admin/ekip");
+ revalidatePath("/hakkimizda");
+}
+
+export async function deleteTeamMember(formData: FormData) {
+ const secret = await requireSessionSecret();
+ const id = String(formData.get("id"));
+ await tablesDB.deleteRow(DATABASE_ID, TABLES.teamMembers, id, secret);
+ revalidatePath("/admin/ekip");
+ revalidatePath("/hakkimizda");
+}
+
+// ─── Industries ──────────────────────────────────────────────────
+
+export async function saveIndustry(formData: FormData) {
+ const secret = await requireSessionSecret();
+ const id = str(formData.get("id"));
+ const title = str(formData.get("title"));
+ if (!title) throw new Error("Başlık zorunlu");
+ const slug = str(formData.get("slug")) || slugify(title);
+
+ const faqRaw = String(formData.get("faq") ?? "");
+ const faq = faqRaw
+ .split("\n---\n")
+ .map((block) => {
+ const lines = block.trim().split("\n");
+ const q = lines[0]?.trim();
+ const a = lines.slice(1).join("\n").trim();
+ if (!q || !a) return null;
+ return JSON.stringify({ q, a });
+ })
+ .filter((x): x is string => x !== null);
+
+ const data = {
+ slug,
+ title,
+ subtitle: str(formData.get("subtitle")),
+ content: str(formData.get("content")),
+ hero_image: str(formData.get("hero_image")),
+ features: strArr(formData.get("features")),
+ faq: faq.length > 0 ? faq : null,
+ seo_title: str(formData.get("seo_title")),
+ seo_description: str(formData.get("seo_description")),
+ featured: bool(formData.get("featured")),
+ order: num(formData.get("order")) ?? 0,
+ };
+
+ if (id) {
+ await tablesDB.updateRow(DATABASE_ID, TABLES.industries, id, data, secret);
+ } else {
+ await tablesDB.createRow(DATABASE_ID, TABLES.industries, slug, data, secret);
+ }
+ revalidatePath("/admin/sektorler");
+ revalidatePath(`/sektor/${slug}`);
+}
+
+export async function deleteIndustry(formData: FormData) {
+ const secret = await requireSessionSecret();
+ const id = String(formData.get("id"));
+ await tablesDB.deleteRow(DATABASE_ID, TABLES.industries, id, secret);
+ revalidatePath("/admin/sektorler");
+}
+
// ─── Contact ─────────────────────────────────────────────────────
export async function updateMessageStatus(formData: FormData) {
diff --git a/lib/appwrite-rest.ts b/lib/appwrite-rest.ts
index 1aec319..4f75a39 100644
--- a/lib/appwrite-rest.ts
+++ b/lib/appwrite-rest.ts
@@ -22,6 +22,8 @@ export const TABLES = {
seoPages: "seo_pages",
seoSettings: "seo_settings",
siteSettings: "site_settings",
+ teamMembers: "team_members",
+ industries: "industries",
} as const;
export class AppwriteError extends Error {
diff --git a/lib/data.ts b/lib/data.ts
index aa57240..0a9b133 100644
--- a/lib/data.ts
+++ b/lib/data.ts
@@ -4,11 +4,13 @@ import { getSessionSecret } from "@/lib/auth";
import type {
BlogPostRow,
ContactMessageRow,
+ IndustryRow,
ProjectRow,
ServiceRow,
SeoPageRow,
SeoSettingsRow,
SiteSettingsRow,
+ TeamMemberRow,
TestimonialRow,
} from "@/lib/types";
@@ -119,6 +121,27 @@ export async function listSeoPages() {
]);
}
+export async function listTeamMembers() {
+ return safeList(TABLES.teamMembers, [
+ Q.orderAsc("order"),
+ Q.limit(50),
+ ]);
+}
+
+export async function listIndustries(opts?: { featured?: boolean }) {
+ const q = [Q.orderAsc("order"), Q.limit(100)];
+ if (opts?.featured) q.unshift(Q.equal("featured", true));
+ return safeList(TABLES.industries, q);
+}
+
+export async function getIndustryBySlug(slug: string): Promise {
+ const res = await safeList(TABLES.industries, [
+ Q.equal("slug", slug),
+ Q.limit(1),
+ ]);
+ return res[0] ?? null;
+}
+
export async function getSiteSettings(): Promise {
try {
return await tablesDB.getRow(
diff --git a/lib/types.ts b/lib/types.ts
index d3f75b1..880a93e 100644
--- a/lib/types.ts
+++ b/lib/types.ts
@@ -36,6 +36,7 @@ export interface ProjectRow extends AwRow {
industry?: string | null;
duration?: string | null;
service_slug?: string | null;
+ metrics?: string[] | null; // JSON {"value":"+150%","label":"Trafik artışı"}
}
export interface BlogPostRow extends AwRow {
@@ -142,6 +143,39 @@ export interface SiteSettingsRow extends AwRow {
google_review_url?: string | null;
google_rating?: number | null;
google_review_count?: number | null;
+
+ homepage_faq?: string[] | null; // JSON {"q","a"}
+ guarantee_title?: string | null;
+ guarantee_description?: string | null;
+ guarantee_items?: string[] | null;
+}
+
+export interface TeamMemberRow extends AwRow {
+ name: string;
+ role?: string | null;
+ bio?: string | null;
+ photo_url?: string | null;
+ linkedin_url?: string | null;
+ order?: number | null;
+}
+
+export interface IndustryRow extends AwRow {
+ slug: string;
+ title: string;
+ subtitle?: string | null;
+ content?: string | null;
+ hero_image?: string | null;
+ features?: string[] | null;
+ faq?: string[] | null;
+ seo_title?: string | null;
+ seo_description?: string | null;
+ featured?: boolean | null;
+ order?: number | null;
+}
+
+export interface ProjectMetric {
+ value: string;
+ label: string;
}
export interface TrustItem {