feat: modernize admin panel UI and add openinary integration
This commit is contained in:
@@ -19,18 +19,21 @@ export default async function CategoriesPage() {
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<h1 className="text-2xl font-bold text-gray-900">Kategoriler</h1>
|
||||
<Link href="/admin/categories/new" className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg font-medium flex items-center gap-2 transition-colors">
|
||||
<Plus size={18} /> Yeni Kategori Ekle
|
||||
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-extrabold text-slate-900 tracking-tight">Kategoriler</h1>
|
||||
<p className="text-sm text-slate-500 mt-1">Menü kategorilerinizi yönetin ve sıralayın.</p>
|
||||
</div>
|
||||
<Link href="/admin/categories/new" className="bg-blue-600 hover:bg-blue-700 text-white px-5 py-2.5 rounded-xl font-medium flex items-center gap-2 transition-all shadow-sm hover:shadow-md hover:-translate-y-0.5">
|
||||
<Plus size={18} /> Yeni Kategori
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
|
||||
<div className="bg-white rounded-2xl shadow-[0_2px_10px_-3px_rgba(6,81,237,0.1)] border border-slate-100 overflow-hidden">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-left border-collapse">
|
||||
<thead>
|
||||
<tr className="bg-gray-50 border-b border-gray-100 text-sm font-semibold text-gray-600">
|
||||
<tr className="bg-slate-50/50 border-b border-slate-100 text-xs uppercase tracking-wider font-semibold text-slate-500">
|
||||
<th className="py-4 px-6">Kategori Adı</th>
|
||||
<th className="py-4 px-6 text-center">Sıra</th>
|
||||
<th className="py-4 px-6 text-center">Ürün Sayısı</th>
|
||||
@@ -38,25 +41,25 @@ export default async function CategoriesPage() {
|
||||
<th className="py-4 px-6 text-right">İşlemler</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-100">
|
||||
<tbody className="divide-y divide-slate-100/80">
|
||||
{categories.map((category) => (
|
||||
<tr key={category.id} className="hover:bg-gray-50 transition-colors">
|
||||
<td className="py-4 px-6 font-medium text-gray-900">{category.name_tr}</td>
|
||||
<td className="py-4 px-6 text-center text-gray-600">{category.order_num}</td>
|
||||
<tr key={category.id} className="hover:bg-slate-50/50 transition-colors group">
|
||||
<td className="py-4 px-6 font-semibold text-slate-800">{category.name_tr}</td>
|
||||
<td className="py-4 px-6 text-center text-slate-500 font-medium">{category.order_num}</td>
|
||||
<td className="py-4 px-6 text-center">
|
||||
<span className="bg-blue-50 text-blue-700 py-1 px-3 rounded-full text-xs font-medium">
|
||||
<span className="bg-indigo-50 text-indigo-600 py-1 px-3 rounded-lg text-xs font-semibold">
|
||||
{category._count.products} Ürün
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-4 px-6 text-center">
|
||||
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
||||
category.status === 1 ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'
|
||||
<span className={`inline-flex items-center px-2.5 py-1 rounded-lg text-xs font-semibold ${
|
||||
category.status === 1 ? 'bg-emerald-50 text-emerald-600' : 'bg-slate-100 text-slate-500'
|
||||
}`}>
|
||||
{category.status === 1 ? 'Aktif' : 'Pasif'}
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-4 px-6 text-right">
|
||||
<div className="flex justify-end gap-2">
|
||||
<div className="flex justify-end gap-1 opacity-100 sm:opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<Link href={`/admin/categories/${category.id}/edit`} className="p-2 text-blue-600 hover:bg-blue-50 rounded-lg transition-colors" title="Düzenle">
|
||||
<Pencil size={18} />
|
||||
</Link>
|
||||
@@ -67,8 +70,8 @@ export default async function CategoriesPage() {
|
||||
))}
|
||||
{categories.length === 0 && (
|
||||
<tr>
|
||||
<td colSpan={5} className="py-8 text-center text-gray-500">
|
||||
Henüz kategori bulunmuyor.
|
||||
<td colSpan={5} className="py-12 text-center text-slate-500">
|
||||
Henüz kategori bulunmuyor. Yeni bir kategori ekleyerek başlayın.
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import Link from 'next/link'
|
||||
import { LayoutDashboard, LogOut, Package, Settings, Tags, Users2 } from 'lucide-react'
|
||||
import { logout } from '@/lib/auth'
|
||||
import { redirect } from 'next/navigation'
|
||||
import { Sidebar } from '@/components/admin/Sidebar'
|
||||
import { Header } from '@/components/admin/Header'
|
||||
|
||||
export default function AdminLayout({
|
||||
children,
|
||||
@@ -9,54 +7,18 @@ export default function AdminLayout({
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div className="flex h-screen bg-gray-100 overflow-hidden">
|
||||
{/* Sidebar */}
|
||||
<aside className="w-64 bg-white shadow-md flex flex-col hidden md:flex">
|
||||
<div className="p-4 border-b">
|
||||
<h2 className="text-xl font-bold text-gray-800">Admin Panel</h2>
|
||||
</div>
|
||||
<nav className="flex-1 p-4 space-y-2">
|
||||
<Link href="/admin" className="flex items-center gap-3 px-3 py-2 text-gray-700 rounded-lg hover:bg-gray-100 transition-colors">
|
||||
<LayoutDashboard size={20} />
|
||||
<span>Dashboard</span>
|
||||
</Link>
|
||||
<Link href="/admin/categories" className="flex items-center gap-3 px-3 py-2 text-gray-700 rounded-lg hover:bg-gray-100 transition-colors">
|
||||
<Tags size={20} />
|
||||
<span>Kategoriler</span>
|
||||
</Link>
|
||||
<Link href="/admin/products" className="flex items-center gap-3 px-3 py-2 text-gray-700 rounded-lg hover:bg-gray-100 transition-colors">
|
||||
<Package size={20} />
|
||||
<span>Ürünler</span>
|
||||
</Link>
|
||||
<Link href="/admin/settings" className="flex items-center gap-3 px-3 py-2 text-gray-700 rounded-lg hover:bg-gray-100 transition-colors">
|
||||
<Settings size={20} />
|
||||
<span>Ayarlar</span>
|
||||
</Link>
|
||||
<Link href="/admin/users" className="flex items-center gap-3 px-3 py-2 text-gray-700 rounded-lg hover:bg-gray-100 transition-colors">
|
||||
<Users2 size={20} />
|
||||
<span>Kullanıcılar</span>
|
||||
</Link>
|
||||
</nav>
|
||||
<div className="p-4 border-t">
|
||||
<form action={async () => {
|
||||
'use server'
|
||||
await logout()
|
||||
redirect('/admin/login')
|
||||
}}>
|
||||
<button className="flex items-center gap-3 w-full px-3 py-2 text-red-600 rounded-lg hover:bg-red-50 transition-colors">
|
||||
<LogOut size={20} />
|
||||
<span>Çıkış Yap</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="flex-1 overflow-y-auto bg-gray-50 p-6 md:p-8">
|
||||
{children}
|
||||
</main>
|
||||
|
||||
{/* Mobile Bottom Nav (Optional, simpler for now just a top bar) */}
|
||||
<div className="flex h-screen bg-[#F8FAFC] overflow-hidden selection:bg-blue-100 selection:text-blue-900">
|
||||
<Sidebar />
|
||||
|
||||
<div className="flex-1 flex flex-col min-w-0 overflow-hidden">
|
||||
<Header />
|
||||
|
||||
<main className="flex-1 overflow-y-auto p-6 md:p-8 lg:p-10">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
{children}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import prisma from '@/lib/prisma'
|
||||
import { Package, Tags, Eye, TrendingUp } from 'lucide-react'
|
||||
import { Package, Tags, Eye, TrendingUp, ChevronRight } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
|
||||
export const metadata = {
|
||||
@@ -14,55 +14,89 @@ export default async function AdminDashboard() {
|
||||
])
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<h1 className="text-2xl font-bold text-gray-900">Dashboard</h1>
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<h1 className="text-3xl font-extrabold text-slate-900 tracking-tight">Dashboard</h1>
|
||||
<p className="mt-2 text-slate-500">Moy Beach dijital menü yönetim paneline hoş geldiniz.</p>
|
||||
</div>
|
||||
|
||||
{/* Stats Cards */}
|
||||
<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 flex items-center gap-4">
|
||||
<div className="p-3 bg-blue-100 text-blue-600 rounded-lg">
|
||||
<Tags size={24} />
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div className="bg-white p-6 rounded-2xl shadow-[0_2px_10px_-3px_rgba(6,81,237,0.1)] border border-slate-100 flex items-center gap-5 transition-all duration-300 hover:shadow-[0_8px_30px_rgb(0,0,0,0.04)] hover:-translate-y-0.5 group">
|
||||
<div className="p-4 bg-gradient-to-br from-blue-50 to-blue-100/50 text-blue-600 rounded-xl group-hover:scale-110 transition-transform duration-300">
|
||||
<Tags size={28} strokeWidth={1.5} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500 font-medium">Kategori Sayısı</p>
|
||||
<p className="text-2xl font-bold text-gray-900">{totalCategories}</p>
|
||||
<p className="text-sm text-slate-500 font-medium">Kategori Sayısı</p>
|
||||
<p className="text-3xl font-bold text-slate-900 tracking-tight mt-1">{totalCategories}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-6 rounded-xl shadow-sm border border-gray-100 flex items-center gap-4">
|
||||
<div className="p-3 bg-indigo-100 text-indigo-600 rounded-lg">
|
||||
<Package size={24} />
|
||||
<div className="bg-white p-6 rounded-2xl shadow-[0_2px_10px_-3px_rgba(6,81,237,0.1)] border border-slate-100 flex items-center gap-5 transition-all duration-300 hover:shadow-[0_8px_30px_rgb(0,0,0,0.04)] hover:-translate-y-0.5 group">
|
||||
<div className="p-4 bg-gradient-to-br from-indigo-50 to-indigo-100/50 text-indigo-600 rounded-xl group-hover:scale-110 transition-transform duration-300">
|
||||
<Package size={28} strokeWidth={1.5} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500 font-medium">Toplam Ürün</p>
|
||||
<p className="text-2xl font-bold text-gray-900">{totalProducts}</p>
|
||||
<p className="text-sm text-slate-500 font-medium">Toplam Ürün</p>
|
||||
<p className="text-3xl font-bold text-slate-900 tracking-tight mt-1">{totalProducts}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-6 rounded-xl shadow-sm border border-gray-100 flex items-center gap-4">
|
||||
<div className="p-3 bg-green-100 text-green-600 rounded-lg">
|
||||
<TrendingUp size={24} />
|
||||
<div className="bg-white p-6 rounded-2xl shadow-[0_2px_10px_-3px_rgba(6,81,237,0.1)] border border-slate-100 flex items-center gap-5 transition-all duration-300 hover:shadow-[0_8px_30px_rgb(0,0,0,0.04)] hover:-translate-y-0.5 group">
|
||||
<div className="p-4 bg-gradient-to-br from-emerald-50 to-emerald-100/50 text-emerald-600 rounded-xl group-hover:scale-110 transition-transform duration-300">
|
||||
<TrendingUp size={28} strokeWidth={1.5} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500 font-medium">Aktif Ürünler</p>
|
||||
<p className="text-2xl font-bold text-gray-900">{activeProducts}</p>
|
||||
<p className="text-sm text-slate-500 font-medium">Aktif Ürünler</p>
|
||||
<p className="text-3xl font-bold text-slate-900 tracking-tight mt-1">{activeProducts}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick Actions */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="bg-white p-6 rounded-xl shadow-sm border border-gray-100">
|
||||
<h2 className="text-lg font-semibold mb-4">Hızlı Kısayollar</h2>
|
||||
<div className="flex flex-col gap-3">
|
||||
<Link href="/admin/products" className="text-blue-600 hover:text-blue-800 font-medium flex items-center gap-2">
|
||||
<Package size={18} /> Ürünleri Yönet
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<div className="bg-white rounded-2xl shadow-[0_2px_10px_-3px_rgba(6,81,237,0.1)] border border-slate-100 overflow-hidden">
|
||||
<div className="p-6 border-b border-slate-100">
|
||||
<h2 className="text-lg font-bold text-slate-800">Hızlı Kısayollar</h2>
|
||||
</div>
|
||||
<div className="p-3">
|
||||
<Link href="/admin/products" className="flex items-center justify-between p-4 hover:bg-slate-50 rounded-xl transition-colors group">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-2.5 bg-indigo-50 text-indigo-600 rounded-lg group-hover:bg-indigo-100 transition-colors">
|
||||
<Package size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold text-slate-800">Ürünleri Yönet</p>
|
||||
<p className="text-sm text-slate-500">Ürün ekle, düzenle veya fiyat güncelle</p>
|
||||
</div>
|
||||
</div>
|
||||
<ChevronRight size={20} className="text-slate-400 group-hover:text-indigo-600 transition-colors" />
|
||||
</Link>
|
||||
<Link href="/admin/categories" className="text-blue-600 hover:text-blue-800 font-medium flex items-center gap-2">
|
||||
<Tags size={18} /> Kategorileri Yönet
|
||||
|
||||
<Link href="/admin/categories" className="flex items-center justify-between p-4 hover:bg-slate-50 rounded-xl transition-colors group">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-2.5 bg-blue-50 text-blue-600 rounded-lg group-hover:bg-blue-100 transition-colors">
|
||||
<Tags size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold text-slate-800">Kategorileri Yönet</p>
|
||||
<p className="text-sm text-slate-500">Menü kategorilerini ve sıralamasını düzenle</p>
|
||||
</div>
|
||||
</div>
|
||||
<ChevronRight size={20} className="text-slate-400 group-hover:text-blue-600 transition-colors" />
|
||||
</Link>
|
||||
<Link href="/" target="_blank" className="text-gray-600 hover:text-gray-800 font-medium flex items-center gap-2">
|
||||
<Eye size={18} /> Canlı Menüyü Görüntüle
|
||||
|
||||
<Link href="/" target="_blank" className="flex items-center justify-between p-4 hover:bg-slate-50 rounded-xl transition-colors group">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-2.5 bg-emerald-50 text-emerald-600 rounded-lg group-hover:bg-emerald-100 transition-colors">
|
||||
<Eye size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold text-slate-800">Canlı Menüyü Görüntüle</p>
|
||||
<p className="text-sm text-slate-500">Müşterilerin gördüğü güncel menüyü aç</p>
|
||||
</div>
|
||||
</div>
|
||||
<ChevronRight size={20} className="text-slate-400 group-hover:text-emerald-600 transition-colors" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import prisma from '@/lib/prisma'
|
||||
import { Pencil, Plus, Trash2, Image as ImageIcon } from 'lucide-react'
|
||||
import { Pencil, Plus, Image as ImageIcon } from 'lucide-react'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import DeleteButton from './DeleteButton'
|
||||
@@ -19,41 +19,45 @@ export default async function ProductsPage() {
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<h1 className="text-2xl font-bold text-gray-900">Ürün Yönetimi</h1>
|
||||
<Link href="/admin/products/new" className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg flex items-center gap-2 transition-colors">
|
||||
<Plus size={20} />
|
||||
<span>Yeni Ürün Ekle</span>
|
||||
<div className="space-y-8">
|
||||
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-extrabold text-slate-900 tracking-tight">Ürün Yönetimi</h1>
|
||||
<p className="text-sm text-slate-500 mt-1">Tüm kategorilerdeki ürünlerinizi görüntüleyin ve düzenleyin.</p>
|
||||
</div>
|
||||
<Link href="/admin/products/new" className="bg-blue-600 hover:bg-blue-700 text-white px-5 py-2.5 rounded-xl font-medium flex items-center gap-2 transition-all shadow-sm hover:shadow-md hover:-translate-y-0.5">
|
||||
<Plus size={18} /> Yeni Ürün Ekle
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="space-y-8">
|
||||
{categories.map((category) => (
|
||||
<div key={category.id} className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
|
||||
<div className="bg-gray-50 px-6 py-4 border-b border-gray-100 flex justify-between items-center">
|
||||
<h2 className="text-lg font-bold text-gray-800">{category.name_tr}</h2>
|
||||
<span className="text-sm text-gray-500 font-medium">{category.products.length} ürün</span>
|
||||
<div key={category.id} className="bg-white rounded-2xl shadow-[0_2px_10px_-3px_rgba(6,81,237,0.1)] border border-slate-100 overflow-hidden">
|
||||
<div className="bg-slate-50/50 px-6 py-5 border-b border-slate-100 flex justify-between items-center">
|
||||
<h2 className="text-lg font-bold text-slate-800 tracking-tight">{category.name_tr}</h2>
|
||||
<span className="bg-white px-3 py-1 rounded-full text-xs font-semibold text-slate-500 border border-slate-200 shadow-sm">
|
||||
{category.products.length} ürün
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{category.products.length > 0 ? (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-left border-collapse">
|
||||
<thead>
|
||||
<tr className="border-b border-gray-100">
|
||||
<th className="py-3 px-6 text-sm font-semibold text-gray-600 w-16">Resim</th>
|
||||
<th className="py-3 px-6 text-sm font-semibold text-gray-600">İsim</th>
|
||||
<th className="py-3 px-6 text-sm font-semibold text-gray-600">Fiyat</th>
|
||||
<th className="py-3 px-6 text-sm font-semibold text-gray-600">Durum</th>
|
||||
<th className="py-3 px-6 text-sm font-semibold text-gray-600 text-right">İşlemler</th>
|
||||
<tr className="border-b border-slate-100 text-xs uppercase tracking-wider font-semibold text-slate-500">
|
||||
<th className="py-4 px-6 w-16">Resim</th>
|
||||
<th className="py-4 px-6">İsim</th>
|
||||
<th className="py-4 px-6">Fiyat</th>
|
||||
<th className="py-4 px-6 text-center">Durum</th>
|
||||
<th className="py-4 px-6 text-right">İşlemler</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-50">
|
||||
<tbody className="divide-y divide-slate-100/80">
|
||||
{category.products.map((product) => (
|
||||
<tr key={product.id} className="hover:bg-gray-50 transition-colors">
|
||||
<td className="py-3 px-6">
|
||||
<tr key={product.id} className="hover:bg-slate-50/50 transition-colors group">
|
||||
<td className="py-4 px-6">
|
||||
{product.image_url ? (
|
||||
<div className="relative w-12 h-12 rounded-lg overflow-hidden border border-gray-200">
|
||||
<div className="relative w-12 h-12 rounded-xl overflow-hidden shadow-sm border border-slate-200 group-hover:scale-105 transition-transform">
|
||||
<Image
|
||||
src={product.image_url}
|
||||
alt={product.name_tr}
|
||||
@@ -62,20 +66,20 @@ export default async function ProductsPage() {
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-12 h-12 rounded-lg bg-gray-100 flex items-center justify-center text-gray-400 border border-gray-200">
|
||||
<div className="w-12 h-12 rounded-xl bg-slate-100 flex items-center justify-center text-slate-400 border border-slate-200 shadow-sm">
|
||||
<ImageIcon size={20} />
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
<td className="py-3 px-6 font-medium text-gray-900">{product.name_tr}</td>
|
||||
<td className="py-3 px-6 text-gray-600">₺{product.price.toString()}</td>
|
||||
<td className="py-3 px-6">
|
||||
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${product.status === 1 ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}`}>
|
||||
<td className="py-4 px-6 font-semibold text-slate-800">{product.name_tr}</td>
|
||||
<td className="py-4 px-6 font-medium text-slate-600">₺{product.price.toString()}</td>
|
||||
<td className="py-4 px-6 text-center">
|
||||
<span className={`inline-flex items-center px-2.5 py-1 rounded-lg text-xs font-semibold ${product.status === 1 ? 'bg-emerald-50 text-emerald-600' : 'bg-slate-100 text-slate-500'}`}>
|
||||
{product.status === 1 ? 'Aktif' : 'Pasif'}
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-3 px-6 text-right">
|
||||
<div className="flex justify-end gap-2">
|
||||
<td className="py-4 px-6 text-right">
|
||||
<div className="flex justify-end gap-1 opacity-100 sm:opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<Link href={`/admin/products/${product.id}/edit`} className="p-2 text-blue-600 hover:bg-blue-50 rounded-lg transition-colors" title="Düzenle">
|
||||
<Pencil size={18} />
|
||||
</Link>
|
||||
@@ -88,8 +92,8 @@ export default async function ProductsPage() {
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-6 px-6 text-gray-500 text-sm">
|
||||
Bu kategoride henüz ürün yok.
|
||||
<div className="py-10 px-6 text-center text-slate-500 text-sm">
|
||||
Bu kategoride henüz ürün bulunmuyor.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useActionState, useState, useRef } from 'react'
|
||||
import { saveSettings } from './actions'
|
||||
import Image from 'next/image'
|
||||
import { Upload, X, Loader2 } from 'lucide-react'
|
||||
import { Upload, X, Loader2, Save } from 'lucide-react'
|
||||
|
||||
export default function SettingsForm({
|
||||
defaultRestaurantName,
|
||||
@@ -49,62 +49,62 @@ export default function SettingsForm({
|
||||
}
|
||||
|
||||
return (
|
||||
<form action={action} className="space-y-6">
|
||||
<form action={action} className="space-y-8">
|
||||
{state?.success && (
|
||||
<div className="bg-green-50 text-green-700 p-3 rounded-lg text-sm">
|
||||
<div className="bg-emerald-50 text-emerald-700 px-4 py-3 rounded-xl text-sm font-medium border border-emerald-100/50">
|
||||
{state.message}
|
||||
</div>
|
||||
)}
|
||||
{state?.error && (
|
||||
<div className="bg-red-50 text-red-700 p-3 rounded-lg text-sm">
|
||||
<div className="bg-red-50 text-red-700 px-4 py-3 rounded-xl text-sm font-medium border border-red-100/50">
|
||||
{state.error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Logo Upload */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label className="block text-sm font-semibold text-slate-700 mb-3">
|
||||
Site Logosu
|
||||
</label>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-6">
|
||||
{/* Preview */}
|
||||
<div className="relative w-40 h-20 rounded-xl overflow-hidden border-2 border-gray-200 bg-stone-900 flex items-center justify-center flex-shrink-0">
|
||||
<div className="relative w-48 h-24 rounded-2xl overflow-hidden border border-slate-200 bg-slate-900 shadow-inner flex items-center justify-center flex-shrink-0 group">
|
||||
{logoUrl ? (
|
||||
<>
|
||||
<Image
|
||||
src={logoUrl}
|
||||
alt="Logo önizleme"
|
||||
fill
|
||||
className="object-contain p-2"
|
||||
className="object-contain p-3"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => { setLogoUrl(''); if (fileInputRef.current) fileInputRef.current.value = '' }}
|
||||
className="absolute top-1 right-1 bg-red-500 text-white rounded-full p-0.5 hover:bg-red-600 transition-colors"
|
||||
className="absolute top-2 right-2 bg-red-500/90 text-white rounded-full p-1 hover:bg-red-600 transition-colors opacity-0 group-hover:opacity-100 backdrop-blur-sm shadow-sm"
|
||||
>
|
||||
<X size={12} />
|
||||
<X size={14} />
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<span className="text-xs text-gray-400 text-center px-2">Logo yok<br />(varsayılan kullanılır)</span>
|
||||
<span className="text-xs text-slate-400 text-center px-4 font-medium">Logo yok<br />(varsayılan kullanılır)</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Upload button */}
|
||||
<div className="flex flex-col gap-2 justify-center h-20">
|
||||
<div className="flex flex-col gap-2.5 justify-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
disabled={uploading}
|
||||
className="flex items-center gap-2 px-4 py-2 border-2 border-dashed border-gray-300 rounded-lg text-sm text-gray-600 hover:border-blue-400 hover:text-blue-600 transition-colors disabled:opacity-50"
|
||||
className="flex items-center gap-2 px-5 py-2.5 border-2 border-dashed border-slate-300 rounded-xl text-sm font-medium text-slate-600 hover:border-blue-400 hover:text-blue-600 transition-colors disabled:opacity-50 hover:bg-blue-50/50"
|
||||
>
|
||||
{uploading
|
||||
? <><Loader2 size={16} className="animate-spin" /> Yükleniyor...</>
|
||||
: <><Upload size={16} /> Logo Yükle</>
|
||||
? <><Loader2 size={18} className="animate-spin" /> Yükleniyor...</>
|
||||
: <><Upload size={18} /> Yeni Logo Seç</>
|
||||
}
|
||||
</button>
|
||||
<p className="text-xs text-gray-500">PNG, SVG, WEBP — Maks. 5MB</p>
|
||||
{uploadError && <p className="text-xs text-red-600">{uploadError}</p>}
|
||||
<p className="text-xs text-slate-400 font-medium ml-1">Önerilen format: Şeffaf arka planlı PNG veya SVG (Maks. 5MB)</p>
|
||||
{uploadError && <p className="text-xs text-red-600 font-medium ml-1">{uploadError}</p>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -118,39 +118,41 @@ export default function SettingsForm({
|
||||
<input type="hidden" name="logo_url" value={logoUrl} />
|
||||
</div>
|
||||
|
||||
<hr className="border-gray-100" />
|
||||
<hr className="border-slate-100" />
|
||||
|
||||
{/* Restaurant Name */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Restoran / Mekan Adı
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="restaurant_name"
|
||||
defaultValue={defaultRestaurantName}
|
||||
required
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-colors"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Restaurant Name */}
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-slate-700 mb-1.5">
|
||||
Restoran / Mekan Adı
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="restaurant_name"
|
||||
defaultValue={defaultRestaurantName}
|
||||
required
|
||||
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 focus:bg-white outline-none text-sm transition-all"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Location */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Konum Bilgisi
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="location"
|
||||
defaultValue={defaultLocation}
|
||||
required
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-colors"
|
||||
/>
|
||||
{/* Location */}
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-slate-700 mb-1.5">
|
||||
Konum Bilgisi
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="location"
|
||||
defaultValue={defaultLocation}
|
||||
required
|
||||
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 focus:bg-white outline-none text-sm transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Google Review Url */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
<label className="block text-sm font-semibold text-slate-700 mb-1.5">
|
||||
Google Yorum Linki
|
||||
</label>
|
||||
<input
|
||||
@@ -158,18 +160,21 @@ export default function SettingsForm({
|
||||
name="google_review_url"
|
||||
defaultValue={defaultGoogleReviewUrl}
|
||||
placeholder="https://g.page/r/..."
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-colors"
|
||||
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 focus:bg-white outline-none text-sm transition-all"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-1">Eğer buraya link eklerseniz site açılışında ziyaretçilere yorum yapmaları için popup gösterilir.</p>
|
||||
<p className="text-xs text-slate-500 mt-2 font-medium ml-1">Eğer buraya link eklerseniz, ziyaretçilere her 30 dakikada bir işletmenizi değerlendirmelerini isteyen bir açılır pencere (popup) gösterilir.</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isPending || uploading}
|
||||
className="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-6 rounded-lg transition-colors disabled:opacity-70"
|
||||
>
|
||||
{isPending ? 'Kaydediliyor...' : 'Kaydet'}
|
||||
</button>
|
||||
<div className="pt-4 flex justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isPending || uploading}
|
||||
className="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2.5 px-8 rounded-xl transition-all shadow-sm hover:shadow hover:-translate-y-0.5 disabled:opacity-70 disabled:hover:translate-y-0 disabled:shadow-none flex items-center gap-2"
|
||||
>
|
||||
<Save size={18} />
|
||||
{isPending ? 'Kaydediliyor...' : 'Değişiklikleri Kaydet'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import prisma from '@/lib/prisma'
|
||||
import SettingsForm from './SettingsForm'
|
||||
import { Settings as SettingsIcon } from 'lucide-react'
|
||||
|
||||
export const metadata = {
|
||||
title: 'Genel Ayarlar - Admin',
|
||||
@@ -18,10 +19,18 @@ export default async function SettingsPage() {
|
||||
const googleReviewUrl = settings['google_review_url'] || ''
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<h1 className="text-2xl font-bold text-gray-900">Genel Ayarlar</h1>
|
||||
<div className="space-y-8">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 bg-blue-600 rounded-xl flex items-center justify-center text-white shadow-lg shadow-blue-600/20">
|
||||
<SettingsIcon size={24} />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-extrabold text-slate-900 tracking-tight">Genel Ayarlar</h1>
|
||||
<p className="text-sm text-slate-500 mt-1">Sitenizin temel bilgilerini, logo ve bağlantılarını yapılandırın.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-6 rounded-xl shadow-sm border border-gray-100 max-w-2xl">
|
||||
<div className="bg-white p-6 md:p-8 rounded-2xl shadow-[0_2px_10px_-3px_rgba(6,81,237,0.1)] border border-slate-100 max-w-3xl">
|
||||
<SettingsForm
|
||||
defaultRestaurantName={restaurantName}
|
||||
defaultLocation={location}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { PageTransition } from '@/components/ui/PageTransition'
|
||||
|
||||
export default function Template({ children }: { children: React.ReactNode }) {
|
||||
return <PageTransition>{children}</PageTransition>
|
||||
}
|
||||
@@ -8,63 +8,70 @@ export default function CreateUserForm() {
|
||||
const [state, formAction, isPending] = useActionState(createUser, undefined)
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<UserPlus size={20} className="text-blue-600" />
|
||||
<h2 className="text-lg font-semibold text-gray-800">Yeni Kullanıcı Ekle</h2>
|
||||
<div className="bg-white rounded-2xl shadow-[0_2px_10px_-3px_rgba(6,81,237,0.1)] border border-slate-100 p-6 md:p-8">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="p-2 bg-blue-50 text-blue-600 rounded-lg">
|
||||
<UserPlus size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-lg font-bold text-slate-800 tracking-tight">Yeni Kullanıcı Ekle</h2>
|
||||
<p className="text-xs text-slate-500">Sisteme yeni bir yönetici veya editör ekleyin.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{state?.success && (
|
||||
<div className="bg-green-50 text-green-700 p-3 rounded-lg text-sm mb-4">
|
||||
<div className="bg-emerald-50 text-emerald-700 px-4 py-3 rounded-xl text-sm font-medium mb-6 border border-emerald-100/50">
|
||||
Kullanıcı başarıyla oluşturuldu.
|
||||
</div>
|
||||
)}
|
||||
{state?.error && (
|
||||
<div className="bg-red-50 text-red-700 p-3 rounded-lg text-sm mb-4">
|
||||
<div className="bg-red-50 text-red-700 px-4 py-3 rounded-xl text-sm font-medium mb-6 border border-red-100/50">
|
||||
{state.error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form action={formAction} className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||
<form action={formAction} className="grid grid-cols-1 sm:grid-cols-3 gap-5">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Kullanıcı Adı *</label>
|
||||
<label className="block text-sm font-semibold text-slate-700 mb-1.5">Kullanıcı Adı <span className="text-red-500">*</span></label>
|
||||
<input
|
||||
type="text"
|
||||
name="username"
|
||||
required
|
||||
autoComplete="off"
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none text-sm"
|
||||
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 focus:bg-white outline-none text-sm transition-all"
|
||||
placeholder="örn: ahmet"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Şifre * <span className="text-gray-400 font-normal">(min. 6 karakter)</span></label>
|
||||
<label className="block text-sm font-semibold text-slate-700 mb-1.5">Şifre <span className="text-red-500">*</span> <span className="text-slate-400 font-normal">(min. 6 karakter)</span></label>
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
required
|
||||
autoComplete="new-password"
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none text-sm"
|
||||
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 focus:bg-white outline-none text-sm transition-all"
|
||||
placeholder="••••••"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Rol</label>
|
||||
<label className="block text-sm font-semibold text-slate-700 mb-1.5">Rol</label>
|
||||
<select
|
||||
name="role"
|
||||
defaultValue="admin"
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none text-sm"
|
||||
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 focus:bg-white outline-none text-sm transition-all appearance-none"
|
||||
>
|
||||
<option value="admin">Admin</option>
|
||||
<option value="editor">Editor</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="sm:col-span-3 flex justify-end">
|
||||
<div className="sm:col-span-3 flex justify-end mt-2">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
className="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-5 rounded-lg text-sm transition-colors disabled:opacity-70 flex items-center gap-2"
|
||||
className="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2.5 px-6 rounded-xl text-sm transition-all shadow-sm hover:shadow-md hover:-translate-y-0.5 disabled:opacity-70 disabled:hover:translate-y-0 disabled:hover:shadow-sm flex items-center gap-2"
|
||||
>
|
||||
<UserPlus size={16} />
|
||||
<UserPlus size={18} />
|
||||
{isPending ? 'Oluşturuluyor...' : 'Kullanıcı Oluştur'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useActionState, useState } from 'react'
|
||||
import { changePassword, deleteUser } from './actions'
|
||||
import { KeyRound, Trash2, ChevronDown, ChevronUp, Shield } from 'lucide-react'
|
||||
import { KeyRound, Trash2, ChevronDown, ChevronUp, Shield, User as UserIcon } from 'lucide-react'
|
||||
|
||||
type User = {
|
||||
id: number
|
||||
@@ -16,44 +16,44 @@ function ChangePasswordForm({ userId }: { userId: number }) {
|
||||
const [state, formAction, isPending] = useActionState(action, undefined)
|
||||
|
||||
return (
|
||||
<form action={formAction} className="mt-3 p-4 bg-gray-50 rounded-lg border border-gray-200 space-y-3">
|
||||
<form action={formAction} className="p-5 bg-slate-50/80 border-t border-slate-100 space-y-4">
|
||||
{state?.success && (
|
||||
<p className="text-xs text-green-700 bg-green-50 px-3 py-2 rounded-lg">{state.message}</p>
|
||||
<p className="text-sm font-medium text-emerald-700 bg-emerald-50 px-4 py-2.5 rounded-xl border border-emerald-100">{state.message}</p>
|
||||
)}
|
||||
{state?.error && (
|
||||
<p className="text-xs text-red-700 bg-red-50 px-3 py-2 rounded-lg">{state.error}</p>
|
||||
<p className="text-sm font-medium text-red-700 bg-red-50 px-4 py-2.5 rounded-xl border border-red-100">{state.error}</p>
|
||||
)}
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-600 mb-1">Yeni Şifre</label>
|
||||
<label className="block text-xs font-semibold text-slate-600 mb-1.5 uppercase tracking-wider">Yeni Şifre</label>
|
||||
<input
|
||||
type="password"
|
||||
name="new_password"
|
||||
required
|
||||
autoComplete="new-password"
|
||||
placeholder="Min. 6 karakter"
|
||||
className="w-full px-3 py-1.5 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 outline-none"
|
||||
className="w-full px-3 py-2 bg-white border border-slate-200 rounded-xl text-sm focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 outline-none transition-all"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-600 mb-1">Şifre Tekrar</label>
|
||||
<label className="block text-xs font-semibold text-slate-600 mb-1.5 uppercase tracking-wider">Şifre Tekrar</label>
|
||||
<input
|
||||
type="password"
|
||||
name="confirm_password"
|
||||
required
|
||||
autoComplete="new-password"
|
||||
placeholder="Tekrar giriniz"
|
||||
className="w-full px-3 py-1.5 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 outline-none"
|
||||
className="w-full px-3 py-2 bg-white border border-slate-200 rounded-xl text-sm focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 outline-none transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<div className="flex justify-end pt-1">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
className="bg-amber-500 hover:bg-amber-600 text-white text-sm font-medium py-1.5 px-4 rounded-lg transition-colors disabled:opacity-70 flex items-center gap-1.5"
|
||||
className="bg-amber-500 hover:bg-amber-600 text-white text-sm font-medium py-2 px-5 rounded-xl transition-all shadow-sm hover:shadow hover:-translate-y-0.5 disabled:opacity-70 disabled:hover:translate-y-0 flex items-center gap-2"
|
||||
>
|
||||
<KeyRound size={14} />
|
||||
<KeyRound size={16} />
|
||||
{isPending ? 'Güncelleniyor...' : 'Şifreyi Güncelle'}
|
||||
</button>
|
||||
</div>
|
||||
@@ -71,43 +71,49 @@ function UserRow({ user }: { user: User }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="border border-gray-100 rounded-xl overflow-hidden">
|
||||
<div className="flex items-center justify-between px-5 py-4 hover:bg-gray-50 transition-colors">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-9 h-9 rounded-full bg-blue-100 flex items-center justify-center text-blue-700 font-semibold text-sm uppercase">
|
||||
<div className="border border-slate-100 rounded-2xl overflow-hidden bg-white hover:border-slate-200 hover:shadow-[0_2px_15px_-3px_rgba(0,0,0,0.05)] transition-all duration-300">
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between p-4 sm:px-6 sm:py-5 gap-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-indigo-50 to-blue-50 flex items-center justify-center text-indigo-600 font-bold text-lg uppercase shadow-sm border border-indigo-100/50">
|
||||
{user.username[0]}
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-gray-900 text-sm">{user.username}</p>
|
||||
<p className="text-xs text-gray-400">
|
||||
{new Date(user.created_at).toLocaleDateString('tr-TR', { day: 'numeric', month: 'long', year: 'numeric' })}
|
||||
<p className="font-bold text-slate-800 text-base">{user.username}</p>
|
||||
<p className="text-xs font-medium text-slate-400 mt-0.5">
|
||||
Katılım: {new Date(user.created_at).toLocaleDateString('tr-TR', { day: 'numeric', month: 'long', year: 'numeric' })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
||||
user.role === 'admin' ? 'bg-purple-100 text-purple-700' : 'bg-gray-100 text-gray-600'
|
||||
<div className="flex flex-wrap items-center gap-2 sm:gap-3 w-full sm:w-auto">
|
||||
<span className={`inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-bold uppercase tracking-wider ${
|
||||
user.role === 'admin' ? 'bg-purple-50 text-purple-600 border border-purple-100' : 'bg-slate-50 text-slate-500 border border-slate-100'
|
||||
}`}>
|
||||
<Shield size={10} />
|
||||
<Shield size={14} />
|
||||
{user.role}
|
||||
</span>
|
||||
|
||||
<div className="flex-1 sm:hidden"></div>
|
||||
|
||||
<button
|
||||
onClick={() => setExpanded(v => !v)}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 text-xs text-amber-600 bg-amber-50 hover:bg-amber-100 rounded-lg transition-colors font-medium"
|
||||
className={`flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-xl transition-all ${
|
||||
expanded
|
||||
? 'bg-amber-100 text-amber-700 shadow-inner'
|
||||
: 'text-amber-600 bg-amber-50 hover:bg-amber-100'
|
||||
}`}
|
||||
>
|
||||
<KeyRound size={13} />
|
||||
Şifre
|
||||
{expanded ? <ChevronUp size={13} /> : <ChevronDown size={13} />}
|
||||
<KeyRound size={16} />
|
||||
<span className="hidden sm:inline">Şifre İşlemleri</span>
|
||||
{expanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={handleDelete}
|
||||
className="p-2 text-red-500 hover:bg-red-50 rounded-lg transition-colors"
|
||||
className="p-2 text-red-500 hover:text-red-700 hover:bg-red-50 rounded-xl transition-colors"
|
||||
title="Kullanıcıyı Sil"
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
<Trash2 size={18} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -119,12 +125,27 @@ function UserRow({ user }: { user: User }) {
|
||||
|
||||
export default function UserList({ users }: { users: User[] }) {
|
||||
return (
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
||||
<h2 className="text-lg font-semibold text-gray-800 mb-4">Mevcut Kullanıcılar ({users.length})</h2>
|
||||
<div className="bg-white rounded-2xl shadow-[0_2px_10px_-3px_rgba(6,81,237,0.1)] border border-slate-100 p-6 md:p-8">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="p-2 bg-indigo-50 text-indigo-600 rounded-lg">
|
||||
<UserIcon size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-lg font-bold text-slate-800 tracking-tight">Sistem Kullanıcıları</h2>
|
||||
<p className="text-xs text-slate-500">Toplam {users.length} kullanıcı kayıtlı.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{users.length === 0 ? (
|
||||
<p className="text-sm text-gray-500 text-center py-6">Henüz kullanıcı yok.</p>
|
||||
<div className="text-center py-12 px-4 border-2 border-dashed border-slate-200 rounded-2xl">
|
||||
<div className="w-12 h-12 bg-slate-50 text-slate-400 rounded-xl flex items-center justify-center mx-auto mb-3">
|
||||
<UserIcon size={24} />
|
||||
</div>
|
||||
<p className="text-sm font-medium text-slate-600">Henüz kullanıcı yok.</p>
|
||||
<p className="text-xs text-slate-400 mt-1">Sisteme giriş yapabilmek için yukarıdan yeni bir kullanıcı ekleyin.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-4">
|
||||
{users.map(user => (
|
||||
<UserRow key={user.id} user={user} />
|
||||
))}
|
||||
|
||||
@@ -18,8 +18,11 @@ export default async function UsersPage() {
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<h1 className="text-2xl font-bold text-gray-900">Kullanıcı Yönetimi</h1>
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<h1 className="text-2xl font-extrabold text-slate-900 tracking-tight">Kullanıcı Yönetimi</h1>
|
||||
<p className="text-sm text-slate-500 mt-1">Admin paneline erişebilecek yetkili kullanıcıları yönetin.</p>
|
||||
</div>
|
||||
<CreateUserForm />
|
||||
<UserList users={users} />
|
||||
</div>
|
||||
|
||||
+8
-14
@@ -1,5 +1,6 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import cloudinary from '@/lib/cloudinary'
|
||||
import { uploadToOpeninary } from '@/lib/openinary'
|
||||
import { optimizedImage } from '@/lib/openinary-url'
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
@@ -10,21 +11,14 @@ export async function POST(request: NextRequest) {
|
||||
return NextResponse.json({ error: 'Dosya bulunamadı.' }, { status: 400 })
|
||||
}
|
||||
|
||||
// Convert file to buffer
|
||||
const arrayBuffer = await file.arrayBuffer()
|
||||
const buffer = Buffer.from(arrayBuffer)
|
||||
const base64 = `data:${file.type};base64,${buffer.toString('base64')}`
|
||||
// Upload to Openinary
|
||||
const result = await uploadToOpeninary(file, 'moyqr');
|
||||
|
||||
// Upload to Cloudinary
|
||||
const result = await cloudinary.uploader.upload(base64, {
|
||||
folder: 'moy-qr/products',
|
||||
transformation: [
|
||||
{ width: 800, height: 800, crop: 'limit' },
|
||||
{ quality: 'auto', fetch_format: 'auto' }
|
||||
]
|
||||
})
|
||||
// Generate optimized URL
|
||||
// e.g. "w_800,h_800,c_limit,q_auto,f_auto" parameters format for Openinary
|
||||
const optimizedUrl = optimizedImage(result.path, 'w_800,h_800,c_limit,q_auto,f_auto');
|
||||
|
||||
return NextResponse.json({ url: result.secure_url })
|
||||
return NextResponse.json({ url: optimizedUrl })
|
||||
} catch (error) {
|
||||
console.error('Upload error:', error)
|
||||
return NextResponse.json({ error: 'Yükleme başarısız oldu.' }, { status: 500 })
|
||||
|
||||
Reference in New Issue
Block a user