From f49df9cbeb99dbcf7b110e81d232acd09de7e9a2 Mon Sep 17 00:00:00 2001 From: Ege Can Komur Date: Wed, 20 May 2026 20:50:30 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20Hakk=C4=B1m=C4=B1zda=20sayfas=C4=B1=20y?= =?UTF-8?q?=C3=B6netilebilir=20(site=5Fsettings=20+=20/admin/site)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Önce hard-coded olan tüm metinler artık /admin/site > 'Hakkımızda sayfası' bölümünden düzenlenebilir. site_settings'e 9 yeni alan eklendi: - about_eyebrow, about_title, about_description (üst hero) - about_values (string array JSON {title, description}) — 4 değer kartı - about_hero_image (opsiyonel, boşsa logo gösterilir) - about_team_eyebrow, about_team_title, about_team_description - about_stats (string array JSON {value, label}) — alt navy bant Mevcut WP değerleri default olarak seed edildi. Hakkımızda sayfası (app/(site)/hakkimizda/page.tsx) artık: - Tüm metinler settings'ten okunuyor (fallback default'lar var) - Hero image varsa logo yerine onu gösteriyor - Stats sıfırdan farklı sayıda olabilir (3 yerine 2/4) Admin form (/admin/site): - Yeni 'Hakkımızda sayfası' section - 4 alt-bölüm: Üst hero / Değerler / Ekip / Stats - MediaPicker ile hero image - Markdown benzeri textarea'lar (--- ayırıcı, | seperator) --- app/(site)/hakkimizda/page.tsx | 151 ++++++++++++++++++---------- app/admin/(protected)/site/page.tsx | 106 +++++++++++++++++++ lib/admin-actions.ts | 34 +++++++ lib/types.ts | 16 +++ 4 files changed, 255 insertions(+), 52 deletions(-) diff --git a/app/(site)/hakkimizda/page.tsx b/app/(site)/hakkimizda/page.tsx index 5a278ba..b4a2aa4 100644 --- a/app/(site)/hakkimizda/page.tsx +++ b/app/(site)/hakkimizda/page.tsx @@ -1,10 +1,11 @@ import type { Metadata } from "next"; import Image from "next/image"; -import { SectionTitle } from "@/components/section-title"; import { CheckCircle2 } from "lucide-react"; +import { SectionTitle } from "@/components/section-title"; import { TeamGrid } from "@/components/team-grid"; -import { listTeamMembers } from "@/lib/data"; +import { getSiteSettings, listTeamMembers } from "@/lib/data"; import { buildMetadata } from "@/lib/seo"; +import type { AboutValue, StatItem } from "@/lib/types"; export async function generateMetadata(): Promise { return buildMetadata("/hakkimizda", { @@ -14,31 +15,68 @@ export async function generateMetadata(): Promise { }); } -const values = [ - { - title: "Uçtan uca üretim", - description: - "Fikir aşamasından lansmana, lansman sonrası bakıma kadar tek bir ekip.", - }, - { - title: "Ölçülebilir sonuç", - description: - "Her projeyi performans, dönüşüm ve kullanıcı deneyimi metrikleriyle değerlendiriyoruz.", - }, - { - title: "Şeffaf süreç", - description: - "Her sprint demo ile başlar, her engel açıkça konuşulur. Sürprize yer yok.", - }, - { - title: "Uzun vadeli ortaklık", - description: - "Proje biter, iş büyür. Bakım ve geliştirme süreçlerinde yanınızdayız.", - }, +const DEFAULT_VALUES: AboutValue[] = [ + { title: "Uçtan uca üretim", description: "Fikir aşamasından lansmana, lansman sonrası bakıma kadar tek bir ekip." }, + { title: "Ölçülebilir sonuç", description: "Her projeyi performans, dönüşüm ve kullanıcı deneyimi metrikleriyle değerlendiriyoruz." }, + { title: "Şeffaf süreç", description: "Her sprint demo ile başlar, her engel açıkça konuşulur. Sürprize yer yok." }, + { title: "Uzun vadeli ortaklık", description: "Proje biter, iş büyür. Bakım ve geliştirme süreçlerinde yanınızdayız." }, ]; +const DEFAULT_STATS: StatItem[] = [ + { value: "50+", label: "Tamamlanan proje" }, + { value: "30+", label: "Mutlu müşteri" }, + { value: "10+", label: "Yıllık deneyim" }, +]; + +function parseValues(items?: string[] | null): AboutValue[] { + if (!items || items.length === 0) return DEFAULT_VALUES; + const out: AboutValue[] = []; + for (const raw of items) { + try { + const obj = JSON.parse(raw) as Partial; + if (obj.title && obj.description) out.push({ title: obj.title, description: obj.description }); + } catch { + /* ignore */ + } + } + return out.length > 0 ? out : DEFAULT_VALUES; +} + +function parseStats(items?: string[] | null): StatItem[] { + if (!items || items.length === 0) return DEFAULT_STATS; + const out: StatItem[] = []; + 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 { + /* ignore */ + } + } + return out.length > 0 ? out : DEFAULT_STATS; +} + export default async function AboutPage() { - const team = await listTeamMembers(); + const [team, settings] = await Promise.all([ + listTeamMembers(), + getSiteSettings(), + ]); + + const eyebrow = settings?.about_eyebrow ?? "Hakkımızda"; + const title = settings?.about_title ?? "Kocaeli'den dünyaya dijital ürünler"; + const description = + settings?.about_description ?? + "Kovak Yazılım, kurumsal markalardan girişimlere kadar geniş bir yelpazedeki müşterileri için web, mobil ve CRM çözümleri üretir. Hızlı, ölçeklenebilir ve estetik."; + const values = parseValues(settings?.about_values); + const heroImage = settings?.about_hero_image ?? null; + + const teamEyebrow = settings?.about_team_eyebrow ?? "Ekibimiz"; + const teamTitle = settings?.about_team_title ?? "Projenizde Kimlerle Çalışırsınız?"; + const teamDescription = + settings?.about_team_description ?? + "Sizin projenizde birebir çalışacak kurucular — teknik altyapı ve ürün geliştirmenin arkasındaki isimler."; + + const stats = parseStats(settings?.about_stats); return ( <> @@ -47,9 +85,9 @@ export default async function AboutPage() {
    @@ -67,14 +105,25 @@ export default async function AboutPage() {
    -
    - Kovak Yazılım +
    + {heroImage ? ( + {title} + ) : ( + Kovak Yazılım + )}
    @@ -84,9 +133,9 @@ export default async function AboutPage() {
    @@ -95,20 +144,18 @@ export default async function AboutPage() {
    )} -
    -
    - {[ - { value: "50+", label: "Tamamlanan proje" }, - { value: "30+", label: "Mutlu müşteri" }, - { value: "10+", label: "Yıllık deneyim" }, - ].map((s) => ( -
    -

    {s.value}

    -

    {s.label}

    -
    - ))} -
    -
    + {stats.length > 0 && ( +
    +
    + {stats.map((s) => ( +
    +

    {s.value}

    +

    {s.label}

    +
    + ))} +
    +
    + )} ); } diff --git a/app/admin/(protected)/site/page.tsx b/app/admin/(protected)/site/page.tsx index ba1fea4..add754e 100644 --- a/app/admin/(protected)/site/page.tsx +++ b/app/admin/(protected)/site/page.tsx @@ -12,6 +12,7 @@ import { getSiteSettings } from "@/lib/data"; import { saveSiteSettings } from "@/lib/admin-actions"; import { MediaPicker } from "@/components/admin/media-picker"; import type { + AboutValue, FaqItem, ProcessStep, StatItem, @@ -104,6 +105,21 @@ function faqToText(items?: string[] | null): string { return parsed.map((it) => `${it.q}\n${it.a}`).join("\n---\n"); } +function aboutValuesToText(items?: string[] | null): string { + if (!items) return ""; + const parsed: AboutValue[] = []; + for (const raw of items) { + try { + const obj = JSON.parse(raw) as Partial; + if (obj.title && obj.description) + parsed.push({ title: obj.title, description: obj.description }); + } catch { + /* ignore */ + } + } + return parsed.map((v) => `${v.title}\n${v.description}`).join("\n---\n"); +} + function Section({ title, description, @@ -371,6 +387,96 @@ export default async function SiteSettingsPage() { /> +
    +
    + + +
    +