feat: add multi-user admin panel and featured partners toggle on home page

This commit is contained in:
AyrisAI
2026-05-17 13:48:05 +03:00
parent 36e98a3883
commit 0504f12f5b
29 changed files with 1110 additions and 182 deletions

View File

@@ -40,8 +40,8 @@ export default function Capabilities() {
<section className="py-16 md:py-24 px-6 md:px-16 lg:px-24">
<div className="max-w-7xl mx-auto flex flex-col lg:flex-row gap-8 md:gap-16 items-start">
{/* Left Content */}
<div className="lg:w-1/3">
<h2 className="text-4xl md:text-5xl font-bold mb-6 text-white italic tracking-tighter uppercase">Yeteneklerimiz</h2>
<div className="lg:w-1/3 w-full">
<h2 className="text-3xl md:text-5xl font-bold mb-6 text-black italic tracking-tighter uppercase leading-tight">Yeteneklerimiz</h2>
<p className="text-white/60 text-lg mb-8 leading-relaxed">
Fikir aşamasından final renk düzenlemesine kadar, görsel üretim sürecinin her adımını takıntılı derecede yüksek standartlarla yönetiyoruz.
</p>

View File

@@ -3,6 +3,7 @@
import { usePathname } from "next/navigation";
import { motion, AnimatePresence } from "framer-motion";
import Navbar from "./Navbar";
import Footer from "./Footer";
export default function ClientLayout({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
@@ -22,6 +23,7 @@ export default function ClientLayout({ children }: { children: React.ReactNode }
{children}
</motion.div>
</AnimatePresence>
{!isAdmin && <Footer />}
</>
);
}

View File

@@ -13,9 +13,9 @@ export default function Hero() {
initial={{ y: 40, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 1, ease: [0.16, 1, 0.3, 1] }}
className="editorial-headline text-4xl md:text-6xl lg:text-[5.5rem] text-black uppercase"
className="editorial-headline text-[2.5rem] sm:text-5xl md:text-6xl lg:text-[5.5rem] text-black uppercase leading-[1.1] break-words"
>
Dijital Varlık ve Görsel<br />
Dijital Varlık <span className="hidden sm:inline">ve Görsel</span><br className="hidden sm:block" />
Hikaye <span className="text-primary">Mimarlığı.</span>
</motion.h1>

View File

@@ -1,84 +1,155 @@
"use client";
import { useState, useEffect } from "react";
import Link from "next/link";
import DynamicLogo from "./DynamicLogo";
import { motion } from "framer-motion";
import { motion, AnimatePresence } from "framer-motion";
import { Menu, X } from "lucide-react";
const NAV_LINKS = [
{ label: "Ana Sayfa", href: "/" },
{ label: "Çalışmalar", href: "/works" },
{ label: "Hizmetler", href: "/services" },
{ label: "Partnerler", href: "/partners" },
{ label: "Hakkımızda", href: "/about" },
{ label: "İletişim", href: "/contact" },
];
export default function Navbar() {
const [isOpen, setIsOpen] = useState(false);
// Body scroll lock
useEffect(() => {
if (isOpen) {
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "unset";
}
return () => {
document.body.style.overflow = "unset";
};
}, [isOpen]);
return (
<motion.nav
initial={{ y: -100 }}
animate={{ y: 0 }}
transition={{ duration: 1, ease: [0.16, 1, 0.3, 1] }}
className="fixed top-0 left-0 w-full z-50 bg-[#f5f5f0]/90 backdrop-blur-md border-b border-black/10"
>
<div className="flex items-center justify-between px-6 md:px-12 py-5">
{/* Logo */}
<Link href="/" className="flex items-center gap-3 focus-visible:outline-2 focus-visible:outline-primary focus-visible:outline-offset-4 rounded-sm" aria-label="Muğla Dijital - Ana Sayfaya Dön">
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.5 }}
className="relative w-36 h-10"
<>
<motion.nav
initial={{ y: -100 }}
animate={{ y: 0 }}
transition={{ duration: 1, ease: [0.16, 1, 0.3, 1] }}
className="fixed top-0 left-0 w-full z-50 bg-[#f5f5f0]/90 backdrop-blur-md border-b border-black/10"
>
<div className=" flex items-center justify-between py-2 md:py-5">
{/* Logo */}
<Link
href="/"
className="flex items-center gap-3 focus-visible:outline-2 focus-visible:outline-primary focus-visible:outline-offset-4 rounded-sm z-50"
aria-label="Muğla Dijital - Ana Sayfaya Dön"
onClick={() => setIsOpen(false)}
>
<DynamicLogo
src="/logo.png"
color="black"
width="100%"
height="100%"
size="contain"
/>
</motion.div>
</Link>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.5 }}
className="relative md:w-36 w-18 h-7 md:h-10"
>
<DynamicLogo
src="/logo.png"
color="black"
width="100%"
height="100%"
size="contain"
/>
</motion.div>
</Link>
{/* Center Nav */}
<div className="hidden lg:flex items-center">
{[
{ label: "Çalışmalar", href: "/works" },
{ label: "Hizmetler", href: "/services" },
{ label: "Partnerler", href: "/partners" },
{ label: "Hakkımızda", href: "/about" },
{ label: "İletişim", href: "/contact" },
].map((item, idx) => (
<div key={item.label} className="flex items-center">
{/* Diagonal separator */}
<motion.div
initial={{ opacity: 0, scaleY: 0 }}
animate={{ opacity: 1, scaleY: 1 }}
transition={{ delay: 0.2 + idx * 0.1 }}
className="w-16 h-10 relative mx-1"
>
<div className="absolute inset-0 flex items-center justify-center">
<div className="w-px h-14 bg-black/10 rotate-[-25deg]" />
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 + idx * 0.1 }}
>
<Link
href={item.href}
className="text-[11px] tracking-[0.15em] uppercase text-black/70 hover:text-black transition-colors nav-link-hover px-3 py-2 focus-visible:outline-2 focus-visible:outline-primary focus-visible:outline-offset-4 rounded-sm"
{/* Center Nav - Desktop */}
<div className="hidden lg:flex items-center">
{NAV_LINKS.map((item, idx) => (
<div key={item.label} className="flex items-center">
{/* Diagonal separator */}
<motion.div
initial={{ opacity: 0, scaleY: 0 }}
animate={{ opacity: 1, scaleY: 1 }}
transition={{ delay: 0.2 + idx * 0.1 }}
className="w-16 h-10 relative mx-1"
>
{item.label}
</Link>
</motion.div>
</div>
))}
</div>
<div className="absolute inset-0 flex items-center justify-center">
<div className="w-px h-14 bg-black/10 rotate-[-25deg]" />
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 + idx * 0.1 }}
>
<Link
href={item.href}
className="text-[11px] tracking-[0.15em] uppercase text-black/70 hover:text-black transition-colors nav-link-hover px-3 py-2 focus-visible:outline-2 focus-visible:outline-primary focus-visible:outline-offset-4 rounded-sm"
>
{item.label}
</Link>
</motion.div>
</div>
))}
</div>
{/* Right - Brand name */}
<motion.span
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1 }}
className="text-[13px] font-bold tracking-[0.1em] uppercase text-black"
aria-hidden="true"
>
Muğla Dijital
</motion.span>
</div>
</motion.nav>
{/* Right - Mobile Toggle & Brand */}
<div className="flex items-center gap-0 md:gap-6">
<motion.span
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1 }}
className="hidden md:block text-[13px] font-bold tracking-[0.1em] uppercase text-black"
aria-hidden="true"
>
Muğla Dijital
</motion.span>
<button
onClick={() => setIsOpen(!isOpen)}
className="lg:hidden p-2 flex items-center justify-center text-black/70 hover:text-black transition-colors z-50"
aria-label={isOpen ? "Menüyü Kapat" : "Menüyü Aç"}
>
{isOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
</button>
</div>
</div>
</motion.nav>
{/* Mobile Menu Overlay */}
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0, x: "100%" }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: "100%" }}
transition={{ duration: 0.5, ease: [0.16, 1, 0.3, 1] }}
className="fixed inset-0 z-40 bg-[#f5f5f0] flex flex-col justify-center px-8 md:px-16"
>
<div className="flex flex-col gap-8">
{NAV_LINKS.map((item, idx) => (
<motion.div
key={item.label}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 + idx * 0.05 }}
>
<Link
href={item.href}
onClick={() => setIsOpen(false)}
className="text-4xl md:text-6xl font-bold tracking-tighter text-black/90 hover:text-black transition-all hover:pl-4"
style={{ fontFamily: "var(--font-martian)" }}
>
{item.label}
</Link>
</motion.div>
))}
</div>
</motion.div>
)}
</AnimatePresence>
</>
);
}

