feat: integrate Cloudinary image upload, Instagram Feed, and fix Next.js warnings

This commit is contained in:
2026-06-09 21:28:45 +03:00
parent 2b3392dcb1
commit a4ad9344a0
52 changed files with 4300 additions and 263 deletions
+27
View File
@@ -0,0 +1,27 @@
import { Marcellus } from 'next/font/google';
import '@/app/globals.css';
const marcellus = Marcellus({
subsets: ['latin'],
variable: '--font-marcellus',
weight: ['400'],
});
export const metadata = {
title: 'Kozmos Admin - Giriş Yap',
description: 'Kozmos Beach & More - Yönetim Paneli Girişi',
};
export default function AuthLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="tr" className={`${marcellus.variable}`}>
<body className="bg-gray-50 text-gray-900 font-body antialiased min-h-screen">
{children}
</body>
</html>
);
}
+13
View File
@@ -0,0 +1,13 @@
import LoginClient from "@/components/admin/LoginClient";
import { getAuthCookie } from "@/lib/auth";
import { redirect } from "next/navigation";
export default async function LoginPage() {
const admin = await getAuthCookie();
if (admin) {
redirect("/admin");
}
return <LoginClient />;
}
+22
View File
@@ -0,0 +1,22 @@
import { getAboutSettings } from "@/app/actions/about";
import AboutClient from "@/components/admin/AboutClient";
export const dynamic = "force-dynamic";
export default async function AboutPage() {
const result = await getAboutSettings();
const settings = result.success && result.data ? result.data : {
title: "Sonsuz Mavilikte Bir Kaçış",
description: "Kozmos Beach & More, denizin esintisiyle doğanın ritmini bir araya getiren eşsiz bir deneyim sunuyor. Gündüz güneşin ve berrak denizin tadını çıkarırken, gün batımıyla birlikte DJ performansları ve özel kokteyller eşliğinde unutulmaz anılar biriktirin.",
card1Title: "Kusursuz Sahil",
card1Desc: "İncecik altın kumu ve turkuaz sularıyla kendinizi tamamen yenileyin.",
card2Title: "Gastronomi",
card2Desc: "Dünya mutfağından özenle seçilmiş lezzetler ve imza kokteyller.",
card3Title: "Canlı Eğlence",
card3Desc: "Her akşam gün batımında başlayan benzersiz DJ performansları.",
image: "https://images.unsplash.com/photo-1544148103-0773bf10d330?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
};
return <AboutClient settings={settings as any} />;
}
+12
View File
@@ -0,0 +1,12 @@
import { getAdmins } from "@/app/actions/admin";
import AdminsClient from "@/components/admin/AdminsClient";
// Vercel / Next.js için dinamik rendering (cache'lemeyi kapatmak için)
export const dynamic = "force-dynamic";
export default async function AdminsPage() {
const result = await getAdmins();
const admins = result.success && result.data ? result.data : [];
return <AdminsClient initialAdmins={admins} />;
}
+21
View File
@@ -0,0 +1,21 @@
import { getBeachSettings } from "@/app/actions/beach";
import BeachClient from "@/components/admin/BeachClient";
export const dynamic = "force-dynamic";
export default async function BeachPage() {
const result = await getBeachSettings();
// result.data will always be populated if success because we upsert "default"
const settings = result.success && result.data ? result.data : {
title: "Ayrıcalıklı Bir Gevşeme Alanı",
description: "Tertemiz sularla buluşan özel plajımız, kendinizi tamamen yenileyebilmeniz için rahatlığın merkezinde. Altın kumların ve berrak denizin tadını çıkarırken, dünya standartlarındaki hizmetimizle her anınızın özel hissettirmesini sağlıyoruz.",
reservationInfo: "VIP cabana ve ön sıra şezlonglar yoğun talep görmektedir. Yeriniz garantilemek için en az bir gün önceden bizimle iletişime geçmenizi öneririz.",
image1: "https://images.unsplash.com/photo-1544148103-0773bf10d330?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
image2: "https://images.unsplash.com/photo-1519046904884-53103b34b206?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
image3: "https://images.unsplash.com/photo-1499793983690-e29da59ef1c2?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
image4: "https://images.unsplash.com/photo-1520250497591-112f2f40a3f4?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
};
return <BeachClient settings={settings as any} />;
}
+18
View File
@@ -0,0 +1,18 @@
import { getContactSettings } from "@/app/actions/contact";
import ContactClient from "@/components/admin/ContactClient";
export const dynamic = "force-dynamic";
export default async function ContactPage() {
const result = await getContactSettings();
// result.data will always be populated if success because we upsert "default"
const settings = result.success && result.data ? result.data : {
address: "Akyaka, Gökova Körfezi, Muğla",
phone: "905000000000",
instagram: "https://www.instagram.com/kozmosbeach/",
mapUrl: "https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d12754.717654763133!2d28.3144!3d36.9507!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x14bf76ccf312ba37%3A0xcdaaa97b102b4bb7!2sAkyaka%2C%20Ula%2FMu%C4%9Fla!5e0!3m2!1str!2str!4v1700000000000!5m2!1str!2str"
};
return <ContactClient settings={settings as any} />;
}
+18
View File
@@ -0,0 +1,18 @@
import { getEvents } from "@/app/actions/events";
import EventsClient from "@/components/admin/EventsClient";
export const dynamic = "force-dynamic";
export default async function EventsPage() {
const result = await getEvents();
const events = result.success && result.data ? result.data : [];
// Data parsing for Client Component
const parsedEvents = events.map(e => ({
...e,
createdAt: e.createdAt.toISOString(),
updatedAt: e.updatedAt.toISOString(),
}));
return <EventsClient initialEvents={parsedEvents as any} />;
}
+18
View File
@@ -0,0 +1,18 @@
import { getGalleryImages } from "@/app/actions/gallery";
import GalleryClient from "@/components/admin/GalleryClient";
export const dynamic = "force-dynamic";
export default async function GalleryPage() {
const result = await getGalleryImages();
const images = result.success && result.data ? result.data : [];
// Data parsing for Client Component
const parsedImages = images.map(img => ({
...img,
createdAt: img.createdAt.toISOString(),
updatedAt: img.updatedAt.toISOString(),
}));
return <GalleryClient initialImages={parsedImages as any} />;
}
+124
View File
@@ -0,0 +1,124 @@
import { Marcellus } from 'next/font/google';
import '@/app/globals.css';
import Link from 'next/link';
import { Users, LayoutDashboard, LogOut } from 'lucide-react';
const marcellus = Marcellus({
subsets: ['latin'],
variable: '--font-marcellus',
weight: ['400'],
});
export const metadata = {
title: 'Kozmos Admin',
description: 'Kozmos Beach & More - Yönetim Paneli',
};
export default function AdminLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="tr" className={`${marcellus.variable}`}>
<body className="bg-gray-50 text-gray-900 font-body antialiased flex min-h-screen">
{/* Sidebar */}
<aside className="w-64 bg-charcoal text-cream flex flex-col shadow-2xl">
{/* Header */}
<div className="p-6 border-b border-gray-700 flex items-center justify-center">
<img src="/logo.png" alt="Kozmos Logo" className="h-12 brightness-0 invert opacity-90 object-contain" />
</div>
<nav className="flex-1 p-4 space-y-2">
<Link
href="/admin"
className="flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-gray-800 transition-colors"
>
<LayoutDashboard className="w-5 h-5" />
<span>Dashboard</span>
</Link>
<Link
href="/admin/admins"
className="flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-gray-800 transition-colors"
>
<Users className="w-5 h-5" />
<span>Admin Yönetimi</span>
</Link>
<Link
href="/admin/events"
className="flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-gray-800 transition-colors"
>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg>
<span>Etkinlik Yönetimi</span>
</Link>
<Link
href="/admin/about"
className="flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-gray-800 transition-colors"
>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>
<span>Hakkımızda Yönetimi</span>
</Link>
<Link
href="/admin/beach"
className="flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-gray-800 transition-colors"
>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"></circle><path d="M12 2v20"></path><path d="M2 12h20"></path></svg>
<span>Plaj Yönetimi</span>
</Link>
<Link
href="/admin/gallery"
className="flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-gray-800 transition-colors"
>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg>
<span>Galeri Yönetimi</span>
</Link>
<Link
href="/admin/contact"
className="flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-gray-800 transition-colors"
>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path><circle cx="12" cy="10" r="3"></circle></svg>
<span>İletişim & Konum</span>
</Link>
</nav>
<div className="p-4 border-t border-gray-700 space-y-2">
<a
href="/"
className="flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-gray-800 transition-colors text-gray-400 hover:text-white"
>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
<span>Siteye Git</span>
</a>
<form action={async () => {
"use server";
const { logout } = await import("@/app/actions/auth");
await logout();
}}>
<button
type="submit"
className="w-full flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-red-900/50 transition-colors text-red-400 hover:text-red-300"
>
<LogOut className="w-5 h-5" />
<span>Çıkış Yap</span>
</button>
</form>
</div>
</aside>
{/* Main Content */}
<main className="flex-1 overflow-y-auto">
{children}
</main>
</body>
</html>
);
}
+17
View File
@@ -0,0 +1,17 @@
export default function AdminDashboard() {
return (
<div className="p-8">
<h1 className="text-3xl font-serif text-gray-900 mb-8">Dashboard</h1>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="bg-white p-6 rounded-xl shadow-sm border border-gray-100">
<h3 className="text-gray-500 text-sm font-medium mb-2">Sisteme Hoş Geldiniz</h3>
<p className="text-2xl font-bold text-gray-900">Kozmos Yönetim Paneli</p>
<p className="text-sm text-gray-500 mt-4">
Sol menüden "Admin Yönetimi" kısmına giderek yeni yönetici ekleyebilir veya silebilirsiniz.
</p>
</div>
</div>
</div>
);
}