initial commit: project completion with proper gitignore

This commit is contained in:
AyrisAI
2026-05-16 00:43:22 +03:00
commit e708ba2156
84 changed files with 11035 additions and 0 deletions

266
app/about/page.tsx Normal file
View File

@@ -0,0 +1,266 @@
import Image from "next/image";
import Link from "next/link";
import {
ArrowRight,
Circle,
Camera,
Video,
Instagram,
TrendingUp
} from "lucide-react";
import Footer from "@/components/Footer";
import { Metadata } from "next";
export const metadata: Metadata = {
title: "Hakkımızda | Muğla Dijital Medya & Prodüksiyon Ajansı",
description: "Muğla Dijital Medya Ajansı'nın hikayesini, değerlerini ve vizyonunu keşfedin. Drone çekimi, video prodüksiyon, sosyal medya yönetimi ve reklam hizmetleri.",
alternates: {
canonical: "/about",
},
openGraph: {
title: "Hakkımızda | Muğla Dijital",
description: "Muğla'nın dijital çözüm ortağı.",
url: "https://mugladijitalmedya.com/about",
}
};
const timeline = [
{ year: "2020", title: "Ajans Kuruldu", desc: "Muğla'da küçük bir ekiple yola çıktık. Drone çekimleri ve sosyal medya yönetimi ile ilk projelerimizi tamamladık." },
{ year: "2021", title: "İlk Büyük Projeler", desc: "Bölgedeki oteller ve işletmeler için profesyonel tanıtım çekimleri gerçekleştirdik. Müşteri portföyümüz hızla büyüdü." },
{ year: "2023", title: "Dijital Genişleme", desc: "Reklam yönetimi, SEO ve web tasarım hizmetlerini de ekleyerek tam kapsamlı bir dijital ajansa dönüştük." },
{ year: "2025", title: "Sektörde Güçlü Konum", desc: "150+ mutlu müşteri, 500+ tamamlanmış proje ile Muğla ve çevresinin güvenilir dijital çözüm ortağı olduk." }
];
const highlights = [
{ number: "500+", label: "Tamamlanan Proje" },
{ number: "150+", label: "Mutlu Müşteri" },
{ number: "5+", label: "Yıllık Deneyim" },
{ number: "9", label: "Hizmet Alanı" }
];
export default function AboutPage() {
return (
<main className="min-h-screen bg-[#f5f5f0] text-black">
{/* Hero Section */}
<section className="pt-48 pb-24 px-6 md:px-12">
<div className="max-w-7xl mx-auto">
<div className="flex justify-between items-center mb-6">
<span className="text-[10px] tracking-[0.2em] uppercase text-black/40">Hakkımızda</span>
<span className="text-[10px] tracking-[0.1em] text-black/20 italic">Son Güncelleme: 15 Mayıs 2026</span>
</div>
<h1 className="editorial-headline text-4xl md:text-6xl lg:text-[5.5rem] text-black reveal opacity-0">
Muğla&apos;nın Dijital<br />
Hikaye <span className="text-primary">Mimarları.</span>
</h1>
</div>
</section>
{/* Stats Bar - Editorial Style */}
<section className="border-y border-black/10">
<div className="max-w-7xl mx-auto grid grid-cols-2 md:grid-cols-4">
{highlights.map((item, idx) => (
<div key={idx} className={`py-12 px-8 text-center ${idx < 3 ? 'md:border-r border-black/10' : ''} ${idx % 2 === 0 ? 'border-r md:border-r-0 md:border-r border-black/10' : ''}`}>
<div className="text-4xl font-black text-primary mb-2">{item.number}</div>
<div className="text-[10px] font-bold uppercase tracking-widest text-black/40">{item.label}</div>
</div>
))}
</div>
</section>
{/* Mission Section - Editorial Layout */}
<section className="py-24 px-6 md:px-12 border-b border-black/10">
<div className="max-w-7xl mx-auto grid grid-cols-1 lg:grid-cols-12 gap-16 items-center">
<div className="lg:col-span-7 space-y-10">
<span className="text-[10px] tracking-[0.2em] uppercase text-black/40 block">Misyonumuz</span>
<h2 className="editorial-headline text-3xl md:text-5xl text-black uppercase">
Markanızı Havadan ve<br /> Yerden <span className="text-primary">Kurguluyoruz.</span>
</h2>
<div className="space-y-6 max-w-2xl">
<p className="text-black/60 text-[14px] leading-relaxed">
<strong>Muğla Dijital Medya Ajansı</strong>, Muğla merkezli profesyonel bir dijital medya ve prodüksiyon ajansıdır. İşletmelerin dijital dünyada güçlü bir şekilde var olmasını sağlamak amacıyla; profesyonel drone çekimleri, kurumsal fotoğrafçılık ve video prodüksiyon hizmetleriyle markaların görsel kimliğini inşa ediyoruz.
</p>
<p className="text-black/60 text-[14px] leading-relaxed">
Stratejik sosyal medya yönetimi, Google ve Meta reklam kampanyaları, SEO optimizasyonu ve modern web tasarım çözümlerimizle markanızın dijital varlığını güçlendiriyoruz. Ayrıca düğün ve özel gün çekimleriyle en değerli anlarınızı profesyonel kurgularla ölümsüzleştiriyoruz.
</p>
</div>
<Link href="/services" className="button-primary group">
Hizmetlerimiz
<ArrowRight className="w-3 h-3 group-hover:translate-x-1 transition-transform" />
</Link>
</div>
<div className="lg:col-span-5 relative">
<div className="aspect-[4/5] relative overflow-hidden border border-black/10 grayscale hover:grayscale-0 transition-all duration-1000">
<Image src="https://images.unsplash.com/photo-1473968512647-3e447244af8f?q=80&w=800" alt="Muğla Dijital Profesyonel Drone Çekimi ve Havadan Görüntüleme Hizmetleri" fill className="object-cover" />
</div>
{/* Diagonal decoration */}
<div className="absolute -bottom-8 -right-8 w-32 h-32 border-r border-b border-black/10 hidden md:block" />
</div>
</div>
</section>
{/* Core Values - Editorial Bento */}
<section className="py-24 px-6 md:px-12 border-b border-black/10 bg-black/[0.02]">
<div className="max-w-7xl mx-auto">
<div className="mb-16">
<span className="text-[10px] tracking-[0.2em] uppercase text-black/40 block mb-4">Değerlerimiz</span>
<h2 className="editorial-headline text-3xl md:text-5xl text-black uppercase">Temel İlkelerimiz</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 border-t border-l border-black/10">
{[
{
title: "Profesyonel Prodüksiyon",
desc: "Drone çekimi ve prodüksiyon alanında en son teknolojiyle markanız için unutulmaz görsel içerikler üretiyoruz.",
icon: Camera
},
{
title: "Dijital Büyüme",
desc: "Sosyal medya yönetimi ve reklam stratejileri ile markanızın sürdürülebilir şekilde büyümesini sağlıyoruz.",
icon: TrendingUp
},
{
title: "Müşteri Odaklılık",
desc: "Her projeyi müşterimizin ihtiyaçlarına özel tasarlıyor, şeffaf iletişim ve sonuç odaklı çalışma prensibiyle ilerliyoruz.",
icon: Circle
}
].map((v, idx) => (
<div key={idx} className="p-10 border-r border-b border-black/10 relative group hover:bg-black/[0.01] transition-colors overflow-hidden">
<div className="w-10 h-10 border border-black/10 flex items-center justify-center text-primary mb-8">
<v.icon className="w-5 h-5" />
</div>
<h3 className="editorial-headline text-xl text-black mb-4 uppercase">{v.title}</h3>
<p className="text-[12px] text-black/40 leading-relaxed font-medium">
{v.desc}
</p>
{/* 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" />
</div>
</div>
))}
</div>
</div>
</section>
{/* FAQ Section - AI Extraction Optimized */}
<section className="py-24 px-6 md:px-12 border-b border-black/10">
<div className="max-w-7xl mx-auto">
<div className="mb-16">
<span className="text-[10px] tracking-[0.2em] uppercase text-black/40 block mb-4">Destek</span>
<h2 className="editorial-headline text-3xl md:text-5xl text-black uppercase">Sıkça Sorulan Sorular</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-16 gap-y-12">
{[
{
q: "Muğla Dijital Medya Ajansı nedir?",
a: "Muğla Dijital, Muğla merkezli profesyonel bir dijital medya ajansıdır. Drone çekimi, video prodüksiyon, sosyal medya yönetimi, SEO ve dijital reklam yönetimi (Google/Meta) alanlarında markalara yaratıcı ve sonuç odaklı çözümler sunuyoruz."
},
{
q: "Drone çekimi hizmetleriniz neleri kapsıyor?",
a: "Drone çekimi hizmetlerimiz; otel tanıtım filmleri, gayrimenkul çekimleri, arazi ve arsa havadan görüntüleme, etkinlik ve düğün çekimlerini kapsamaktadır. Tüm çekimler profesyonel lisanslı pilotlarımız ve 4K/5.4K çözünürlüklü ekipmanlarımızla gerçekleştirilir."
},
{
q: "Sosyal medya yönetimi süreciniz nasıl işliyor?",
a: "Sosyal medya yönetimi sürecimiz; marka analizi, içerik planlama, profesyonel çekimler, grafik tasarım, paylaşım yönetimi ve aylık performans raporlamasını içerir. Instagram, Facebook, LinkedIn ve TikTok platformlarında markanızı büyütecek stratejiler uyguluyoruz."
},
{
q: "Muğla dışında hizmet veriyor musunuz?",
a: "Evet, merkezimiz Muğla olmakla birlikte başta Aydın, Denizli, Antalya ve İzmir olmak üzere tüm Ege bölgesi ve Türkiye genelinde projeler gerçekleştiriyoruz."
},
{
q: "Dijital reklam yönetimi (Google ve Meta) için bütçe nasıl belirlenir?",
a: "Reklam bütçesi; sektörünüze, hedef kitlenize ve hedeflerinize göre analiz edilerek belirlenir. Minimum bütçe ile maksimum dönüşüm alabilmeniz için stratejik planlama yapıyor, kampanyalarınızı günlük olarak optimize ediyoruz."
}
].map((faq, idx) => (
<div key={idx} className="space-y-4">
<h3 className="text-[14px] font-bold uppercase tracking-tight text-black">{faq.q}</h3>
<p className="text-[12px] text-black/50 leading-relaxed max-w-lg">{faq.a}</p>
</div>
))}
</div>
</div>
{/* FAQ Schema */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "Muğla Dijital Medya Ajansı nedir?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Muğla Dijital, Muğla merkezli profesyonel bir dijital medya ajansıdır. Drone çekimi, video prodüksiyon, sosyal medya yönetimi, SEO ve dijital reklam yönetimi (Google/Meta) alanlarında markalara yaratıcı ve sonuç odaklı çözümler sunuyoruz."
}
},
{
"@type": "Question",
"name": "Drone çekimi hizmetleriniz neleri kapsıyor?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Drone çekimi hizmetlerimiz; otel tanıtım filmleri, gayrimenkul çekimleri, arazi ve arsa havadan görüntüleme, etkinlik ve düğün çekimlerini kapsamaktadır."
}
},
{
"@type": "Question",
"name": "Muğla dışında hizmet veriyor musunuz?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Evet, merkezimiz Muğla olmakla birlikte başta Aydın, Denizli, Antalya ve İzmir olmak üzere tüm Ege bölgesi ve Türkiye genelinde projeler gerçekleştiriyoruz."
}
}
]
})
}}
/>
{/* AboutPage Schema */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "AboutPage",
"mainEntity": {
"@type": "ProfessionalService",
"name": "Muğla Dijital Medya Ajansı",
"description": "Muğla'nın öncü dijital medya ve prodüksiyon ajansı.",
"areaServed": "Ege Bölgesi",
"serviceType": ["Drone Çekimi", "Sosyal Medya Yönetimi", "Reklam Yönetimi"]
}
})
}}
/>
</section>
{/* CTA Section */}
<section className="py-24 px-6 text-center">
<div className="max-w-4xl mx-auto space-y-12">
<h2 className="editorial-headline text-4xl md:text-6xl text-black uppercase">
Projenizi Hayata <br /> <span className="text-primary">Geçirelim.</span>
</h2>
<p className="text-black/40 text-[13px] tracking-[0.1em] leading-relaxed max-w-xl mx-auto">
Drone çekimi, sosyal medya yönetimi veya dijital reklam ne ihtiyacınız varsa, birlikte çözüm üretelim.
</p>
<div className="flex flex-col sm:flex-row items-center justify-center gap-6">
<Link href="/contact" className="button-primary px-10">
Teklif Al
</Link>
<Link href="/works" className="button-secondary px-10">
Portfolyo
</Link>
</div>
</div>
</section>
<Footer />
</main>
);
}

