feat: Hakkımızda sayfası yönetilebilir (site_settings + /admin/site)
Ö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)
This commit is contained in:
@@ -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<Metadata> {
|
||||
return buildMetadata("/hakkimizda", {
|
||||
@@ -14,31 +15,68 @@ export async function generateMetadata(): Promise<Metadata> {
|
||||
});
|
||||
}
|
||||
|
||||
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<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 = 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() {
|
||||
<div>
|
||||
<SectionTitle
|
||||
align="left"
|
||||
eyebrow="Hakkımızda"
|
||||
title="Kocaeli'den dünyaya dijital ürünler"
|
||||
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."
|
||||
eyebrow={eyebrow}
|
||||
title={title}
|
||||
description={description}
|
||||
/>
|
||||
|
||||
<ul className="mt-10 space-y-4">
|
||||
@@ -67,14 +105,25 @@ export default async function AboutPage() {
|
||||
|
||||
<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="flex aspect-square items-center justify-center p-12">
|
||||
<Image
|
||||
src="/logo.png"
|
||||
alt="Kovak Yazılım"
|
||||
width={400}
|
||||
height={400}
|
||||
className="size-full object-contain drop-shadow-xl"
|
||||
/>
|
||||
<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>
|
||||
@@ -84,9 +133,9 @@ export default async function AboutPage() {
|
||||
<section className="border-y border-[var(--border)] bg-gray-50 py-20">
|
||||
<div className="mx-auto max-w-7xl px-6">
|
||||
<SectionTitle
|
||||
eyebrow="Ekibimiz"
|
||||
title="Projenizde Kimlerle Çalışırsınız?"
|
||||
description="Sizin projenizde birebir çalışacak kurucular — teknik altyapı ve ürün geliştirmenin arkasındaki isimler."
|
||||
eyebrow={teamEyebrow}
|
||||
title={teamTitle}
|
||||
description={teamDescription}
|
||||
/>
|
||||
<div className="mt-14">
|
||||
<TeamGrid members={team} />
|
||||
@@ -95,20 +144,18 @@ export default async function AboutPage() {
|
||||
</section>
|
||||
)}
|
||||
|
||||
<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">
|
||||
{[
|
||||
{ value: "50+", label: "Tamamlanan proje" },
|
||||
{ value: "30+", label: "Mutlu müşteri" },
|
||||
{ value: "10+", label: "Yıllık deneyim" },
|
||||
].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>
|
||||
{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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user