View File

@@ -1,7 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { getPartners } from "@/app/actions";
import { getFeaturedPartners } from "@/app/actions";
import { motion } from "framer-motion";
import DynamicLogo from "./DynamicLogo";
import Link from "next/link";
@@ -11,7 +11,7 @@ export default function Partners() {
useEffect(() => {
async function fetchPartners() {
const data = await getPartners();
const data = await getFeaturedPartners();
setPartners(data || []);
}
fetchPartners();
@@ -34,7 +34,7 @@ export default function Partners() {
{/* Partners List Section */}
<div className="flex-1 flex items-center pl-4 md:pl-8 pr-4 md:pr-8 overflow-hidden">
<div className="flex flex-nowrap items-center justify-start w-full gap-4 md:gap-10">
<div className="flex flex-wrap md:flex-nowrap items-center justify-center md:justify-start w-full gap-6 md:gap-10 py-4">
{partners.slice(0, 5).map((partner, index) => {
const content = (
<motion.div

View File

@@ -12,7 +12,7 @@ import {
Camera,
Zap
} from "lucide-react";
import Footer from "@/components/Footer";
const processSteps = [
{ icon: Search, title: "Analiz", desc: "İşletmenizi ve hedef kitlenizi analiz ediyoruz." },
@@ -69,7 +69,7 @@ export default function ServicesClient({ services: initialServices, locations: i
</li>
))}
</ul>
{/* Subtle diagonal on hover */}
<div className="absolute top-0 right-0 w-16 h-16 opacity-0 group-hover:opacity-100 transition-opacity">
<div className="absolute top-0 right-0 w-px h-24 bg-black/5 rotate-[-45deg] origin-top-right" />
@@ -119,7 +119,7 @@ export default function ServicesClient({ services: initialServices, locations: i
<ul className="space-y-3">
{services.slice(0, 3).map((service) => (
<li key={service.id}>
<Link
<Link
href={`/services/${service.slug}/${loc.slug}`}
className="text-[11px] text-black/40 hover:text-primary transition-colors flex items-center justify-between group"
>
@@ -156,7 +156,6 @@ export default function ServicesClient({ services: initialServices, locations: i
</div>
</section>
<Footer />
</main>
);
}

View File

@@ -89,7 +89,7 @@ export default function ServicesGrid() {
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ delay: 0.6 }}
className="p-8 md:p-12 border-r border-black/10 relative cell-diagonal min-h-[200px] flex items-end"
className="p-8 md:p-12 border-r border-black/10 relative cell-diagonal overflow-hidden min-h-[200px] flex items-end"
>
<h3 className="editorial-headline text-2xl md:text-3xl text-black uppercase">
Profesyonel<br />
@@ -103,7 +103,7 @@ export default function ServicesGrid() {
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ delay: 0.8 }}
className="p-8 md:p-12 relative cell-diagonal min-h-[200px] flex items-end"
className="p-8 md:p-12 relative cell-diagonal overflow-hidden min-h-[200px] flex items-end"
>
<h3 className="editorial-headline text-2xl md:text-3xl text-black uppercase">
Dijital<br />

View File

@@ -2,8 +2,7 @@
import Image from "next/image";
import Link from "next/link";
import { ArrowRight, Share2 } from "lucide-react";
import Footer from "@/components/Footer";
export default function WorkDetailClient({ project, nextProject }: { project: any, nextProject: any }) {
const isInstagram = (url: string) => {
@@ -43,13 +42,13 @@ export default function WorkDetailClient({ project, nextProject }: { project: an
return (
<main className="min-h-screen bg-[#f5f5f0] text-black pt-24">
{/* 1. Editorial Header Section */}
<section className="pt-32 pb-20 px-6 md:px-12 border-b border-black/10">
<div className="max-w-7xl mx-auto">
{/* Top Line */}
<div className="w-full h-px bg-black/10 mb-20" />
<div className="grid grid-cols-1 lg:grid-cols-12 gap-16 items-start">
{/* Left: Client Name */}
<div className="lg:col-span-5">
@@ -94,7 +93,7 @@ export default function WorkDetailClient({ project, nextProject }: { project: an
<div className="max-w-4xl mx-auto space-y-32">
{/* Hero Image First */}
<div className="relative aspect-[16/10] w-full overflow-hidden border border-black/5 shadow-2xl bg-[#e2d1c1] p-1">
<div className="relative w-full h-full overflow-hidden">
<div className="relative w-full h-full overflow-hidden">
<Image
src={project.hero_image || "https://images.unsplash.com/photo-1550745165-9bc0b252726f"}
alt={project.title}
@@ -102,54 +101,54 @@ export default function WorkDetailClient({ project, nextProject }: { project: an
className="object-cover"
priority
/>
</div>
</div>
</div>
{/* Gallery / Instagram Feed */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-12 gap-y-24 mt-24">
{gallery.map((item: string, idx: number) => (
<div key={idx} className="space-y-8 animate-reveal">
<div className="flex items-center gap-4 text-[10px] font-bold text-black/20 tracking-widest uppercase">
<span>0{idx + 1}</span>
<div className="h-px flex-grow bg-black/5" />
<span>{isInstagram(item) ? "INSTAGRAM FEED" : "PRODUCTION VIEW"}</span>
</div>
<div className="flex items-center gap-4 text-[10px] font-bold text-black/20 tracking-widest uppercase">
<span>0{idx + 1}</span>
<div className="h-px flex-grow bg-black/5" />
<span>{isInstagram(item) ? "INSTAGRAM FEED" : "PRODUCTION VIEW"}</span>
</div>
{isInstagram(item) ? (
<div className="flex justify-center">
<div className="w-full max-w-[540px] border border-black/10 bg-white shadow-xl overflow-hidden rounded-xl">
<iframe
src={getInstaEmbedUrl(item)}
className="w-full h-[700px] border-0"
scrolling="no"
allowFullScreen
/>
{isInstagram(item) ? (
<div className="flex justify-center">
<div className="w-full max-w-[540px] border border-black/10 bg-white shadow-xl overflow-hidden rounded-xl">
<iframe
src={getInstaEmbedUrl(item)}
className="w-full h-[700px] border-0"
scrolling="no"
allowFullScreen
/>
</div>
</div>
</div>
) : (
<div className="relative aspect-[16/10] border border-black/5 bg-black/5 shadow-lg overflow-hidden group">
{isVideo(item) ? (
<iframe
src={item}
className="w-full h-full"
allowFullScreen
/>
) : (
<Image
src={item}
alt={`Gallery ${idx}`}
fill
className="object-cover transition-transform duration-1000 group-hover:scale-105"
/>
)}
</div>
)}
</div>
))}
) : (
<div className="relative aspect-[16/10] border border-black/5 bg-black/5 shadow-lg overflow-hidden group">
{isVideo(item) ? (
<iframe
src={item}
className="w-full h-full"
allowFullScreen
/>
) : (
<Image
src={item}
alt={`Gallery ${idx}`}
fill
className="object-cover transition-transform duration-1000 group-hover:scale-105"
/>
)}
</div>
)}
</div>
))}
</div>
</div>
</section>
{/* Minimal CTA Section */}
<section className="py-32 px-6 md:px-12 bg-[#f5f5f0]">
<div className="max-w-7xl mx-auto text-center">
@@ -157,9 +156,9 @@ export default function WorkDetailClient({ project, nextProject }: { project: an
<h2 className="editorial-headline text-3xl md:text-5xl lg:text-6xl text-black uppercase leading-tight mb-16 max-w-4xl mx-auto">
CESUR, İNSANCIL VE <br /> İZ BIRAKAN PROJELERİ <br /> <span className="text-primary">BİRLİKTE</span> ŞEKİLLENDİRELİM.
</h2>
<Link
href="/contact"
<Link
href="/contact"
className="inline-flex items-center justify-center w-32 h-32 md:w-40 md:h-40 rounded-full bg-primary text-white text-[10px] font-bold uppercase tracking-widest hover:scale-110 transition-all duration-500 shadow-xl shadow-primary/20 group"
>
<span className="group-hover:scale-110 transition-transform">İLETİŞİME GEÇ</span>
@@ -186,7 +185,6 @@ export default function WorkDetailClient({ project, nextProject }: { project: an
</section>
)}
<Footer />
</main>
);
}

View File

@@ -4,7 +4,7 @@ import Image from "next/image";
import { useState } from "react";
import Link from "next/link";
import { ArrowUpRight } from "lucide-react";
import Footer from "@/components/Footer";
interface ProjectCardProps {
hero_image: string;
@@ -97,7 +97,7 @@ export default function WorksClient({ projects: initialProjects }: { projects: a
break;
}
}
} catch (e) {}
} catch (e) { }
return Array.isArray(current) ? current : (typeof current === 'string' && current ? [current] : []);
})))];
@@ -113,7 +113,7 @@ export default function WorksClient({ projects: initialProjects }: { projects: a
break;
}
}
} catch (e) {}
} catch (e) { }
const cats = Array.isArray(current) ? current : (typeof current === 'string' && current ? [current] : []);
return cats.includes(activeCategory);
});
@@ -181,7 +181,6 @@ export default function WorksClient({ projects: initialProjects }: { projects: a
</div>
</section>
<Footer />
</main>
);
}