100
app/actions.ts Normal file
View File

@@ -0,0 +1,100 @@
'use server'
import sql from '@/lib/db';
export async function getSettings() {
try {
const settings = await sql`SELECT * FROM settings WHERE id = 1 LIMIT 1`;
return settings[0] || null;
} catch (error) {
console.error('Error fetching settings:', error);
return null;
}
}
export async function getFeaturedServices() {
try {
const services = await sql`SELECT * FROM services WHERE is_featured = true ORDER BY display_order ASC LIMIT 4`;
return services;
} catch (error) {
console.error('Error fetching services:', error);
return [];
}
}
export async function getFeaturedProjects() {
try {
const projects = await sql`SELECT * FROM projects WHERE is_featured = true ORDER BY created_at DESC LIMIT 6`;
return projects;
} catch (error) {
console.error('Error fetching projects:', error);
return [];
}
}
export async function submitLead(formData: {
firstName: string;
lastName: string;
email: string;
projectType: string;
message: string;
}) {
try {
const fullName = `${formData.firstName} ${formData.lastName}`;
await sql`
INSERT INTO leads (full_name, email, service_type, message, status)
VALUES (${fullName}, ${formData.email}, ${formData.projectType}, ${formData.message}, 'new')
`;
return { success: true };
} catch (error) {
console.error('Error submitting lead:', error);
return { error: 'Failed to submit' };
}
}
export async function getPartners() {
try {
return await sql`
SELECT p.*, (
SELECT slug FROM projects
WHERE LOWER(TRIM(p.name)) LIKE '%' || LOWER(TRIM(title)) || '%'
OR LOWER(TRIM(title)) LIKE '%' || LOWER(TRIM(p.name)) || '%'
LIMIT 1
) as project_slug
FROM partners p
ORDER BY p.display_order ASC
`;
} catch (error) {
console.error('Error fetching partners:', error);
return [];
}
}
export async function getProjectBySlug(slug: string) {
try {
const projects = await sql`SELECT * FROM projects WHERE slug = ${slug} LIMIT 1`;
if (projects.length === 0) return null;
const project = projects[0];
// Fetch next project for the bottom section
const nextProjects = await sql`
SELECT * FROM projects
WHERE created_at < ${project.created_at}
ORDER BY created_at DESC
LIMIT 1
`;
// If no older project, fetch the newest one
let nextProject: any = nextProjects[0] || null;
if (!nextProject) {
const newest = await sql`SELECT * FROM projects ORDER BY created_at DESC LIMIT 1`;
nextProject = newest[0] !== project ? newest[0] : null;
}
return { project, nextProject };
} catch (error) {
console.error('Error fetching project:', error);
return null;
}
}

View File

@@ -0,0 +1,60 @@
import Link from 'next/link';
import { LayoutDashboard, Users, Briefcase, Settings, FileText, LogOut, Handshake } from 'lucide-react';
import { logout } from '../actions';
export default function AdminLayout({ children }: { children: React.ReactNode }) {
return (
<div className="min-h-screen bg-black text-white flex">
{/* Sidebar */}
<aside className="w-64 border-r border-white/10 flex flex-col bg-zinc-950 p-4">
<div className="mb-8 px-4">
<h1 className="text-xl font-black uppercase tracking-widest text-[#1e9a83]">Muğla Dijital</h1>
<p className="text-xs text-white/40 uppercase tracking-widest">Admin Panel</p>
</div>
<nav className="flex-1 space-y-2">
<Link href="/admin" className="flex items-center gap-3 px-4 py-3 rounded-xl hover:bg-white/5 transition-colors text-sm font-medium">
<LayoutDashboard className="w-5 h-5 text-white/60" />
Dashboard
</Link>
<Link href="/admin/leads" className="flex items-center gap-3 px-4 py-3 rounded-xl hover:bg-white/5 transition-colors text-sm font-medium">
<Users className="w-5 h-5 text-white/60" />
Mesajlar & Başvurular
</Link>
<Link href="/admin/projects" className="flex items-center gap-3 px-4 py-3 rounded-xl hover:bg-white/5 transition-colors text-sm font-medium">
<Briefcase className="w-5 h-5 text-white/60" />
Projeler
</Link>
<Link href="/admin/services" className="flex items-center gap-3 px-4 py-3 rounded-xl hover:bg-white/5 transition-colors text-sm font-medium">
<FileText className="w-5 h-5 text-white/60" />
Hizmetler
</Link>
<Link href="/admin/settings" className="flex items-center gap-3 px-4 py-3 rounded-xl hover:bg-white/5 transition-colors text-sm font-medium">
<Settings className="w-5 h-5 text-white/60" />
Site Ayarları
</Link>
<Link href="/admin/partners" className="flex items-center gap-3 px-4 py-3 rounded-xl hover:bg-white/5 transition-colors text-sm font-medium">
<Handshake className="w-5 h-5 text-white/60" />
Partnerler
</Link>
</nav>
<div className="mt-auto border-t border-white/10 pt-4">
<form action={logout}>
<button type="submit" className="flex w-full items-center gap-3 px-4 py-3 rounded-xl hover:bg-red-500/10 hover:text-red-500 transition-colors text-sm font-medium text-white/60">
<LogOut className="w-5 h-5" />
Çıkış Yap
</button>
</form>
</div>
</aside>
{/* Main Content */}
<main className="flex-1 overflow-y-auto">
<div className="max-w-6xl mx-auto p-8">
{children}
</div>
</main>
</div>
);
}

View File

