feat: add dynamic site settings, hero media, admin panels, and database integration
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
import { prisma } from '@/lib/db';
|
||||
import { getSession } from '@/lib/auth';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { Trash2, Plus, Briefcase, Edit, X } from 'lucide-react';
|
||||
import { addServiceItem, deleteServiceItem, updateServiceItem } from '../actions';
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { uploadImage } from '@/lib/cloudinary';
|
||||
import Link from 'next/link';
|
||||
|
||||
export default async function ServicesAdminPage({ searchParams }: { searchParams: Promise<{ edit?: string }> }) {
|
||||
const session = await getSession();
|
||||
if (!session) redirect('/admin/login');
|
||||
|
||||
const { edit } = await searchParams;
|
||||
|
||||
const services = await prisma.service.findMany({
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
|
||||
const editingService = edit ? services.find(s => s.id === edit) : null;
|
||||
|
||||
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 / Düzenleme 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">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h2 className="text-xl font-bold">{editingService ? "Hizmeti Düzenle" : "Yeni Hizmet Ekle"}</h2>
|
||||
{editingService && (
|
||||
<Link href="/admin/services" className="text-slate-400 hover:text-slate-600">
|
||||
<X className="w-5 h-5" />
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<form action={async (formData: FormData) => {
|
||||
'use server';
|
||||
const id = formData.get('id') as string;
|
||||
const title = formData.get('title') as string;
|
||||
const description = formData.get('description') as string;
|
||||
const iconFile = formData.get('icon') as File;
|
||||
|
||||
if (title && description) {
|
||||
let iconUrl = editingService?.iconUrl || '';
|
||||
if (iconFile && iconFile.size > 0) {
|
||||
iconUrl = await uploadImage(iconFile);
|
||||
}
|
||||
|
||||
if (id) {
|
||||
await updateServiceItem(id, { title, description, iconUrl });
|
||||
} else {
|
||||
await addServiceItem({ title, description, iconUrl });
|
||||
}
|
||||
|
||||
revalidatePath('/admin/services');
|
||||
redirect('/admin/services');
|
||||
}
|
||||
}} className="space-y-4">
|
||||
|
||||
{editingService && <input type="hidden" name="id" value={editingService.id} />}
|
||||
|
||||
<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" defaultValue={editingService?.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" defaultValue={editingService?.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">Resim Yükle {editingService ? "(Değiştirmek için seçin)" : ""}</label>
|
||||
<input type="file" name="icon" accept="image/*" required={!editingService} 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-purple-50 file:text-purple-700 hover:file:bg-purple-100" />
|
||||
{editingService?.iconUrl && (
|
||||
<div className="mt-2 text-xs text-slate-500">Mevcut resim korunuyor. Yeni resim seçerseniz değiştirilecektir.</div>
|
||||
)}
|
||||
</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">
|
||||
{editingService ? <Edit className="w-5 h-5" /> : <Plus className="w-5 h-5" />}
|
||||
<span>{editingService ? "Güncelle" : "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="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{services.map(item => (
|
||||
<div key={item.id} className="group relative rounded-xl overflow-hidden border border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-800">
|
||||
<div className="aspect-video w-full overflow-hidden bg-slate-200 dark:bg-slate-700">
|
||||
{item.iconUrl ? (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img src={item.iconUrl} alt={item.title} className="w-full h-full object-cover" />
|
||||
) : (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<Briefcase className="w-8 h-8 text-slate-400" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<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 line-clamp-3">{item.description}</p>
|
||||
</div>
|
||||
<div className="absolute top-2 right-2 flex space-x-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<Link href={`/admin/services?edit=${item.id}`} className="p-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg shadow-lg transition-colors">
|
||||
<Edit className="w-4 h-4" />
|
||||
</Link>
|
||||
<form action={async () => {
|
||||
'use server';
|
||||
await deleteServiceItem(item.id);
|
||||
revalidatePath('/admin/services');
|
||||
}}>
|
||||
<button type="submit" className="p-2 bg-red-600 hover:bg-red-700 text-white rounded-lg shadow-lg transition-colors">
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{services.length === 0 && (
|
||||
<div className="col-span-full py-8 text-center text-slate-500">
|
||||
Henüz hizmet eklenmemiş.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user