first commit
This commit is contained in:
401
app/admin/page.tsx
Normal file
401
app/admin/page.tsx
Normal file
@@ -0,0 +1,401 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Swal from "sweetalert2";
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
name: string | null;
|
||||
email: string;
|
||||
role: string;
|
||||
emailVerified: boolean;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
interface Session {
|
||||
user: {
|
||||
id: string;
|
||||
email: string;
|
||||
name?: string;
|
||||
role?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export default function AdminPanel() {
|
||||
const router = useRouter();
|
||||
const [users, setUsers] = useState<User[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [currentUserRole, setCurrentUserRole] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
checkAuth();
|
||||
}, []);
|
||||
|
||||
const checkAuth = async () => {
|
||||
try {
|
||||
const res = await fetch("/api/auth/get-session", {
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
router.push("/login");
|
||||
return;
|
||||
}
|
||||
|
||||
const session: Session = await res.json();
|
||||
|
||||
if (!session.user || session.user.role !== "admin") {
|
||||
await Swal.fire({
|
||||
icon: "error",
|
||||
title: "Erişim Engellendi",
|
||||
text: "Bu sayfaya erişim yetkiniz yok. Sadece adminler görebilir.",
|
||||
confirmButtonColor: "#3b82f6",
|
||||
});
|
||||
router.push("/");
|
||||
return;
|
||||
}
|
||||
|
||||
setCurrentUserRole(session.user.role || "user");
|
||||
await fetchUsers();
|
||||
} catch (err) {
|
||||
router.push("/login");
|
||||
}
|
||||
};
|
||||
|
||||
const fetchUsers = async () => {
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const res = await fetch("/api/admin/users", {
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
if (res.status === 401) {
|
||||
router.push("/login");
|
||||
return;
|
||||
}
|
||||
if (res.status === 403) {
|
||||
await Swal.fire({
|
||||
icon: "error",
|
||||
title: "Yetki Hatası",
|
||||
text: "Bu sayfaya erişim yetkiniz yok.",
|
||||
confirmButtonColor: "#3b82f6",
|
||||
});
|
||||
return;
|
||||
}
|
||||
throw new Error("Kullanıcılar yüklenemedi");
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
setUsers(data.data.users);
|
||||
} catch (err: any) {
|
||||
await Swal.fire({
|
||||
icon: "error",
|
||||
title: "Hata",
|
||||
text: err.message,
|
||||
confirmButtonColor: "#3b82f6",
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const changeRole = async (userId: string, newRole: string, currentRole: string) => {
|
||||
const result = await Swal.fire({
|
||||
title: "Rol Değiştir",
|
||||
text: `Bu kullanıcının rolünü "${currentRole}" → "${newRole}" olarak değiştirmek istediğinizden emin misiniz?`,
|
||||
icon: "question",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#3b82f6",
|
||||
cancelButtonColor: "#6b7280",
|
||||
confirmButtonText: "Evet, değiştir",
|
||||
cancelButtonText: "İptal",
|
||||
});
|
||||
|
||||
if (!result.isConfirmed) {
|
||||
await fetchUsers();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/admin/users/${userId}/role`, {
|
||||
method: "PATCH",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ role: newRole }),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const data = await res.json();
|
||||
throw new Error(data.error || "Rol güncellenemedi");
|
||||
}
|
||||
|
||||
await Swal.fire({
|
||||
icon: "success",
|
||||
title: "Başarılı!",
|
||||
text: "Kullanıcı rolü güncellendi",
|
||||
timer: 2000,
|
||||
showConfirmButton: false,
|
||||
});
|
||||
|
||||
await fetchUsers();
|
||||
} catch (err: any) {
|
||||
await Swal.fire({
|
||||
icon: "error",
|
||||
title: "Hata",
|
||||
text: err.message,
|
||||
confirmButtonColor: "#3b82f6",
|
||||
});
|
||||
await fetchUsers();
|
||||
}
|
||||
};
|
||||
|
||||
const toggleEmailVerification = async (userId: string, currentStatus: boolean, email: string) => {
|
||||
const newStatus = !currentStatus;
|
||||
const result = await Swal.fire({
|
||||
title: "Email Doğrulama",
|
||||
text: `${email} için email doğrulamasını ${newStatus ? "aktif" : "pasif"} yapmak istiyor musunuz?`,
|
||||
icon: "question",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#3b82f6",
|
||||
cancelButtonColor: "#6b7280",
|
||||
confirmButtonText: "Evet, değiştir",
|
||||
cancelButtonText: "İptal",
|
||||
});
|
||||
|
||||
if (!result.isConfirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/admin/users/${userId}/verification`, {
|
||||
method: "PATCH",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ emailVerified: newStatus }),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const data = await res.json();
|
||||
throw new Error(data.error || "Doğrulama güncellenemedi");
|
||||
}
|
||||
|
||||
await Swal.fire({
|
||||
icon: "success",
|
||||
title: "Başarılı!",
|
||||
text: `Email doğrulama ${newStatus ? "aktif edildi" : "pasif edildi"}`,
|
||||
timer: 2000,
|
||||
showConfirmButton: false,
|
||||
});
|
||||
|
||||
await fetchUsers();
|
||||
} catch (err: any) {
|
||||
await Swal.fire({
|
||||
icon: "error",
|
||||
title: "Hata",
|
||||
text: err.message,
|
||||
confirmButtonColor: "#3b82f6",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const deleteUser = async (userId: string, email: string) => {
|
||||
const result = await Swal.fire({
|
||||
title: "Kullanıcıyı Sil",
|
||||
html: `<b>${email}</b> kullanıcısını silmek istediğinizden emin misiniz?<br><br>
|
||||
<span style="color: #ef4444; font-weight: 600;">⚠️ Bu işlem geri alınamaz!</span><br>
|
||||
<small>Kullanıcının tüm resimleri ve verileri silinecek.</small>`,
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#ef4444",
|
||||
cancelButtonColor: "#6b7280",
|
||||
confirmButtonText: "Evet, sil!",
|
||||
cancelButtonText: "İptal",
|
||||
});
|
||||
|
||||
if (!result.isConfirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/admin/users/${userId}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const data = await res.json();
|
||||
throw new Error(data.error || "Kullanıcı silinemedi");
|
||||
}
|
||||
|
||||
await Swal.fire({
|
||||
icon: "success",
|
||||
title: "Silindi!",
|
||||
text: "Kullanıcı başarıyla silindi",
|
||||
timer: 2000,
|
||||
showConfirmButton: false,
|
||||
});
|
||||
|
||||
await fetchUsers();
|
||||
} catch (err: any) {
|
||||
await Swal.fire({
|
||||
icon: "error",
|
||||
title: "Hata",
|
||||
text: err.message,
|
||||
confirmButtonColor: "#3b82f6",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getRoleBadgeColor = (role: string) => {
|
||||
switch (role) {
|
||||
case "admin":
|
||||
return "bg-red-100 text-red-800 border-red-200";
|
||||
case "moderator":
|
||||
return "bg-blue-100 text-blue-800 border-blue-200";
|
||||
default:
|
||||
return "bg-gray-100 text-gray-800 border-gray-200";
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-100 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto"></div>
|
||||
<p className="mt-4 text-gray-600">Yükleniyor...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-100 p-8">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-4xl font-bold text-gray-900 mb-2">Admin Panel</h1>
|
||||
<p className="text-gray-600">Kullanıcı yönetimi ve rol atama</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-2xl shadow-xl overflow-hidden border border-gray-100">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead className="bg-gradient-to-r from-blue-600 to-indigo-600 text-white">
|
||||
<tr>
|
||||
<th className="px-6 py-4 text-left text-sm font-semibold">Kullanıcı</th>
|
||||
<th className="px-6 py-4 text-left text-sm font-semibold">Email</th>
|
||||
<th className="px-6 py-4 text-left text-sm font-semibold">Rol</th>
|
||||
<th className="px-6 py-4 text-left text-sm font-semibold">Doğrulama</th>
|
||||
<th className="px-6 py-4 text-left text-sm font-semibold">Kayıt Tarihi</th>
|
||||
<th className="px-6 py-4 text-left text-sm font-semibold">İşlemler</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-200">
|
||||
{users.map((user) => (
|
||||
<tr key={user.id} className="hover:bg-gray-50 transition-colors">
|
||||
<td className="px-6 py-4">
|
||||
<div className="font-medium text-gray-900">
|
||||
{user.name || "İsimsiz"}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">{user.id.substring(0, 8)}</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 text-gray-700">{user.email}</td>
|
||||
<td className="px-6 py-4">
|
||||
<select
|
||||
value={user.role}
|
||||
onChange={(e) => changeRole(user.id, e.target.value, user.role)}
|
||||
className={`px-3 py-1 rounded-full text-sm font-medium border ${getRoleBadgeColor(
|
||||
user.role
|
||||
)} focus:outline-none focus:ring-2 focus:ring-blue-500 cursor-pointer transition-all hover:shadow-md`}
|
||||
>
|
||||
<option value="user">User</option>
|
||||
<option value="moderator">Moderator</option>
|
||||
<option value="admin">Admin</option>
|
||||
</select>
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<button
|
||||
onClick={() => toggleEmailVerification(user.id, user.emailVerified, user.email)}
|
||||
className={`inline-flex items-center gap-1 px-3 py-1.5 rounded-full text-xs font-medium transition-all cursor-pointer hover:shadow-md ${
|
||||
user.emailVerified
|
||||
? "bg-green-100 text-green-700 hover:bg-green-200"
|
||||
: "bg-yellow-100 text-yellow-700 hover:bg-yellow-200"
|
||||
}`}
|
||||
>
|
||||
{user.emailVerified ? (
|
||||
<>
|
||||
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
Doğrulandı
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
Beklemede
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</td>
|
||||
<td className="px-6 py-4 text-sm text-gray-600">
|
||||
{new Date(user.createdAt).toLocaleDateString("tr-TR")}
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<button
|
||||
onClick={() => deleteUser(user.id, user.email)}
|
||||
className="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg text-sm font-medium transition-colors"
|
||||
>
|
||||
Sil
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||
<div className="bg-white p-6 rounded-xl shadow-lg border border-gray-100">
|
||||
<h3 className="text-sm font-medium text-gray-600 mb-2">Toplam Kullanıcı</h3>
|
||||
<p className="text-3xl font-bold text-gray-900">{users.length}</p>
|
||||
</div>
|
||||
<div className="bg-white p-6 rounded-xl shadow-lg border border-gray-100">
|
||||
<h3 className="text-sm font-medium text-gray-600 mb-2">Admin Sayısı</h3>
|
||||
<p className="text-3xl font-bold text-red-600">
|
||||
{users.filter((u) => u.role === "admin").length}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-white p-6 rounded-xl shadow-lg border border-gray-100">
|
||||
<h3 className="text-sm font-medium text-gray-600 mb-2">Moderatör Sayısı</h3>
|
||||
<p className="text-3xl font-bold text-blue-600">
|
||||
{users.filter((u) => u.role === "moderator").length}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-white p-6 rounded-xl shadow-lg border border-gray-100">
|
||||
<h3 className="text-sm font-medium text-gray-600 mb-2">Doğrulananlar</h3>
|
||||
<p className="text-3xl font-bold text-green-600">
|
||||
{users.filter((u) => u.emailVerified).length}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user