@@ -0,0 +1,71 @@
import { getLeadsAdmin, deleteLead } from '../../actions';
import { Trash2, Mail, User, Briefcase, Calendar } from 'lucide-react';
import { revalidatePath } from 'next/cache';
export default async function LeadsPage() {
const leads = await getLeadsAdmin();
async function handleDelete(formData: FormData) {
'use server';
const id = Number(formData.get('id'));
await deleteLead(id);
revalidatePath('/admin/leads');
}
return (
<div className="space-y-8">
<div>
<h1 className="text-3xl font-black uppercase tracking-widest text-white mb-2">Mesajlar & Başvurular</h1>
<p className="text-white/40">İletişim formundan gelen müşteri talepleri.</p>
</div>
<div className="space-y-4">
{leads.map((lead: any) => (
<div key={lead.id} className="bg-zinc-950 border border-white/10 rounded-2xl p-6 flex flex-col md:flex-row gap-6 justify-between md:items-center group hover:border-[#1e9a83]/50 transition-colors">
<div className="space-y-3 flex-1">
<div className="flex items-center gap-4">
<h3 className="text-lg font-bold text-white flex items-center gap-2">
<User className="w-4 h-4 text-[#1e9a83]" />
{lead.full_name}
</h3>
<span className="bg-white/5 px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-widest text-white/60 flex items-center gap-2">
<Briefcase className="w-3 h-3" />
{lead.service_type}
</span>
</div>
<div className="flex flex-col md:flex-row gap-4 text-sm text-white/60">
<a href={`mailto:${lead.email}`} className="flex items-center gap-2 hover:text-white transition-colors">
<Mail className="w-4 h-4" /> {lead.email}
</a>
<div className="flex items-center gap-2">
<Calendar className="w-4 h-4" />
{new Date(lead.created_at).toLocaleDateString('tr-TR', { day: 'numeric', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit' })}
</div>
</div>
<div className="bg-white/5 p-4 rounded-xl mt-4">
<p className="text-white/80 text-sm leading-relaxed">{lead.message}</p>
</div>
</div>
<div>
<form action={handleDelete}>
<input type="hidden" name="id" value={lead.id} />
<button type="submit" className="p-3 bg-red-500/10 text-red-500 rounded-xl hover:bg-red-500 hover:text-white transition-colors">
<Trash2 className="w-5 h-5" />
</button>
</form>
</div>
</div>
))}
{(!leads || leads.length === 0) && (
<div className="text-center py-12 bg-white/5 rounded-2xl border border-white/10 border-dashed">
<p className="text-white/40">Henüz bir mesaj bulunmuyor.</p>
</div>
)}
</div>
</div>
);
}

View File

@@ -0,0 +1,47 @@
import { getDashboardStats } from '../actions';
import { Users, Briefcase, FileText } from 'lucide-react';
export default async function DashboardPage() {
const stats = await getDashboardStats();
return (
<div className="space-y-8">
<div>
<h1 className="text-3xl font-black uppercase tracking-widest text-white mb-2">Dashboard</h1>
<p className="text-white/40">Sistem istatistikleri ve özet görünüm.</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="bg-zinc-950 border border-white/10 rounded-2xl p-6 flex items-center gap-6">
<div className="w-14 h-14 bg-blue-500/10 text-blue-500 rounded-2xl flex items-center justify-center">
<Users className="w-6 h-6" />
</div>
<div>
<p className="text-sm text-white/40 font-bold uppercase tracking-widest">Mesajlar</p>
<p className="text-3xl font-black">{stats.leads}</p>
</div>
</div>
<div className="bg-zinc-950 border border-white/10 rounded-2xl p-6 flex items-center gap-6">
<div className="w-14 h-14 bg-[#1e9a83]/10 text-[#1e9a83] rounded-2xl flex items-center justify-center">
<Briefcase className="w-6 h-6" />
</div>
<div>
<p className="text-sm text-white/40 font-bold uppercase tracking-widest">Projeler</p>
<p className="text-3xl font-black">{stats.projects}</p>
</div>
</div>
<div className="bg-zinc-950 border border-white/10 rounded-2xl p-6 flex items-center gap-6">
<div className="w-14 h-14 bg-purple-500/10 text-purple-500 rounded-2xl flex items-center justify-center">
<FileText className="w-6 h-6" />
</div>
<div>
<p className="text-sm text-white/40 font-bold uppercase tracking-widest">Hizmetler</p>
<p className="text-3xl font-black">{stats.services}</p>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,125 @@
"use client";
import { useState } from "react";
import { Trash2, Save, Edit2, X } from "lucide-react";
import { updatePartner } from "../../actions";
import { useRouter } from "next/navigation";
import Image from "next/image";
export default function PartnerRow({ partner, onDelete }: { partner: any, onDelete: any }) {
const [isEditing, setIsEditing] = useState(false);
const [name, setName] = useState(partner.name);
const [displayOrder, setDisplayOrder] = useState(partner.display_order);
const [isLoading, setIsLoading] = useState(false);
const router = useRouter();
const handleSave = async () => {
setIsLoading(true);
const res = await updatePartner(partner.id, name, Number(displayOrder));
setIsLoading(false);
if (res.success) {
setIsEditing(false);
router.refresh();
} else {
alert("Güncellenirken bir hata oluştu");
}
};
return (
<tr className="hover:bg-white/[0.02] transition-colors border-b border-white/10 last:border-0">
<td className="px-6 py-4">
{isEditing ? (
<input
type="number"
value={displayOrder}
onChange={(e) => setDisplayOrder(e.target.value)}
className="w-16 bg-white/5 border border-white/10 rounded px-2 py-1 text-white focus:border-primary outline-none"
/>
) : (
<span className="font-bold text-white/40">{partner.display_order}</span>
)}
</td>
<td className="px-6 py-4">
<div className="flex items-center gap-4">
{/* Logo Preview */}
<div className="w-24 h-16 bg-white/5 border border-white/10 rounded-lg flex items-center justify-center overflow-hidden p-2 group relative">
{partner.logo ? (
<img
src={partner.logo}
alt={partner.name}
className="w-full h-full object-contain opacity-90 group-hover:opacity-100 transition-opacity transform scale-[4]"
/>
) : (
<span className="text-[10px] text-white/20">Logo Yok</span>
)}
</div>
{/* Tiny Path Info */}
<div className="hidden xl:block">
<div className="text-[10px] text-white/20 font-mono max-w-[200px] truncate">
{partner.logo}
</div>
</div>
</div>
</td>
<td className="px-6 py-4">
{isEditing ? (
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full max-w-md bg-white/5 border border-white/10 rounded px-3 py-1.5 text-white focus:border-primary outline-none"
/>
) : (
<div className="font-bold text-white tracking-wide">{partner.name}</div>
)}
</td>
<td className="px-6 py-4 text-right">
<div className="flex justify-end items-center gap-2">
{isEditing ? (
<>
<button
onClick={handleSave}
disabled={isLoading}
className="p-2 bg-green-500/10 text-green-500 rounded-lg hover:bg-green-500 hover:text-white transition-all disabled:opacity-50"
title="Kaydet"
>
<Save className="w-4 h-4" />
</button>
<button
onClick={() => {
setIsEditing(false);
setName(partner.name);
setDisplayOrder(partner.display_order);
}}
className="p-2 bg-white/5 text-white/40 rounded-lg hover:bg-white/10 hover:text-white transition-all"
title="İptal"
>
<X className="w-4 h-4" />
</button>
</>
) : (
<>
<button
onClick={() => setIsEditing(true)}
className="p-2 bg-white/5 text-white/40 rounded-lg hover:bg-primary hover:text-white transition-all"
title="Düzenle"
>
<Edit2 className="w-4 h-4" />
</button>
<form action={onDelete} className="inline">
<input type="hidden" name="id" value={partner.id} />
<button
type="submit"
className="p-2 bg-red-500/10 text-red-500 rounded-lg hover:bg-red-500 hover:text-white transition-all"
title="Sil"
>
<Trash2 className="w-4 h-4" />
</button>
</form>
</>
)}
</div>
</td>
</tr>
);
}

View File

@@ -0,0 +1,55 @@
import { getPartnersAdmin, deletePartner } from '../../actions';
import { Plus } from 'lucide-react';
import { revalidatePath } from 'next/cache';
import PartnerRow from './PartnerRow';
export default async function PartnersAdminPage() {
const partners = await getPartnersAdmin();
async function handleDelete(formData: FormData) {
'use server';
const id = Number(formData.get('id'));
await deletePartner(id);
revalidatePath('/admin/partners');
}
return (
<div className="space-y-8">
<div className="flex justify-between items-center">
<div>
<h1 className="text-3xl font-black uppercase tracking-widest text-white mb-2">Partnerler</h1>
<p className="text-white/40">Referans markaların listesi.</p>
</div>
<button className="bg-primary text-white px-6 py-2 rounded-xl font-bold hover:bg-primary/80 transition-colors flex items-center gap-2">
<Plus className="w-4 h-4" />
Yeni Partner Ekle
</button>
</div>
<div className="bg-zinc-950 border border-white/10 rounded-2xl overflow-hidden shadow-2xl">
<table className="w-full text-left">
<thead className="bg-white/5 border-b border-white/10">
<tr>
<th className="px-6 py-4 text-[10px] font-bold uppercase tracking-widest text-white/40 w-24">Sıra</th>
<th className="px-6 py-4 text-[10px] font-bold uppercase tracking-widest text-white/40">Logo Önizleme</th>
<th className="px-6 py-4 text-[10px] font-bold uppercase tracking-widest text-white/40">Marka Adı</th>
<th className="px-6 py-4 text-[10px] font-bold uppercase tracking-widest text-white/40 text-right">İşlemler</th>
</tr>
</thead>
<tbody>
{partners.map((partner: any) => (
<PartnerRow key={partner.id} partner={partner} onDelete={handleDelete} />
))}
{(!partners || partners.length === 0) && (
<tr>
<td colSpan={4} className="px-6 py-8 text-center text-white/40">
Henüz partner eklenmemiş.
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
);
}

View File

@@ -0,0 +1,32 @@
import { getProjectByIdAdmin, getServicesAdmin, getPartnersAdmin } from '../../../actions';
import ProjectForm from '../new/ProjectForm';
import Link from 'next/link';
import { ArrowLeft } from 'lucide-react';
import { notFound } from 'next/navigation';
export default async function EditProjectPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const project = await getProjectByIdAdmin(Number(id));
const services = await getServicesAdmin();
const partners = await getPartnersAdmin();
if (!project) {
notFound();
}
return (
<div className="space-y-8 max-w-5xl">
<div className="flex items-center gap-4">
<Link href="/admin/projects" className="p-2 bg-white/5 hover:bg-white/10 rounded-xl transition-colors group">
<ArrowLeft className="w-5 h-5 text-white/40 group-hover:text-white" />
</Link>
<div>
<h1 className="text-3xl font-black uppercase tracking-widest text-white mb-2">Projeyi Düzenle</h1>
<p className="text-white/40">{project.title} projesini güncelliyorsunuz.</p>
</div>
</div>
<ProjectForm initialData={project} services={services} partners={partners} />
</div>
);
}

View File

@@ -0,0 +1,207 @@
'use client';
import { useState, useEffect } from 'react';
import { createProjectAdmin, updateProjectAdmin } from '../../../actions';
import { Save, CheckCircle2, AlertCircle, X } from 'lucide-react';
import { useRouter } from 'next/navigation';
const slugify = (text: string) => {
return text
.toString()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase()
.trim()
.replace(/\s+/g, '-')
.replace(/[^\w-]+/g, '')
.replace(/--+/g, '-');
};
export default function ProjectForm({
initialData,
services = [],
partners = []
}: {
initialData?: any,
services?: any[],
partners?: any[]
}) {
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
const [errorMsg, setErrorMsg] = useState('');
const [title, setTitle] = useState(initialData?.title || '');
const [slug, setSlug] = useState(initialData?.slug || '');
const router = useRouter();
// Auto-slug generation when title changes (only if it's a new project or manually changed title)
const handleTitleChange = (newTitle: string) => {
setTitle(newTitle);
if (!initialData) {
setSlug(slugify(newTitle));
}
};
const parseSafe = (data: any) => {
let current = data;
try {
for (let i = 0; i < 3; i++) {
if (typeof current === 'string' && (current.trim().startsWith('[') || current.trim().startsWith('"'))) {
current = JSON.parse(current);
} else {
break;
}
}
return Array.isArray(current) ? current : (typeof current === 'string' && current ? [current] : []);
} catch (e) {
return Array.isArray(current) ? current : (typeof current === 'string' && current ? [current] : []);
}
};
const initialTechStack = parseSafe(initialData?.tech_stack);
const initialGallery = parseSafe(initialData?.gallery);
const initialCategories = parseSafe(initialData?.category);
async function handleSubmit(formData: FormData) {
setStatus('loading');
let res;
if (initialData?.id) {
res = await updateProjectAdmin(initialData.id, formData);
} else {
res = await createProjectAdmin(formData);
}
if (res.error) {
setErrorMsg(res.error);
setStatus('error');
} else {
setStatus('success');
setTimeout(() => {
router.push('/admin/projects');
router.refresh();
}, 1000);
}
}
return (
<form action={handleSubmit} className="bg-zinc-950 border border-white/10 rounded-3xl p-8 space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-white/40 ml-1">Proje Adı (Partner Seçin)</label>
<select
name="title"
required
value={title}
onChange={(e) => handleTitleChange(e.target.value)}
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:border-[#1e9a83] outline-none"
>
<option value="" className="bg-zinc-900">Seçiniz...</option>
{partners.map(p => (
<option key={p.id} value={p.name} className="bg-zinc-900">{p.name}</option>
))}
</select>
</div>
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-white/40 ml-1">Kısa Başlık (URL Slug)</label>
<input
type="text"
name="slug"
value={slug}
onChange={(e) => setSlug(e.target.value)}
required
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:border-[#1e9a83] outline-none font-mono text-xs"
placeholder="örn: marka-ismi"
/>
</div>
<div className="space-y-2 lg:col-span-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-white/40 ml-1">Kategoriler (Hizmetler - Çoklu Seçim)</label>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 p-4 bg-white/5 border border-white/10 rounded-xl">
{services.map(s => {
const isChecked = initialCategories.includes(s.title);
return (
<label key={s.id} className="flex items-center gap-3 cursor-pointer group">
<input
type="checkbox"
name="category"
value={s.title}
defaultChecked={isChecked}
className="w-4 h-4 rounded border-white/10 bg-white/5 accent-[#1e9a83]"
/>
<span className="text-[11px] font-bold uppercase text-white/60 group-hover:text-white transition-colors">
{s.title}
</span>
</label>
);
})}
</div>
</div>
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-white/40 ml-1">Yıl</label>
<input type="text" name="year" defaultValue={initialData?.year || new Date().getFullYear()} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:border-[#1e9a83] outline-none" placeholder="Örn: 2024" />
</div>
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-white/40 ml-1">Müşteri (Görünen İsim)</label>
<input type="text" name="client" defaultValue={initialData?.client || title} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:border-[#1e9a83] outline-none" placeholder="Örn: ABC A.Ş." />
</div>
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-white/40 ml-1">Lokasyon</label>
<input type="text" name="location" defaultValue={initialData?.location || 'Muğla, TR'} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:border-[#1e9a83] outline-none" placeholder="Örn: Muğla, TR" />
</div>
</div>
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-white/40 ml-1">Alt Başlık (Kısa ıklama)</label>
<input type="text" name="subtitle" defaultValue={initialData?.subtitle} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:border-[#1e9a83] outline-none" placeholder="Projenin kısa özeti..." />
</div>
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-white/40 ml-1">Ana Görsel (URL veya Yol)</label>
<input type="text" name="hero_image" defaultValue={initialData?.hero_image} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:border-[#1e9a83] outline-none" placeholder="/images/projects/1.jpg" />
</div>
<h3 className="text-sm font-bold uppercase tracking-widest text-white/60 mt-8 mb-4 border-b border-white/10 pb-2">Hikaye & Detay</h3>
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-white/40 ml-1">Hikaye Başlığı</label>
<input type="text" name="narrative_title" defaultValue={initialData?.narrative_title} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:border-[#1e9a83] outline-none" placeholder="Geleceği Yeniden Şekillendirmek" />
</div>
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-white/40 ml-1">Hikaye İçeriği (Paragraf)</label>
<textarea name="narrative_desc" defaultValue={initialData?.narrative_desc} rows={4} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:border-[#1e9a83] outline-none resize-none" placeholder="Projenin detaylııklaması..."></textarea>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-white/40 ml-1">Teknolojiler (Her satıra bir tane)</label>
<textarea name="tech_stack" defaultValue={initialTechStack.join('\n')} rows={5} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:border-[#1e9a83] outline-none resize-none" placeholder="React&#10;Next.js&#10;TailwindCSS"></textarea>
</div>
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-white/40 ml-1">Galeri & Instagram Linkleri (Her satıra bir tane)</label>
<textarea name="gallery" defaultValue={initialGallery.join('\n')} rows={5} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:border-[#1e9a83] outline-none resize-none" placeholder="https://instagram.com/p/...&#10;/img1.jpg&#10;/img2.jpg"></textarea>
</div>
</div>
<div className="flex items-center gap-3 pt-2">
<input type="checkbox" name="is_featured" id="is_featured" defaultChecked={initialData?.is_featured} className="w-5 h-5 accent-[#1e9a83] rounded" />
<label htmlFor="is_featured" className="text-sm font-bold text-white/80 cursor-pointer">Bu projeyi anasayfada (Öne Çıkanlar) göster</label>
</div>
<div className="pt-6">
<button type="submit" disabled={status === 'loading'} className="bg-[#1e9a83] hover:bg-[#157a67] text-white font-bold py-3 px-8 rounded-xl transition-colors flex items-center gap-2 disabled:opacity-50">
<Save className="w-5 h-5" />
{status === 'loading' ? 'Kaydediliyor...' : initialData ? 'Projeyi Güncelle' : 'Projeyi Kaydet'}
</button>
</div>
{status === 'success' && (
<div className="flex items-center gap-2 text-green-500 text-sm font-bold bg-green-500/10 p-4 rounded-xl mt-4">
<CheckCircle2 className="w-5 h-5" /> Proje başarıyla {initialData ? 'güncellendi' : 'eklendi'}! Yönlendiriliyorsunuz...
</div>
)}
{status === 'error' && (
<div className="flex items-center gap-2 text-red-500 text-sm font-bold bg-red-500/10 p-4 rounded-xl mt-4">
<AlertCircle className="w-5 h-5" /> {errorMsg}
</div>
)}
</form>
);
}

View File

@@ -0,0 +1,25 @@
import ProjectForm from './ProjectForm';
import { ArrowLeft } from 'lucide-react';
import Link from 'next/link';
import { getServicesAdmin, getPartnersAdmin } from '../../../actions';
export default async function NewProjectPage() {
const services = await getServicesAdmin();
const partners = await getPartnersAdmin();
return (
<div className="space-y-8">
<div className="flex items-center gap-4">
<Link href="/admin/projects" className="p-2 bg-white/5 hover:bg-white/10 rounded-xl transition-colors">
<ArrowLeft className="w-5 h-5 text-white/60" />
</Link>
<div>
<h1 className="text-3xl font-black uppercase tracking-widest text-white mb-2">Yeni Proje Ekle</h1>
<p className="text-white/40">Sisteme yeni bir proje dahil edin.</p>
</div>
</div>
<ProjectForm services={services} partners={partners} />
</div>
);
}

View File

@@ -0,0 +1,90 @@
import { getProjectsAdmin, deleteProject } from '../../actions';
import { Trash2, Edit } from 'lucide-react';
import { revalidatePath } from 'next/cache';
import Image from 'next/image';
import Link from 'next/link';
export default async function ProjectsPage() {
const projects = await getProjectsAdmin();
async function handleDelete(formData: FormData) {
'use server';
const id = Number(formData.get('id'));
await deleteProject(id);
revalidatePath('/admin/projects');
}
return (
<div className="space-y-8">
<div className="flex justify-between items-center">
<div>
<h1 className="text-3xl font-black uppercase tracking-widest text-white mb-2">Projeler</h1>
<p className="text-white/40">Sistemdeki tüm projelerin listesi.</p>
</div>
<Link href="/admin/projects/new" className="bg-[#1e9a83] text-white px-6 py-2 rounded-xl font-bold hover:bg-[#157a67] transition-colors">
Yeni Proje Ekle
</Link>
</div>
<div className="bg-zinc-950 border border-white/10 rounded-2xl overflow-hidden">
<table className="w-full text-left">
<thead className="bg-white/5 border-b border-white/10">
<tr>
<th className="px-6 py-4 text-[10px] font-bold uppercase tracking-widest text-white/40">Görsel</th>
<th className="px-6 py-4 text-[10px] font-bold uppercase tracking-widest text-white/40">Proje Adı</th>
<th className="px-6 py-4 text-[10px] font-bold uppercase tracking-widest text-white/40">Kategori</th>
<th className="px-6 py-4 text-[10px] font-bold uppercase tracking-widest text-white/40">Öne Çıkan</th>
<th className="px-6 py-4 text-[10px] font-bold uppercase tracking-widest text-white/40 text-right">İşlemler</th>
</tr>
</thead>
<tbody className="divide-y divide-white/10">
{projects.map((project: any) => (
<tr key={project.id} className="hover:bg-white/[0.02] transition-colors">
<td className="px-6 py-4">
<div className="w-16 h-12 relative rounded-lg overflow-hidden bg-white/10">
{project.hero_image && (
<Image src={project.hero_image} alt={project.title} fill className="object-cover" />
)}
</div>
</td>
<td className="px-6 py-4">
<div className="font-bold text-white">{project.title}</div>
<div className="text-xs text-white/40">{project.slug}</div>
</td>
<td className="px-6 py-4">
<span className="bg-white/5 px-2 py-1 rounded-md text-xs text-white/60">{project.category}</span>
</td>
<td className="px-6 py-4">
{project.is_featured ? (
<span className="text-green-500 text-xs font-bold uppercase tracking-widest">Evet</span>
) : (
<span className="text-white/20 text-xs font-bold uppercase tracking-widest">Hayır</span>
)}
</td>
<td className="px-6 py-4 text-right flex justify-end gap-2">
<Link href={`/admin/projects/${project.id}`} className="p-2 bg-blue-500/10 text-blue-500 rounded-lg hover:bg-blue-500 hover:text-white transition-colors">
<Edit className="w-4 h-4" />
</Link>
<form action={handleDelete}>
<input type="hidden" name="id" value={project.id} />
<button type="submit" className="p-2 bg-red-500/10 text-red-500 rounded-lg hover:bg-red-500 hover:text-white transition-colors">
<Trash2 className="w-4 h-4" />
</button>
</form>
</td>
</tr>
))}
{(!projects || projects.length === 0) && (
<tr>
<td colSpan={5} className="px-6 py-8 text-center text-white/40">
Henüz proje eklenmemiş.
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
);
}

View File

@@ -0,0 +1,82 @@
import { getServicesAdmin, deleteService } from '../../actions';
import { Trash2, Edit } from 'lucide-react';
import { revalidatePath } from 'next/cache';
export default async function ServicesPage() {
const services = await getServicesAdmin();
async function handleDelete(formData: FormData) {
'use server';
const id = Number(formData.get('id'));
await deleteService(id);
revalidatePath('/admin/services');
}
return (
<div className="space-y-8">
<div className="flex justify-between items-center">
<div>
<h1 className="text-3xl font-black uppercase tracking-widest text-white mb-2">Hizmetler</h1>
<p className="text-white/40">Sistemdeki tüm hizmetlerin listesi.</p>
</div>
<button className="bg-[#1e9a83] text-white px-6 py-2 rounded-xl font-bold hover:bg-[#157a67] transition-colors">
Yeni Hizmet Ekle
</button>
</div>
<div className="bg-zinc-950 border border-white/10 rounded-2xl overflow-hidden">
<table className="w-full text-left">
<thead className="bg-white/5 border-b border-white/10">
<tr>
<th className="px-6 py-4 text-[10px] font-bold uppercase tracking-widest text-white/40 w-16">Sıra</th>
<th className="px-6 py-4 text-[10px] font-bold uppercase tracking-widest text-white/40">İkon Adı</th>
<th className="px-6 py-4 text-[10px] font-bold uppercase tracking-widest text-white/40">Hizmet Adı</th>
<th className="px-6 py-4 text-[10px] font-bold uppercase tracking-widest text-white/40">Öne Çıkan</th>
<th className="px-6 py-4 text-[10px] font-bold uppercase tracking-widest text-white/40 text-right">İşlemler</th>
</tr>
</thead>
<tbody className="divide-y divide-white/10">
{services.map((service: any) => (
<tr key={service.id} className="hover:bg-white/[0.02] transition-colors">
<td className="px-6 py-4">
<span className="font-bold text-white/40">{service.display_order}</span>
</td>
<td className="px-6 py-4">
<span className="bg-white/5 px-2 py-1 rounded-md text-xs text-[#1e9a83] font-mono">{service.icon_name || '-'}</span>
</td>
<td className="px-6 py-4">
<div className="font-bold text-white">{service.title}</div>
</td>
<td className="px-6 py-4">
{service.is_featured ? (
<span className="text-green-500 text-xs font-bold uppercase tracking-widest">Evet</span>
) : (
<span className="text-white/20 text-xs font-bold uppercase tracking-widest">Hayır</span>
)}
</td>
<td className="px-6 py-4 text-right flex justify-end gap-2">
<button className="p-2 bg-blue-500/10 text-blue-500 rounded-lg hover:bg-blue-500 hover:text-white transition-colors">
<Edit className="w-4 h-4" />
</button>
<form action={handleDelete}>
<input type="hidden" name="id" value={service.id} />
<button type="submit" className="p-2 bg-red-500/10 text-red-500 rounded-lg hover:bg-red-500 hover:text-white transition-colors">
<Trash2 className="w-4 h-4" />
</button>
</form>
</td>
</tr>
))}
{(!services || services.length === 0) && (
<tr>
<td colSpan={5} className="px-6 py-8 text-center text-white/40">
Henüz hizmet eklenmemiş.
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
);
}

View File

@@ -0,0 +1,86 @@
'use client';
import { useState } from 'react';
import { updateSettings } from '../../actions';
import { Save, CheckCircle2, AlertCircle } from 'lucide-react';
export default function SettingsForm({ initialData }: { initialData: any }) {
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
async function handleSubmit(formData: FormData) {
setStatus('loading');
const res = await updateSettings(formData);
if (res.error) setStatus('error');
else setStatus('success');
setTimeout(() => setStatus('idle'), 3000);
}
return (
<form action={handleSubmit} className="bg-zinc-950 border border-white/10 rounded-3xl p-8 space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-white/40 ml-1">Site Adı</label>
<input type="text" name="site_name" defaultValue={initialData?.site_name} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:border-[#1e9a83] outline-none" />
</div>
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-white/40 ml-1">İletişim E-posta</label>
<input type="email" name="contact_email" defaultValue={initialData?.contact_email} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:border-[#1e9a83] outline-none" />
</div>
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-white/40 ml-1">İletişim Telefon</label>
<input type="text" name="contact_phone" defaultValue={initialData?.contact_phone} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:border-[#1e9a83] outline-none" />
</div>
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-white/40 ml-1">Rozet Metni</label>
<input type="text" name="status_badge_text" defaultValue={initialData?.status_badge_text} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:border-[#1e9a83] outline-none" />
</div>
</div>
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-white/40 ml-1">Site ıklaması (SEO)</label>
<textarea name="site_description" rows={3} defaultValue={initialData?.site_description} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:border-[#1e9a83] outline-none resize-none" />
</div>
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-white/40 ml-1">Ofis Adresi</label>
<textarea name="office_address" rows={2} defaultValue={initialData?.office_address} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:border-[#1e9a83] outline-none resize-none" />
</div>
<h3 className="text-sm font-bold uppercase tracking-widest text-white/60 mt-8 mb-4 border-b border-white/10 pb-2">Sosyal Medya Linkleri</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-white/40 ml-1">Instagram URL</label>
<input type="url" name="instagram_url" defaultValue={initialData?.instagram_url} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:border-[#1e9a83] outline-none" />
</div>
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-white/40 ml-1">Twitter URL</label>
<input type="url" name="twitter_url" defaultValue={initialData?.twitter_url} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:border-[#1e9a83] outline-none" />
</div>
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-white/40 ml-1">LinkedIn URL</label>
<input type="url" name="linkedin_url" defaultValue={initialData?.linkedin_url} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:border-[#1e9a83] outline-none" />
</div>
</div>
<div className="pt-6">
<button type="submit" disabled={status === 'loading'} className="bg-[#1e9a83] hover:bg-[#157a67] text-white font-bold py-3 px-8 rounded-xl transition-colors flex items-center gap-2 disabled:opacity-50">
<Save className="w-5 h-5" />
{status === 'loading' ? 'Kaydediliyor...' : 'Ayarları Kaydet'}
</button>
</div>
{status === 'success' && (
<div className="flex items-center gap-2 text-green-500 text-sm font-bold bg-green-500/10 p-4 rounded-xl mt-4">
<CheckCircle2 className="w-5 h-5" /> Ayarlar başarıyla güncellendi!
</div>
)}
{status === 'error' && (
<div className="flex items-center gap-2 text-red-500 text-sm font-bold bg-red-500/10 p-4 rounded-xl mt-4">
<AlertCircle className="w-5 h-5" /> Güncelleme sırasında bir hata oluştu.
</div>
)}
</form>
);
}

View File

@@ -0,0 +1,17 @@
import { getSettings } from '@/app/actions';
import SettingsForm from './SettingsForm';
export default async function SettingsPage() {
const settings = await getSettings();
return (
<div className="space-y-8">
<div>
<h1 className="text-3xl font-black uppercase tracking-widest text-white mb-2">Site Ayarları</h1>
<p className="text-white/40">Genel site bilgilerini ve iletişim adreslerini buradan güncelleyebilirsiniz.</p>
</div>
<SettingsForm initialData={settings} />
</div>
);
}

260
app/admin/actions.ts Normal file
View File

@@ -0,0 +1,260 @@
'use server';
import sql from '@/lib/db';
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';
export async function login(prevState: any, formData: FormData) {
const password = formData.get('password') as string;
if (password === process.env.ADMIN_PASSWORD) {
const cookieStore = await cookies();
cookieStore.set('admin_session', 'authenticated', {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 60 * 60 * 24 * 7, // 1 week
path: '/',
});
redirect('/admin');
}
return { error: 'Hatalı şifre' };
}
export async function logout() {
const cookieStore = await cookies();
cookieStore.delete('admin_session');
redirect('/admin/login');
}
export async function getDashboardStats() {
try {
const [leadsCount] = await sql`SELECT count(*) FROM leads`;
const [projectsCount] = await sql`SELECT count(*) FROM projects`;
const [servicesCount] = await sql`SELECT count(*) FROM services`;
return {
leads: Number(leadsCount.count),
projects: Number(projectsCount.count),
services: Number(servicesCount.count),
};
} catch (e) {
return { leads: 0, projects: 0, services: 0 };
}
}
export async function getLeadsAdmin() {
try {
return await sql`SELECT * FROM leads ORDER BY created_at DESC`;
} catch (e) {
return [];
}
}
export async function deleteLead(id: number) {
try {
await sql`DELETE FROM leads WHERE id = ${id}`;
return { success: true };
} catch (e) {
return { error: 'Silinemedi' };
}
}
export async function updateSettings(formData: FormData) {
try {
const data = {
site_name: formData.get('site_name') as string,
site_description: formData.get('site_description') as string,
office_address: formData.get('office_address') as string,
contact_email: formData.get('contact_email') as string,
contact_phone: formData.get('contact_phone') as string,
instagram_url: formData.get('instagram_url') as string,
twitter_url: formData.get('twitter_url') as string,
linkedin_url: formData.get('linkedin_url') as string,
status_badge_text: formData.get('status_badge_text') as string,
};
await sql`
UPDATE settings SET
site_name = ${data.site_name},
site_description = ${data.site_description},
office_address = ${data.office_address},
contact_email = ${data.contact_email},
contact_phone = ${data.contact_phone},
instagram_url = ${data.instagram_url},
twitter_url = ${data.twitter_url},
linkedin_url = ${data.linkedin_url},
status_badge_text = ${data.status_badge_text},
updated_at = CURRENT_TIMESTAMP
WHERE id = 1
`;
return { success: true };
} catch (e) {
return { error: 'Güncellenemedi' };
}
}
export async function getProjectsAdmin() {
try {
return await sql`SELECT * FROM projects ORDER BY created_at DESC`;
} catch (e) {
return [];
}
}
export async function getProjectByIdAdmin(id: number) {
try {
const result = await sql`SELECT * FROM projects WHERE id = ${id}`;
return result[0];
} catch (e) {
return null;
}
}
export async function deleteProject(id: number) {
try {
await sql`DELETE FROM projects WHERE id = ${id}`;
return { success: true };
} catch (e) {
return { error: 'Silinemedi' };
}
}
export async function createProjectAdmin(formData: FormData) {
try {
const slug = formData.get('slug') as string;
const title = formData.get('title') as string;
const subtitle = formData.get('subtitle') as string;
const year = formData.get('year') as string;
const hero_image = formData.get('hero_image') as string;
const client = formData.get('client') as string;
const role = formData.get('role') as string;
const location = formData.get('location') as string;
const narrative_title = formData.get('narrative_title') as string;
const narrative_desc = formData.get('narrative_desc') as string;
const is_featured = formData.get('is_featured') === 'on';
const categoriesRaw = formData.getAll('category') as string[];
const techStackRaw = formData.get('tech_stack') as string;
const galleryRaw = formData.get('gallery') as string;
const category = JSON.stringify(categoriesRaw.filter(Boolean));
const tech_stack = JSON.stringify(techStackRaw ? techStackRaw.split(/[\n,]/).map(s => s.trim()).filter(Boolean) : []);
const gallery = JSON.stringify(galleryRaw ? galleryRaw.split(/[\n,]/).map(s => s.trim()).filter(Boolean) : []);
await sql`
INSERT INTO projects (
slug, title, subtitle, category, year, hero_image, client, role, location,
narrative_title, narrative_desc, is_featured, tech_stack, gallery
) VALUES (
${slug}, ${title}, ${subtitle}, ${category}::jsonb, ${year}, ${hero_image}, ${client}, ${role}, ${location},
${narrative_title}, ${narrative_desc}, ${is_featured}, ${tech_stack}::jsonb, ${gallery}::jsonb
)
`;
return { success: true };
} catch (e: any) {
console.error('Error creating project:', e);
return { error: 'Ekleme başarısız: ' + (e.message || 'Bilinmeyen hata') };
}
}
export async function updateProjectAdmin(id: number, formData: FormData) {
try {
const slug = formData.get('slug') as string;
const title = formData.get('title') as string;
const subtitle = formData.get('subtitle') as string;
const year = formData.get('year') as string;
const hero_image = formData.get('hero_image') as string;
const client = formData.get('client') as string;
const role = formData.get('role') as string;
const location = formData.get('location') as string;
const narrative_title = formData.get('narrative_title') as string;
const narrative_desc = formData.get('narrative_desc') as string;
const is_featured = formData.get('is_featured') === 'on';
const categoriesRaw = formData.getAll('category') as string[];
const techStackRaw = formData.get('tech_stack') as string;
const galleryRaw = formData.get('gallery') as string;
const category = JSON.stringify(categoriesRaw.filter(Boolean));
const tech_stack = JSON.stringify(techStackRaw ? techStackRaw.split(/[\n,]/).map(s => s.trim()).filter(Boolean) : []);
const gallery = JSON.stringify(galleryRaw ? galleryRaw.split(/[\n,]/).map(s => s.trim()).filter(Boolean) : []);
await sql`
UPDATE projects SET
slug = ${slug}, title = ${title}, subtitle = ${subtitle}, category = ${category}::jsonb,
year = ${year}, hero_image = ${hero_image}, client = ${client}, role = ${role},
location = ${location}, narrative_title = ${narrative_title},
narrative_desc = ${narrative_desc}, is_featured = ${is_featured},
tech_stack = ${tech_stack}::jsonb, gallery = ${gallery}::jsonb
WHERE id = ${id}
`;
return { success: true };
} catch (e: any) {
console.error('Error updating project:', e);
return { error: 'Güncelleme başarısız: ' + (e.message || 'Bilinmeyen hata') };
}
}
export async function getServicesAdmin() {
try {
return await sql`SELECT * FROM services ORDER BY display_order ASC`;
} catch (e) {
return [];
}
}
export async function deleteService(id: number) {
try {
await sql`DELETE FROM services WHERE id = ${id}`;
return { success: true };
} catch (e) {
return { error: 'Silinemedi' };
}
}
export async function getPartnersAdmin() {
try {
return await sql`SELECT * FROM partners ORDER BY display_order ASC`;
} catch (e) {
return [];
}
}
export async function deletePartner(id: number) {
try {
await sql`DELETE FROM partners WHERE id = ${id}`;
return { success: true };
} catch (e) {
return { error: 'Silinemedi' };
}
}
export async function createPartnerAdmin(formData: FormData) {
try {
const name = formData.get('name') as string;
const logo = formData.get('logo') as string;
const display_order = Number(formData.get('display_order')) || 0;
await sql`
INSERT INTO partners (name, logo, display_order)
VALUES (${name}, ${logo}, ${display_order})
`;
return { success: true };
} catch (e: any) {
console.error('Error creating partner:', e);
return { error: 'Ekleme başarısız: ' + (e.message || 'Bilinmeyen hata') };
}
}
export async function updatePartner(id: number, name: string, display_order: number) {
try {
await sql`
UPDATE partners
SET name = ${name}, display_order = ${display_order}
WHERE id = ${id}
`;
return { success: true };
} catch (e) {
return { error: 'Güncellenemedi' };
}
}

51
app/admin/login/page.tsx Normal file
View File

@@ -0,0 +1,51 @@
'use client';
import { useActionState } from 'react';
import { login } from '../actions';
import { Lock, ArrowRight } from 'lucide-react';
export default function LoginPage() {
const [state, formAction, pending] = useActionState(login, null);
return (
<div className="min-h-screen bg-black flex items-center justify-center p-4">
<div className="w-full max-w-md">
<div className="bg-zinc-950 border border-white/10 rounded-3xl p-8 space-y-8">
<div className="text-center space-y-2">
<div className="w-16 h-16 bg-[#1e9a83]/10 rounded-full flex items-center justify-center mx-auto mb-6">
<Lock className="w-8 h-8 text-[#1e9a83]" />
</div>
<h1 className="text-2xl font-black uppercase tracking-widest text-white">Yönetici Girişi</h1>
<p className="text-white/40 text-sm">Devam etmek için şifrenizi giriniz.</p>
</div>
<form action={formAction} className="space-y-6">
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-white/40 ml-1">Şifre</label>
<input
type="password"
name="password"
required
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder:text-white/20 focus:border-[#1e9a83] focus:ring-1 focus:ring-[#1e9a83] outline-none transition-all"
placeholder="••••••••"
/>
</div>
{state?.error && (
<p className="text-red-500 text-xs font-bold text-center bg-red-500/10 py-2 rounded-lg">{state.error}</p>
)}
<button
type="submit"
disabled={pending}
className="w-full bg-[#1e9a83] hover:bg-[#157a67] text-white font-bold py-3 px-4 rounded-xl transition-colors flex items-center justify-center gap-2 disabled:opacity-50"
>
{pending ? 'Giriş Yapılıyor...' : 'Giriş Yap'}
{!pending && <ArrowRight className="w-4 h-4" />}
</button>
</form>
</div>
</div>
</div>
);
}

26
app/contact/page.tsx Normal file
View File

@@ -0,0 +1,26 @@
import Navbar from "@/components/Navbar";
import Footer from "@/components/Footer";
import Contact from "@/components/Contact";
import { Metadata } from "next";
export const metadata: Metadata = {
title: "İletişim",
description: "Muğla Dijital ekibiyle iletişime geçin. Projeleriniz hakkında konuşmak veya teklif almak için bizimle hemen bağlantı kurun.",
alternates: {
canonical: "/contact",
},
openGraph: {
title: "İletişim | Muğla Dijital",
description: "Bize ulaşın ve dijital hedeflerinizi birlikte gerçekleştirelim.",
url: "https://mugladijitalmedya.com/contact",
}
};
export default function ContactPage() {
return (
<main className="min-h-screen bg-[#f5f5f0] text-black pt-12">
<Contact />
<Footer />
</main>
);
}

BIN
app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

224
app/globals.css Normal file
View File

@@ -0,0 +1,224 @@
@import "tailwindcss";
@theme {
--color-primary: #1e9a83;
--color-dark-bg: #f5f5f0;
--color-glass-bg: rgba(0, 0, 0, 0.02);
--color-glass-border: rgba(0, 0, 0, 0.08);
}
:root {
--background: #f5f5f0;
--foreground: #1a1a1a;
--accent: #1e9a83;
--border: rgba(0, 0, 0, 0.1);
}
body {
background-color: var(--background);
color: var(--foreground);
font-family: var(--font-martian), monospace;
overflow-x: hidden;
-webkit-font-smoothing: antialiased;
}
/* ===== DIAGONAL LINE DECORATION ===== */
.diagonal-line {
position: relative;
}
.diagonal-line::after {
content: '';
position: absolute;
top: 0;
right: 0;
width: 1px;
height: 141%;
background: var(--border);
transform: rotate(-25deg);
transform-origin: top right;
}
/* ===== EDITORIAL GRID ===== */
.editorial-grid {
display: grid;
border: 1px solid var(--border);
}
.editorial-cell {
border: 1px solid var(--border);
position: relative;
overflow: hidden;
transition: background 0.4s ease;
}
.editorial-cell:hover {
background: rgba(30, 154, 131, 0.03);
}
/* ===== SECTION DIVIDER ===== */
.section-divider {
width: 100%;
height: 1px;
background: var(--border);
}
/* ===== BUTTONS ===== */
.button-primary {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 1rem 2rem;
border: 1px solid var(--foreground);
font-family: var(--font-martian), monospace;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.15em;
text-transform: uppercase;
transition: all 0.4s ease;
color: var(--foreground);
background: transparent;
}
.button-primary:hover {
background: var(--foreground);
color: var(--background);
}
.button-secondary {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 1rem 2rem;
border: 1px solid var(--border);
font-family: var(--font-martian), monospace;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.15em;
text-transform: uppercase;
transition: all 0.4s ease;
color: var(--foreground);
}
.button-secondary:hover {
border-color: var(--foreground);
}
/* ===== NAV LINK HOVER ===== */
.nav-link-hover {
position: relative;
}
.nav-link-hover::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
width: 0;
height: 1px;
background: var(--foreground);
transition: width 0.3s ease;
}
.nav-link-hover:hover::after {
width: 100%;
}
/* ===== ANIMATIONS ===== */
@keyframes reveal-up {
from {
transform: translateY(30px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.reveal {
animation: reveal-up 1s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
.reveal-delayed-1 { animation-delay: 0.1s; }
.reveal-delayed-2 { animation-delay: 0.2s; }
.reveal-delayed-3 { animation-delay: 0.3s; }
.reveal-delayed-4 { animation-delay: 0.4s; }
.reveal-delayed-5 { animation-delay: 0.5s; }
.stagger-item {
opacity: 0;
animation: reveal-up 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
/* ===== STROKE TEXT ===== */
.stroke-text {
-webkit-text-stroke: 1px rgba(0, 0, 0, 0.15);
color: transparent;
}
.stroke-text:hover {
-webkit-text-stroke: 1px var(--accent);
}
/* ===== CUSTOM SCROLLBAR ===== */
::-webkit-scrollbar {
width: 4px;
}
::-webkit-scrollbar-track {
background: var(--background);
}
::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.15);
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(0, 0, 0, 0.3);
}
/* ===== MARQUEE ===== */
@keyframes marquee {
0% { transform: translateX(0); }
100% { transform: translateX(-50%); }
}
.animate-marquee {
animation: marquee 30s linear infinite;
}
.animate-marquee:hover {
animation-play-state: paused;
}
/* ===== EDITORIAL TYPOGRAPHY ===== */
.editorial-headline {
font-family: var(--font-martian), monospace;
font-weight: 400;
text-transform: uppercase;
letter-spacing: -0.02em;
line-height: 1.1;
}
/* ===== CELL DIAGONAL ===== */
.cell-diagonal::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 141%;
height: 1px;
background: var(--border);
transform: rotate(-20deg);
transform-origin: top left;
pointer-events: none;
}
/* Glass utility for admin panel compatibility */
@utility glass {
background: rgba(0, 0, 0, 0.02);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(0, 0, 0, 0.08);
}

154
app/layout.tsx Normal file
View File

@@ -0,0 +1,154 @@
import type { Metadata } from "next";
import { Martian_Mono } from "next/font/google";
import "./globals.css";
import ClientWrapper from "@/components/ClientWrapper";
const martianMono = Martian_Mono({
variable: "--font-martian",
subsets: ["latin"],
weight: ["100", "200", "300", "400", "500", "600", "700", "800"],
});
import sql from "@/lib/db";
export async function generateMetadata(): Promise<Metadata> {
let settings: any = null;
try {
const result = await sql`SELECT site_name, site_description FROM settings WHERE id = 1 LIMIT 1`;
if (result.length > 0) settings = result[0];
} catch (e) {
console.error(e);
}
const siteName = settings?.site_name || "Muğla Dijital Medya Ajansı";
const siteDesc = settings?.site_description || "Muğla Dijital Medya Ajansı olarak sosyal medya yönetimi, reklam yönetimi, SEO, web tasarım ve dijital pazarlama çözümleriyle markanızı öne çıkarıyoruz.";
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "https://mugladijitalmedya.com";
return {
metadataBase: new URL(baseUrl),
title: {
default: "Muğla Dijital | Medya & Prodüksiyon Ajansı",
template: "%s | Muğla Dijital"
},
description: "Muğla'nın öncü dijital medya ajansı. Drone çekimi, sosyal medya yönetimi ve profesyonel reklam çözümleri.",
authors: [{ name: "Muğla Dijital" }],
creator: "Muğla Dijital",
publisher: "Muğla Dijital",
alternates: {
canonical: "/",
},
openGraph: {
type: "website",
locale: "tr_TR",
url: "https://mugladijitalmedya.com",
siteName: "Muğla Dijital",
title: "Muğla Dijital | Medya & Prodüksiyon Ajansı",
description: "Profesyonel drone çekimi ve dijital medya çözümleri.",
images: [
{
url: "/og-image.jpg",
width: 1200,
height: 630,
alt: "Muğla Dijital Medya",
},
],
},
twitter: {
card: "summary_large_image",
title: "Muğla Dijital | Medya & Prodüksiyon Ajansı",
description: "Dijital dünyada markanızı zirveye taşıyın.",
images: ["/og-image.jpg"],
},
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
'max-video-preview': -1,
'max-image-preview': 'large',
'max-snippet': -1,
},
}
};
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="tr">
<body
className={`${martianMono.variable} antialiased`}
>
<ClientWrapper>
{children}
</ClientWrapper>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "Organization",
"name": "Muğla Dijital Medya Ajansı",
"url": "https://mugladijitalmedya.com",
"logo": "https://mugladijitalmedya.com/logo.png",
"sameAs": [
"https://www.instagram.com/mugladijitalmedya/",
// Add other social media links here
],
"contactPoint": {
"@type": "ContactPoint",
"telephone": "+90-XXX-XXX-XXXX", // Should be updated with real phone
"contactType": "customer service",
"areaServed": "TR",
"availableLanguage": ["Turkish", "English"]
}
})
}}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "LocalBusiness",
"name": "Muğla Dijital Medya Ajansı",
"image": "https://mugladijitalmedya.com/og-image.jpg",
"@id": "https://mugladijitalmedya.com",
"url": "https://mugladijitalmedya.com",
"telephone": "+90-XXX-XXX-XXXX",
"address": {
"@type": "PostalAddress",
"streetAddress": "Muğla",
"addressLocality": "Muğla",
"addressRegion": "Muğla",
"postalCode": "48000",
"addressCountry": "TR"
},
"geo": {
"@type": "GeoCoordinates",
"latitude": 37.2153,
"longitude": 28.3636
},
"openingHoursSpecification": {
"@type": "OpeningHoursSpecification",
"dayOfWeek": [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday"
],
"opens": "09:00",
"closes": "18:00"
}
})
}}
/>
</body>
</html>
);
}

18
app/page.tsx Normal file
View File

@@ -0,0 +1,18 @@
import Navbar from "@/components/Navbar";
import Hero from "@/components/Hero";
import SelectedWorks from "@/components/SelectedWorks";
import Partners from "@/components/Partners";
import ServicesGrid from "@/components/ServicesGrid";
import Footer from "@/components/Footer";
export default function Home() {
return (
<main className="relative min-h-screen bg-[#f5f5f0] text-black">
<Hero />
<Partners />
<ServicesGrid />
<SelectedWorks />
<Footer />
</main>
);
}

57
app/partners/page.tsx Normal file
View File

@@ -0,0 +1,57 @@
import { getPartners } from "@/app/actions";
import Navbar from "@/components/Navbar";
import Footer from "@/components/Footer";
import { Metadata } from "next";
import PartnersList from "@/components/PartnersList";
export const metadata: Metadata = {
title: "Partnerlerimiz | İş Ortaklarımız",
description: "Muğla Dijital olarak birlikte değer yarattığımız iş ortaklarımız ve partnerlerimiz. Sektöründe öncü markalarla dijital büyüme hikayeleri.",
alternates: {
canonical: "/partners",
},
openGraph: {
title: "Partnerlerimiz | Muğla Dijital İş Ortakları",
description: "Birlikte büyüdüğümüz markalar ve iş ortaklıklarımız.",
images: ["/partners-og.jpg"],
}
};
export default async function PartnersPage() {
const partners = await getPartners();
return (
<main className="min-h-screen bg-[#f5f5f0] text-black pt-24">
<Navbar />
<section className="pt-24 pb-16 px-6 md:px-12 border-b border-black/10">
<div className="max-w-7xl mx-auto">
<span className="text-[10px] tracking-[0.2em] uppercase text-black/40 block mb-6">Partnerlerimiz</span>
<h1 className="editorial-headline text-4xl md:text-6xl lg:text-[5.5rem] text-black uppercase">
Birlikte <br /> <span className="text-primary">Büyüyoruz.</span>
</h1>
<p className="text-black/40 text-[14px] max-w-2xl leading-relaxed mt-10">
Güçlü ortaklıkları, başarılı dijital hikayelerin temelidir. Sektöründe öncü markalarla birlikte değer üretiyor ve markaları geleceğe hazırlıyoruz.
</p>
</div>
</section>
<PartnersList partners={(partners || []) as any} />
{/* CTA */}
<section className="py-24 px-6 text-center border-t border-black/10">
<div className="max-w-4xl mx-auto space-y-8">
<h2 className="editorial-headline text-3xl md:text-5xl text-black uppercase">İş Ortaklarımız Arasına <br /><span className="text-primary">Katılın.</span></h2>
<p className="text-black/40 text-[13px] tracking-[0.1em]">
Markanızı dijitalde bir üst seviyeye taşımak için doğru partneri arıyorsanız, yanınızdayız.
</p>
<a href="/contact" className="button-primary mx-auto inline-flex">
İş Birliği Başlat
</a>
</div>
</section>
<Footer />
</main>
);
}

20
app/robots.ts Normal file
View File

@@ -0,0 +1,20 @@
import { MetadataRoute } from 'next';
export default function robots(): MetadataRoute.Robots {
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "https://mugladijitalmedya.com";
return {
rules: [
{
userAgent: '*',
allow: '/',
disallow: '/admin/',
},
{
userAgent: ['GPTBot', 'ChatGPT-User', 'PerplexityBot', 'ClaudeBot', 'anthropic-ai', 'Google-Extended'],
allow: '/',
disallow: '/admin/',
}
],
sitemap: `${baseUrl}/sitemap.xml`,
};
}

50
app/services/page.tsx Normal file
View File

@@ -0,0 +1,50 @@
import sql from "@/lib/db";
import ServicesClient from "@/components/ServicesClient";
import { Metadata } from "next";
export async function generateMetadata(): Promise<Metadata> {
return {
title: "Hizmetlerimiz",
description: "Muğla Dijital olarak sunduğumuz profesyonel dijital medya, sosyal medya yönetimi ve reklam çözümlerimizi inceleyin.",
alternates: {
canonical: "/services",
},
openGraph: {
title: "Hizmetlerimiz | Muğla Dijital",
description: "Dijital dünyada fark yaratan profesyonel hizmetlerimiz.",
url: "https://mugladijitalmedya.com/services",
}
};
}
export default async function ServicesPage() {
const services = await sql`SELECT * FROM services ORDER BY display_order ASC`;
const servicesSchema = {
"@context": "https://schema.org",
"@type": "ItemList",
"itemListElement": (services || []).map((service: any, index: number) => ({
"@type": "ListItem",
"position": index + 1,
"item": {
"@type": "Service",
"name": service.title,
"description": service.description,
"provider": {
"@type": "Organization",
"name": "Muğla Dijital"
}
}
}))
};
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(servicesSchema) }}
/>
<ServicesClient services={services || []} />
</>
);
}

32
app/sitemap.ts Normal file
View File

@@ -0,0 +1,32 @@
import { MetadataRoute } from 'next';
import sql from '@/lib/db';
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "https://mugladijitalmedya.com";
// Static pages
const staticPages = [
'',
'/about',
'/services',
'/works',
'/contact',
].map((route) => ({
url: `${baseUrl}${route}`,
lastModified: new Date(),
changeFrequency: 'weekly' as const,
priority: route === '' ? 1 : 0.8,
}));
// Dynamic projects
const projects = await sql`SELECT slug, updated_at FROM projects`;
const projectPages = (projects || []).map((project) => ({
url: `${baseUrl}/works/${project.slug}`,
lastModified: new Date(project.updated_at || new Date()),
changeFrequency: 'monthly' as const,
priority: 0.6,
}));
return [...staticPages, ...projectPages];
}

29
app/works/[slug]/page.tsx Normal file
View File

@@ -0,0 +1,29 @@
import { getProjectBySlug } from "@/app/actions";
import WorkDetailClient from "@/components/WorkDetailClient";
import { notFound } from "next/navigation";
import { Metadata } from "next";
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }): Promise<Metadata> {
const { slug } = await params;
const data = await getProjectBySlug(slug);
if (!data) return {};
const { project } = data;
return {
title: project.title,
description: project.subtitle,
openGraph: {
title: `${project.title} | Muğla Dijital`,
description: project.subtitle,
images: [project.hero_image],
},
};
}
export default async function ProjectDetailPage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const data = await getProjectBySlug(slug);
if (!data) notFound();
return <WorkDetailClient project={data.project} nextProject={data.nextProject} />;
}

24
app/works/page.tsx Normal file
View File

@@ -0,0 +1,24 @@
import sql from "@/lib/db";
import WorksClient from "@/components/WorksClient";
import { Metadata } from "next";
export async function generateMetadata(): Promise<Metadata> {
return {
title: "Çalışmalarımız",
description: "Muğla Dijital olarak hayata geçirdiğimiz seçkin projeleri, sosyal medya kampanyalarını ve dijital pazarlama başarı hikayelerini inceleyin.",
alternates: {
canonical: "/works",
},
openGraph: {
title: "Çalışmalarımız | Muğla Dijital",
description: "Markalar için yarattığımız dijital başarı hikayeleri.",
url: "https://mugladijitalmedya.com/works",
}
};
}
export default async function WorksPage() {
const projects = await sql`SELECT * FROM projects ORDER BY created_at DESC`;
return <WorksClient projects={projects || []} />;
}