feat: add admin panel, Openinary media integration and standardized footer backlink

This commit is contained in:
mstfyldz
2026-08-23 01:40:48 +03:00
parent 17915cd3d5
commit b312bc5593
29 changed files with 1405 additions and 41 deletions
+102
View File
@@ -0,0 +1,102 @@
import { prisma } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { redirect } from 'next/navigation';
import { Trash2, Plus, Briefcase } from 'lucide-react';
import { addServiceItem, deleteServiceItem } from '../actions';
import { revalidatePath } from 'next/cache';
export default async function ServicesAdminPage() {
const session = await getSession();
if (!session) redirect('/admin/login');
const services = await prisma.service.findMany({
orderBy: { createdAt: 'desc' }
});
return (
<div>
<div className="flex justify-between items-center mb-8">
<h1 className="text-3xl font-bold">Hizmetlerimiz Yönetimi</h1>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Ekleme Formu */}
<div className="lg:col-span-1">
<div className="bg-white dark:bg-slate-900 rounded-2xl shadow-sm border border-slate-200 dark:border-slate-800 p-6 sticky top-8">
<h2 className="text-xl font-bold mb-6">Yeni Hizmet Ekle</h2>
<form action={async (formData: FormData) => {
'use server';
const title = formData.get('title') as string;
const description = formData.get('description') as string;
const iconUrl = formData.get('iconUrl') as string;
if (title && description) {
await addServiceItem({ title, description, iconUrl });
revalidatePath('/admin/services');
}
}} className="space-y-4">
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">Başlık</label>
<input type="text" name="title" 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" />
</div>
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">Açıklama</label>
<textarea name="description" required rows={4} 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"></textarea>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">İkon URL (Opsiyonel)</label>
<input type="url" name="iconUrl" 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" />
</div>
<button type="submit" className="w-full flex items-center justify-center space-x-2 bg-purple-600 hover:bg-purple-700 text-white py-2.5 rounded-lg transition-colors">
<Plus className="w-5 h-5" />
<span>Ekle</span>
</button>
</form>
</div>
</div>
{/* Listeleme */}
<div className="lg:col-span-2">
<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">Mevcut Hizmetler ({services.length})</h2>
<div className="space-y-4">
{services.map(item => (
<div key={item.id} className="group relative flex items-start space-x-4 p-4 rounded-xl border border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-800">
<div className="flex-shrink-0 p-3 bg-white dark:bg-slate-900 rounded-lg border border-slate-200 dark:border-slate-700">
{item.iconUrl ? (
// eslint-disable-next-line @next/next/no-img-element
<img src={item.iconUrl} alt={item.title} className="w-8 h-8 object-contain" />
) : (
<Briefcase className="w-8 h-8 text-slate-400" />
)}
</div>
<div className="flex-1 min-w-0">
<h3 className="font-semibold text-slate-800 dark:text-white text-lg">{item.title}</h3>
<p className="text-sm text-slate-500 dark:text-slate-400 mt-1 whitespace-pre-wrap">{item.description}</p>
</div>
<div className="flex-shrink-0 opacity-0 group-hover:opacity-100 transition-opacity">
<form action={async () => {
'use server';
await deleteServiceItem(item.id);
revalidatePath('/admin/services');
}}>
<button type="submit" className="p-2 text-red-600 hover:bg-red-50 dark:hover:bg-red-900/30 rounded-lg transition-colors">
<Trash2 className="w-5 h-5" />
</button>
</form>
</div>
</div>
))}
{services.length === 0 && (
<div className="py-8 text-center text-slate-500">
Henüz hizmet eklenmemiş.
</div>
)}
</div>
</div>
</div>
</div>
</div>
);
}