feat: add dynamic site settings, hero media, admin panels, and database integration

This commit is contained in:
mstfyldz
2026-06-05 16:59:05 +03:00
parent 121e127f6b
commit 94182b6bc5
27 changed files with 1894 additions and 52 deletions
+92
View File
@@ -0,0 +1,92 @@
import { prisma } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { redirect } from 'next/navigation';
import { Image as ImageIcon, Video, Upload } from 'lucide-react';
import { updateHeroMedia } from '../actions';
import { revalidatePath } from 'next/cache';
import { uploadImage } from '@/lib/cloudinary';
export default async function HeroAdminPage() {
const session = await getSession();
if (!session) redirect('/admin/login');
const heroMediaList = await prisma.heroMedia.findMany();
const currentHero = heroMediaList.length > 0 ? heroMediaList[0] : null;
return (
<div>
<div className="flex justify-between items-center mb-8">
<h1 className="text-3xl font-bold">Hero (Ana Ekran) Yönetimi</h1>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Yükleme Formu */}
<div className="bg-white dark:bg-slate-900 rounded-2xl shadow-sm border border-slate-200 dark:border-slate-800 p-6 h-fit">
<h2 className="text-xl font-bold mb-6">Yeni Medya Yükle</h2>
<form action={async (formData: FormData) => {
'use server';
const file = formData.get('media') as File;
if (file && file.size > 0) {
const url = await uploadImage(file);
// Cloudinary returns video format URLs or we can infer from file type
const type = file.type.startsWith('video/') ? 'video' : 'image';
await updateHeroMedia(url, type);
revalidatePath('/admin/hero');
}
}} className="space-y-4">
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
Resim veya Video Seçin (Maksimum 50MB)
</label>
<input
type="file"
name="media"
accept="image/*,video/*"
required
className="w-full px-4 py-2 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg outline-none focus:ring-2 focus:ring-blue-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100"
/>
<p className="mt-2 text-xs text-slate-500">
Yeni bir dosya yüklediğinizde, mevcut olan otomatik olarak silinir ve yerini alır. Sadece bir adet Hero medyası bulunabilir.
</p>
</div>
<button type="submit" className="w-full flex items-center justify-center space-x-2 bg-blue-600 hover:bg-blue-700 text-white py-2.5 rounded-lg transition-colors">
<Upload className="w-5 h-5" />
<span>Yükle ve Güncelle</span>
</button>
</form>
</div>
{/* Mevcut Medya */}
<div className="bg-white dark:bg-slate-900 rounded-2xl shadow-sm border border-slate-200 dark:border-slate-800 p-6">
<h2 className="text-xl font-bold mb-6">Aktif Medya</h2>
{currentHero ? (
<div className="rounded-xl overflow-hidden border border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-800">
<div className="aspect-[16/9] w-full overflow-hidden bg-slate-200 dark:bg-slate-700 relative">
{currentHero.type === 'video' ? (
<video
src={currentHero.url}
controls
className="w-full h-full object-cover"
/>
) : (
// eslint-disable-next-line @next/next/no-img-element
<img src={currentHero.url} alt="Hero" className="w-full h-full object-cover" />
)}
<div className="absolute top-2 right-2 bg-black/70 backdrop-blur-md text-white text-xs px-2 py-1 rounded flex items-center space-x-1">
{currentHero.type === 'video' ? <Video className="w-3 h-3" /> : <ImageIcon className="w-3 h-3" />}
<span>{currentHero.type === 'video' ? 'Video' : 'Görsel'}</span>
</div>
</div>
</div>
) : (
<div className="py-12 text-center text-slate-500 bg-slate-50 dark:bg-slate-800 rounded-xl border border-dashed border-slate-300 dark:border-slate-700">
Henüz medya yüklenmemiş. Varsayılan resim gösteriliyor.
</div>
)}
</div>
</div>
</div>
);
}