f49df9cbeb
Ö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)
162 lines
5.9 KiB
TypeScript
162 lines
5.9 KiB
TypeScript
import type { Metadata } from "next";
|
||
import Image from "next/image";
|
||
import { CheckCircle2 } from "lucide-react";
|
||
import { SectionTitle } from "@/components/section-title";
|
||
import { TeamGrid } from "@/components/team-grid";
|
||
import { getSiteSettings, listTeamMembers } from "@/lib/data";
|
||
import { buildMetadata } from "@/lib/seo";
|
||
import type { AboutValue, StatItem } from "@/lib/types";
|
||
|
||
export async function generateMetadata(): Promise<Metadata> {
|
||
return buildMetadata("/hakkimizda", {
|
||
title: "Hakkımızda",
|
||
description:
|
||
"Kovak Yazılım, Kocaeli merkezli bir teknoloji ajansıdır. Web, mobil ve CRM çözümleri üretir.",
|
||
});
|
||
}
|
||
|
||
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<AboutValue>;
|
||
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<StatItem>;
|
||
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, 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 (
|
||
<>
|
||
<section className="mx-auto max-w-7xl px-6 py-20">
|
||
<div className="grid items-center gap-12 md:grid-cols-2">
|
||
<div>
|
||
<SectionTitle
|
||
align="left"
|
||
eyebrow={eyebrow}
|
||
title={title}
|
||
description={description}
|
||
/>
|
||
|
||
<ul className="mt-10 space-y-4">
|
||
{values.map((v) => (
|
||
<li key={v.title} className="flex gap-3">
|
||
<CheckCircle2 className="mt-1 size-5 shrink-0 text-[var(--sky-600)]" />
|
||
<div>
|
||
<p className="font-semibold text-[var(--navy)]">{v.title}</p>
|
||
<p className="text-sm text-[var(--muted)]">{v.description}</p>
|
||
</div>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
|
||
<div className="relative">
|
||
<div className="absolute inset-0 -z-10 rounded-3xl bg-gradient-to-br from-[var(--sky-50)] to-[var(--navy-50)]" />
|
||
<div className="relative flex aspect-square items-center justify-center overflow-hidden rounded-3xl p-12">
|
||
{heroImage ? (
|
||
<Image
|
||
src={heroImage}
|
||
alt={title}
|
||
fill
|
||
sizes="(min-width: 768px) 50vw, 100vw"
|
||
className="object-cover"
|
||
priority
|
||
/>
|
||
) : (
|
||
<Image
|
||
src="/logo.png"
|
||
alt="Kovak Yazılım"
|
||
width={400}
|
||
height={400}
|
||
className="size-full object-contain drop-shadow-xl"
|
||
/>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{team.length > 0 && (
|
||
<section className="border-y border-[var(--border)] bg-gray-50 py-20">
|
||
<div className="mx-auto max-w-7xl px-6">
|
||
<SectionTitle
|
||
eyebrow={teamEyebrow}
|
||
title={teamTitle}
|
||
description={teamDescription}
|
||
/>
|
||
<div className="mt-14">
|
||
<TeamGrid members={team} />
|
||
</div>
|
||
</div>
|
||
</section>
|
||
)}
|
||
|
||
{stats.length > 0 && (
|
||
<section className="bg-[var(--navy)] py-20 text-white">
|
||
<div className="mx-auto grid max-w-7xl gap-12 px-6 md:grid-cols-3">
|
||
{stats.map((s) => (
|
||
<div key={s.label} className="text-center">
|
||
<p className="text-5xl font-bold">{s.value}</p>
|
||
<p className="mt-2 text-sm text-white/70">{s.label}</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</section>
|
||
)}
|
||
</>
|
||
);
|
||
}
|