feat: Çözümler bölümü + mobil menü; admin parser düzeltmeleri
- Çözümler: solutions tablosu, /cozumler liste + detay sayfası, anasayfa bölümü, tam admin CRUD (/admin/cozumler), header & footer linkleri, projelerde solution_slug ilişkisi, services-grid genelleştirildi - Mobil menü (hamburger drawer) eklendi — header artık < lg'de gezilebilir - Site ayarları parser: textarea CRLF (\r\n) normalizasyonu — neden biz, süreç adımları, değerler ve SSS blokları artık doğru parçalanıyor - homepage_faq + garanti (title/description/items) saveSiteSettings'e bağlandı (daha önce hiç kaydedilmiyordu)
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { Menu, X, ChevronDown, Phone, ArrowRight } from "lucide-react";
|
||||
|
||||
type NavService = { slug: string; title: string };
|
||||
|
||||
const LINKS = [
|
||||
{ href: "/", label: "Anasayfa" },
|
||||
{ href: "/cozumler", label: "Çözümler" },
|
||||
{ href: "/projeler", label: "Projeler" },
|
||||
{ href: "/blog", label: "Blog" },
|
||||
{ href: "/hakkimizda", label: "Hakkımızda" },
|
||||
{ href: "/iletisim", label: "İletişim" },
|
||||
];
|
||||
|
||||
export function MobileMenu({
|
||||
services,
|
||||
phone,
|
||||
phoneRaw,
|
||||
}: {
|
||||
services: NavService[];
|
||||
phone: string;
|
||||
phoneRaw: string;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [servicesOpen, setServicesOpen] = useState(false);
|
||||
const pathname = usePathname();
|
||||
|
||||
// Rota değişince menüyü kapat
|
||||
useEffect(() => {
|
||||
setOpen(false);
|
||||
}, [pathname]);
|
||||
|
||||
// Açıkken arka plan kaydırmasını kilitle
|
||||
useEffect(() => {
|
||||
document.body.style.overflow = open ? "hidden" : "";
|
||||
return () => {
|
||||
document.body.style.overflow = "";
|
||||
};
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<div className="lg:hidden">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen(true)}
|
||||
aria-label="Menüyü aç"
|
||||
aria-expanded={open}
|
||||
className="inline-flex size-9 items-center justify-center rounded-lg text-gray-700 transition-colors hover:bg-gray-100 hover:text-[var(--navy)]"
|
||||
>
|
||||
<Menu className="size-5" />
|
||||
</button>
|
||||
|
||||
{/* Overlay + drawer */}
|
||||
<div
|
||||
className={`fixed inset-0 z-[60] lg:hidden ${
|
||||
open ? "" : "pointer-events-none"
|
||||
}`}
|
||||
aria-hidden={!open}
|
||||
>
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
onClick={() => setOpen(false)}
|
||||
className={`absolute inset-0 bg-black/40 backdrop-blur-sm transition-opacity duration-300 ${
|
||||
open ? "opacity-100" : "opacity-0"
|
||||
}`}
|
||||
/>
|
||||
|
||||
{/* Panel */}
|
||||
<div
|
||||
className={`absolute right-0 top-0 flex h-full w-[86%] max-w-sm flex-col bg-white shadow-2xl transition-transform duration-300 ease-out ${
|
||||
open ? "translate-x-0" : "translate-x-full"
|
||||
}`}
|
||||
>
|
||||
{/* Üst bar */}
|
||||
<div className="flex h-14 items-center justify-between border-b border-gray-100 px-5">
|
||||
<span className="text-sm font-semibold tracking-tight text-[var(--navy)]">
|
||||
Menü
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen(false)}
|
||||
aria-label="Menüyü kapat"
|
||||
className="inline-flex size-9 items-center justify-center rounded-lg text-gray-600 transition-colors hover:bg-gray-100 hover:text-[var(--navy)]"
|
||||
>
|
||||
<X className="size-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Linkler */}
|
||||
<nav className="flex-1 overflow-y-auto px-3 py-4">
|
||||
<Link
|
||||
href="/"
|
||||
className="block rounded-xl px-4 py-3 text-base font-medium text-gray-800 transition-colors hover:bg-blue-50 hover:text-[var(--navy)]"
|
||||
>
|
||||
Anasayfa
|
||||
</Link>
|
||||
|
||||
{/* Hizmetler — açılır */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setServicesOpen((v) => !v)}
|
||||
aria-expanded={servicesOpen}
|
||||
className="flex w-full items-center justify-between rounded-xl px-4 py-3 text-base font-medium text-gray-800 transition-colors hover:bg-blue-50 hover:text-[var(--navy)]"
|
||||
>
|
||||
Hizmetler
|
||||
<ChevronDown
|
||||
className={`size-4 transition-transform duration-200 ${
|
||||
servicesOpen ? "rotate-180" : ""
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
{servicesOpen && (
|
||||
<div className="mb-1 ml-3 border-l border-gray-100 pl-3">
|
||||
{services.map((s) => (
|
||||
<Link
|
||||
key={s.slug}
|
||||
href={`/hizmetler/${s.slug}`}
|
||||
className="block rounded-lg px-3 py-2 text-sm text-gray-600 transition-colors hover:bg-blue-50 hover:text-[var(--navy)]"
|
||||
>
|
||||
{s.title}
|
||||
</Link>
|
||||
))}
|
||||
<Link
|
||||
href="/hizmetler"
|
||||
className="block rounded-lg px-3 py-2 text-sm font-semibold text-[var(--sky-600)] hover:text-[var(--navy)]"
|
||||
>
|
||||
Tüm hizmetleri gör →
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{LINKS.filter((l) => l.href !== "/").map((l) => (
|
||||
<Link
|
||||
key={l.href}
|
||||
href={l.href}
|
||||
className="block rounded-xl px-4 py-3 text-base font-medium text-gray-800 transition-colors hover:bg-blue-50 hover:text-[var(--navy)]"
|
||||
>
|
||||
{l.label}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* Alt CTA */}
|
||||
<div className="space-y-2 border-t border-gray-100 p-4">
|
||||
<a
|
||||
href={`tel:${phoneRaw}`}
|
||||
className="flex items-center justify-center gap-2 rounded-xl border border-gray-200 px-4 py-3 text-sm font-semibold text-[var(--navy)] transition-colors hover:border-[var(--navy)]"
|
||||
>
|
||||
<Phone className="size-4" />
|
||||
{phone}
|
||||
</a>
|
||||
<Link
|
||||
href="/iletisim"
|
||||
className="flex items-center justify-center gap-2 rounded-xl bg-[var(--navy)] px-4 py-3 text-sm font-semibold text-white transition-colors hover:bg-[var(--navy-700)]"
|
||||
>
|
||||
Ücretsiz Teklif
|
||||
<ArrowRight className="size-4" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user