first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 22:09:32 +03:00
commit 71eff2d979
78 changed files with 10173 additions and 0 deletions

401
app/admin/page.tsx Normal file
View 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>
);
}