feat: anasayfa içeriği, iletişim ve sosyal medya yönetilebilir
Yeni site_settings tablosu (singleton, rowId='homepage'): - Hero: badge, title, subtitle, 2 CTA (label+href), stats (JSON array) - Section başlıkları: services/projects/testimonials eyebrow + title + description - Alt CTA: title, description, button label+href - Contact: phone (görünen + tel: ham), email, address, hafta içi/sonu saatleri - Social: linkedin/instagram/twitter/facebook URL'leri - Footer tagline Mevcut hardcoded değerler seed edildi. Admin: - /admin/site sayfası eklendi (sidebar'a 'Site Ayarları' linki) - Bölümlü tek form: Hero / Hizmetler / Projeler / Referanslar / Alt CTA / İletişim / Sosyal / Footer - Stats için 'değer | etiket' satır formatı Public bağlantılar: - Hero component artık settings prop alıyor (fallback değerlerle) - Anasayfa: tüm section başlıkları ve alt CTA settings'ten geliyor - Header: telefon settings'ten - Footer: tagline, adres, telefon, email, sosyal linkler settings'ten (sosyal link sadece dolu olanlar gösteriliyor) - Footer'da hizmetler artık /hizmetler/[slug] detay sayfalarına bağlı - İletişim sayfası: adres, telefon, email, saatler settings'ten 30 route üretiliyor.
This commit is contained in:
@@ -5,6 +5,7 @@ import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import {
|
||||
LayoutDashboard,
|
||||
Settings,
|
||||
Newspaper,
|
||||
Layers,
|
||||
Briefcase,
|
||||
@@ -19,6 +20,7 @@ type Item = { href: string; label: string; icon: LucideIcon };
|
||||
|
||||
const items: Item[] = [
|
||||
{ href: "/admin", label: "Pano", icon: LayoutDashboard },
|
||||
{ href: "/admin/site", label: "Site Ayarları", icon: Settings },
|
||||
{ href: "/admin/blog", label: "Blog", icon: Newspaper },
|
||||
{ href: "/admin/hizmetler", label: "Hizmetler", icon: Layers },
|
||||
{ href: "/admin/projeler", label: "Projeler", icon: Briefcase },
|
||||
|
||||
+56
-40
@@ -1,10 +1,33 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { siteConfig } from "@/lib/site-config";
|
||||
import { Mail, MapPin, Phone } from "lucide-react";
|
||||
import { LinkedinIcon, InstagramIcon, TwitterIcon } from "@/components/social-icons";
|
||||
import { getSiteSettings, listServices } from "@/lib/data";
|
||||
import { siteConfig } from "@/lib/site-config";
|
||||
|
||||
export async function Footer() {
|
||||
const [settings, services] = await Promise.all([
|
||||
getSiteSettings(),
|
||||
listServices(),
|
||||
]);
|
||||
|
||||
const tagline = settings?.footer_tagline ?? siteConfig.tagline;
|
||||
const address = settings?.contact_address ?? siteConfig.contact.address;
|
||||
const phone = settings?.contact_phone ?? siteConfig.contact.phone;
|
||||
const phoneRaw = settings?.contact_phone_raw ?? siteConfig.contact.phoneRaw;
|
||||
const email = settings?.contact_email ?? siteConfig.contact.email;
|
||||
|
||||
const socials = [
|
||||
{ url: settings?.social_linkedin, label: "LinkedIn", Icon: LinkedinIcon },
|
||||
{ url: settings?.social_instagram, label: "Instagram", Icon: InstagramIcon },
|
||||
{ url: settings?.social_twitter, label: "Twitter / X", Icon: TwitterIcon },
|
||||
].filter((s) => s.url);
|
||||
|
||||
const footerServices =
|
||||
services.length > 0
|
||||
? services.slice(0, 5)
|
||||
: siteConfig.fallbackServices.slice(0, 5);
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className="mt-24 border-t border-[var(--border)] bg-[var(--navy)] text-white">
|
||||
<div className="mx-auto grid max-w-7xl gap-10 px-6 py-14 md:grid-cols-4">
|
||||
@@ -19,9 +42,7 @@ export function Footer() {
|
||||
/>
|
||||
<span className="text-lg font-semibold">{siteConfig.name}</span>
|
||||
</div>
|
||||
<p className="mt-4 text-sm leading-relaxed text-white/70">
|
||||
{siteConfig.tagline}
|
||||
</p>
|
||||
<p className="mt-4 text-sm leading-relaxed text-white/70">{tagline}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -29,9 +50,9 @@ export function Footer() {
|
||||
Hizmetler
|
||||
</h3>
|
||||
<ul className="mt-4 space-y-2 text-sm text-white/70">
|
||||
{siteConfig.fallbackServices.slice(0, 5).map((s) => (
|
||||
{footerServices.map((s) => (
|
||||
<li key={s.slug}>
|
||||
<Link href={`/hizmetler#${s.slug}`} className="hover:text-white">
|
||||
<Link href={`/hizmetler/${s.slug}`} className="hover:text-white">
|
||||
{s.title}
|
||||
</Link>
|
||||
</li>
|
||||
@@ -46,56 +67,51 @@ export function Footer() {
|
||||
<ul className="mt-4 space-y-3 text-sm text-white/70">
|
||||
<li className="flex items-start gap-2">
|
||||
<MapPin className="mt-0.5 size-4 shrink-0" />
|
||||
<span>{siteConfig.contact.address}</span>
|
||||
<span>{address}</span>
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<Phone className="size-4 shrink-0" />
|
||||
<a href={`tel:${siteConfig.contact.phoneRaw}`} className="hover:text-white">
|
||||
{siteConfig.contact.phone}
|
||||
<a href={`tel:${phoneRaw}`} className="hover:text-white">
|
||||
{phone}
|
||||
</a>
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<Mail className="size-4 shrink-0" />
|
||||
<a href={`mailto:${siteConfig.contact.email}`} className="hover:text-white">
|
||||
{siteConfig.contact.email}
|
||||
<a href={`mailto:${email}`} className="hover:text-white">
|
||||
{email}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold uppercase tracking-wider text-white/80">
|
||||
Sosyal Medya
|
||||
</h3>
|
||||
<div className="mt-4 flex gap-3">
|
||||
<a
|
||||
href={siteConfig.social.linkedin}
|
||||
aria-label="LinkedIn"
|
||||
className="flex size-9 items-center justify-center rounded-full bg-white/10 transition hover:bg-white/20"
|
||||
>
|
||||
<LinkedinIcon className="size-4" />
|
||||
</a>
|
||||
<a
|
||||
href={siteConfig.social.instagram}
|
||||
aria-label="Instagram"
|
||||
className="flex size-9 items-center justify-center rounded-full bg-white/10 transition hover:bg-white/20"
|
||||
>
|
||||
<InstagramIcon className="size-4" />
|
||||
</a>
|
||||
<a
|
||||
href={siteConfig.social.twitter}
|
||||
aria-label="Twitter"
|
||||
className="flex size-9 items-center justify-center rounded-full bg-white/10 transition hover:bg-white/20"
|
||||
>
|
||||
<TwitterIcon className="size-4" />
|
||||
</a>
|
||||
{socials.length > 0 && (
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold uppercase tracking-wider text-white/80">
|
||||
Sosyal Medya
|
||||
</h3>
|
||||
<div className="mt-4 flex gap-3">
|
||||
{socials.map(({ url, label, Icon }) => (
|
||||
<a
|
||||
key={label}
|
||||
href={url as string}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label={label}
|
||||
className="flex size-9 items-center justify-center rounded-full bg-white/10 transition hover:bg-white/20"
|
||||
>
|
||||
<Icon className="size-4" />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="border-t border-white/10">
|
||||
<div className="mx-auto flex max-w-7xl flex-col items-center justify-between gap-2 px-6 py-5 text-xs text-white/60 md:flex-row">
|
||||
<p>© {new Date().getFullYear()} {siteConfig.name}. Tüm hakları saklıdır.</p>
|
||||
<p>
|
||||
© {new Date().getFullYear()} {siteConfig.name}. Tüm hakları saklıdır.
|
||||
</p>
|
||||
<p>Kocaeli, Türkiye</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { siteConfig } from "@/lib/site-config";
|
||||
import { Phone } from "lucide-react";
|
||||
import { getSiteSettings } from "@/lib/data";
|
||||
import { siteConfig } from "@/lib/site-config";
|
||||
|
||||
const nav = [
|
||||
{ href: "/", label: "Anasayfa" },
|
||||
@@ -12,7 +13,11 @@ const nav = [
|
||||
{ href: "/iletisim", label: "İletişim" },
|
||||
];
|
||||
|
||||
export function Header() {
|
||||
export async function Header() {
|
||||
const settings = await getSiteSettings();
|
||||
const phone = settings?.contact_phone ?? siteConfig.contact.phone;
|
||||
const phoneRaw = settings?.contact_phone_raw ?? siteConfig.contact.phoneRaw;
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-40 border-b border-[var(--border)] bg-white/90 backdrop-blur">
|
||||
<div className="mx-auto flex max-w-7xl items-center justify-between gap-6 px-6 py-3">
|
||||
@@ -36,11 +41,11 @@ export function Header() {
|
||||
</nav>
|
||||
|
||||
<a
|
||||
href={`tel:${siteConfig.contact.phoneRaw}`}
|
||||
href={`tel:${phoneRaw}`}
|
||||
className="hidden items-center gap-2 rounded-full bg-[var(--navy)] px-4 py-2 text-sm font-medium text-white shadow-sm transition hover:bg-[var(--navy-700)] sm:inline-flex"
|
||||
>
|
||||
<Phone className="size-4" />
|
||||
{siteConfig.contact.phone}
|
||||
{phone}
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
+55
-22
@@ -1,8 +1,43 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { ArrowRight, Sparkles } from "lucide-react";
|
||||
import type { SiteSettingsRow, StatItem } from "@/lib/types";
|
||||
|
||||
const DEFAULT_STATS: StatItem[] = [
|
||||
{ value: "50+", label: "Tamamlanan proje" },
|
||||
{ value: "10+", label: "Yıllık deneyim" },
|
||||
{ value: "24/7", label: "Teknik destek" },
|
||||
];
|
||||
|
||||
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 function Hero({ settings }: { settings?: SiteSettingsRow | null }) {
|
||||
const badge = settings?.hero_badge ?? "Kocaeli'nin teknoloji ajansı";
|
||||
const title =
|
||||
settings?.hero_title ?? "Fikirden ürüne tek bir partner ile yola çıkın";
|
||||
const subtitle =
|
||||
settings?.hero_subtitle ??
|
||||
"Web, mobil ve CRM çözümlerinde uçtan uca geliştirme. Markanıza özel tasarım, ölçeklenebilir altyapı ve uzun vadeli destek.";
|
||||
const primaryLabel =
|
||||
settings?.hero_cta_primary_label ?? "Proje görüşmesi başlat";
|
||||
const primaryHref = settings?.hero_cta_primary_href ?? "/iletisim";
|
||||
const secondaryLabel =
|
||||
settings?.hero_cta_secondary_label ?? "Hizmetlerimizi inceleyin";
|
||||
const secondaryHref = settings?.hero_cta_secondary_href ?? "/hizmetler";
|
||||
const stats = parseStats(settings?.hero_stats);
|
||||
|
||||
export function Hero() {
|
||||
return (
|
||||
<section className="relative overflow-hidden">
|
||||
<div className="absolute inset-0 hero-grid opacity-60" aria-hidden />
|
||||
@@ -12,47 +47,45 @@ export function Hero() {
|
||||
<div>
|
||||
<span className="inline-flex items-center gap-2 rounded-full border border-[var(--sky)]/30 bg-[var(--sky-50)] px-3 py-1 text-xs font-medium text-[var(--sky-600)]">
|
||||
<Sparkles className="size-3.5" />
|
||||
Kocaeli'nin teknoloji ajansı
|
||||
{badge}
|
||||
</span>
|
||||
|
||||
<h1 className="mt-6 text-4xl font-bold leading-tight tracking-tight text-[var(--navy)] sm:text-5xl md:text-6xl">
|
||||
Fikirden ürüne{" "}
|
||||
<span className="gradient-text">tek bir partner</span> ile yola çıkın
|
||||
{title}
|
||||
</h1>
|
||||
|
||||
<p className="mt-6 max-w-xl text-lg leading-relaxed text-[var(--muted)]">
|
||||
Web, mobil ve CRM çözümlerinde uçtan uca geliştirme. Markanıza
|
||||
özel tasarım, ölçeklenebilir altyapı ve uzun vadeli destek.
|
||||
{subtitle}
|
||||
</p>
|
||||
|
||||
<div className="mt-8 flex flex-col gap-3 sm:flex-row">
|
||||
<Link
|
||||
href="/iletisim"
|
||||
href={primaryHref}
|
||||
className="inline-flex items-center justify-center gap-2 rounded-full bg-[var(--navy)] px-6 py-3 text-sm font-medium text-white transition hover:bg-[var(--navy-700)]"
|
||||
>
|
||||
Proje görüşmesi başlat
|
||||
{primaryLabel}
|
||||
<ArrowRight className="size-4" />
|
||||
</Link>
|
||||
<Link
|
||||
href="/hizmetler"
|
||||
href={secondaryHref}
|
||||
className="inline-flex items-center justify-center gap-2 rounded-full border border-[var(--border)] bg-white px-6 py-3 text-sm font-medium text-[var(--navy)] transition hover:border-[var(--navy)]"
|
||||
>
|
||||
Hizmetlerimizi inceleyin
|
||||
{secondaryLabel}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<dl className="mt-12 grid max-w-md grid-cols-3 gap-6">
|
||||
{[
|
||||
{ value: "50+", label: "Tamamlanan proje" },
|
||||
{ value: "10+", label: "Yıllık deneyim" },
|
||||
{ value: "24/7", label: "Teknik destek" },
|
||||
].map((stat) => (
|
||||
<div key={stat.label}>
|
||||
<dt className="text-2xl font-bold text-[var(--navy)]">{stat.value}</dt>
|
||||
<dd className="mt-1 text-xs text-[var(--muted)]">{stat.label}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
{stats.length > 0 && (
|
||||
<dl className="mt-12 grid max-w-md grid-cols-3 gap-6">
|
||||
{stats.map((stat) => (
|
||||
<div key={stat.label}>
|
||||
<dt className="text-2xl font-bold text-[var(--navy)]">
|
||||
{stat.value}
|
||||
</dt>
|
||||
<dd className="mt-1 text-xs text-[var(--muted)]">{stat.label}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="relative flex justify-center">
|
||||
|
||||
Reference in New Issue
Block a user