feat: setup database-driven admin user management
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { LayoutDashboard, Settings, Image as ImageIcon, Compass, CalendarDays, Star, Info } from "lucide-react";
|
||||
import { LayoutDashboard, Settings, Image as ImageIcon, Compass, CalendarDays, Star, Info, Users } from "lucide-react";
|
||||
|
||||
export default function SidebarNav() {
|
||||
const pathname = usePathname();
|
||||
@@ -15,6 +15,7 @@ export default function SidebarNav() {
|
||||
{ name: "Özellikler", href: "/admin/features", icon: Star },
|
||||
{ name: "Aktiviteler", href: "/admin/activities", icon: Compass },
|
||||
{ name: "Etkinlikler", href: "/admin/events", icon: CalendarDays },
|
||||
{ name: "Kullanıcılar", href: "/admin/users", icon: Users },
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useActionState, useEffect } from "react";
|
||||
import { createUser, updateUser, deleteUser, UserActionState } from "@/app/actions/users";
|
||||
import { User, UserPlus, Edit2, Trash2, X, Save, ShieldAlert } from "lucide-react";
|
||||
|
||||
type UserType = {
|
||||
id: number;
|
||||
username: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
export default function UsersClient({ initialUsers }: { initialUsers: UserType[] }) {
|
||||
const [users, setUsers] = useState<UserType[]>(initialUsers);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [editingUser, setEditingUser] = useState<UserType | null>(null);
|
||||
|
||||
// Update state when initialUsers change
|
||||
useEffect(() => {
|
||||
setUsers(initialUsers);
|
||||
}, [initialUsers]);
|
||||
|
||||
const [createState, createAction, isCreating] = useActionState<UserActionState, FormData>(
|
||||
createUser,
|
||||
null
|
||||
);
|
||||
|
||||
const [updateState, updateAction, isUpdating] = useActionState<UserActionState, FormData>(
|
||||
updateUser,
|
||||
null
|
||||
);
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
if (confirm("Bu kullanıcıyı silmek istediğinize emin misiniz?")) {
|
||||
const result = await deleteUser(id);
|
||||
if (result?.success) {
|
||||
setUsers(users.filter(u => u.id !== id));
|
||||
} else {
|
||||
alert(result?.message || "Bir hata oluştu.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditClick = (user: UserType) => {
|
||||
setEditingUser(user);
|
||||
setIsEditing(true);
|
||||
};
|
||||
|
||||
const handleAddNew = () => {
|
||||
setEditingUser(null);
|
||||
setIsEditing(true);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setIsEditing(false);
|
||||
setEditingUser(null);
|
||||
};
|
||||
|
||||
// Check state to close modal on success
|
||||
useEffect(() => {
|
||||
if (createState?.success || updateState?.success) {
|
||||
setIsEditing(false);
|
||||
setEditingUser(null);
|
||||
}
|
||||
}, [createState, updateState]);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* List Section */}
|
||||
{!isEditing && (
|
||||
<div className="bg-white rounded-2xl p-6 shadow-[0_8px_30px_rgb(0,0,0,0.04)] border border-primary/10 relative overflow-hidden group">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h2 className="text-xl font-bold text-dark flex items-center gap-2">
|
||||
<User className="w-5 h-5 text-accent" /> Yönetici Hesapları
|
||||
</h2>
|
||||
<button
|
||||
onClick={handleAddNew}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-accent hover:bg-accent/90 text-white rounded-xl transition-all font-medium text-sm shadow-md"
|
||||
>
|
||||
<UserPlus className="w-4 h-4" /> Yeni Kullanıcı
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{(createState?.message || updateState?.message) && (
|
||||
<div className={`p-4 mb-6 rounded-xl text-sm ${createState?.success || updateState?.success ? 'bg-green-50 text-green-700' : 'bg-red-50 text-red-700'}`}>
|
||||
{createState?.message || updateState?.message}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-4">
|
||||
{users.map((user) => (
|
||||
<div key={user.id} className="flex items-center justify-between p-4 rounded-xl border border-primary/5 hover:border-primary/20 transition-all bg-gray-50/50">
|
||||
<div className="flex flex-col">
|
||||
<span className="font-semibold text-dark">{user.username}</span>
|
||||
<span className="text-xs text-gray-500">
|
||||
Oluşturulma: {new Date(user.createdAt).toLocaleDateString('tr-TR')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => handleEditClick(user)}
|
||||
className="p-2 text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
|
||||
title="Düzenle"
|
||||
>
|
||||
<Edit2 className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete(user.id)}
|
||||
className="p-2 text-red-600 hover:bg-red-50 rounded-lg transition-colors"
|
||||
title="Sil"
|
||||
disabled={users.length <= 1}
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{users.length === 0 && (
|
||||
<p className="text-center text-gray-500 py-4">Kullanıcı bulunamadı.</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Form Section */}
|
||||
{isEditing && (
|
||||
<div className="bg-white rounded-2xl p-6 shadow-[0_8px_30px_rgb(0,0,0,0.04)] border border-primary/10 relative overflow-hidden">
|
||||
<div className="flex justify-between items-center mb-6 border-b border-primary/10 pb-4">
|
||||
<h2 className="text-xl font-bold text-dark flex items-center gap-2">
|
||||
{editingUser ? <Edit2 className="w-5 h-5 text-accent" /> : <UserPlus className="w-5 h-5 text-accent" />}
|
||||
{editingUser ? "Kullanıcıyı Düzenle" : "Yeni Kullanıcı Ekle"}
|
||||
</h2>
|
||||
<button
|
||||
onClick={handleCancel}
|
||||
className="p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form action={editingUser ? updateAction : createAction} className="space-y-4">
|
||||
{editingUser && <input type="hidden" name="id" value={editingUser.id} />}
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="block text-sm font-medium text-dark">Kullanıcı Adı</label>
|
||||
<input
|
||||
type="text"
|
||||
name="username"
|
||||
defaultValue={editingUser?.username || ""}
|
||||
required
|
||||
className="w-full px-4 py-2 bg-gray-50 border border-primary/10 rounded-xl focus:outline-none focus:ring-2 focus:ring-accent/50 text-dark"
|
||||
placeholder="Örn: admin"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="block text-sm font-medium text-dark">Şifre</label>
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
required={!editingUser}
|
||||
className="w-full px-4 py-2 bg-gray-50 border border-primary/10 rounded-xl focus:outline-none focus:ring-2 focus:ring-accent/50 text-dark"
|
||||
placeholder={editingUser ? "Değiştirmek için yeni şifre girin (İsteğe bağlı)" : "Güçlü bir şifre girin"}
|
||||
/>
|
||||
{editingUser && (
|
||||
<p className="text-xs text-gray-500 mt-1 flex items-center gap-1">
|
||||
<ShieldAlert className="w-3 h-3" /> Şifreyi değiştirmek istemiyorsanız boş bırakın.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{(createState?.message || updateState?.message) && (
|
||||
<div className={`p-4 rounded-xl text-sm ${createState?.success || updateState?.success ? 'bg-green-50 text-green-700' : 'bg-red-50 text-red-700'}`}>
|
||||
{createState?.message || updateState?.message}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-3 pt-4 border-t border-primary/10">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCancel}
|
||||
className="px-6 py-2 border border-primary/20 text-dark hover:bg-gray-50 rounded-xl transition-colors"
|
||||
>
|
||||
İptal
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isCreating || isUpdating}
|
||||
className="flex-1 flex items-center justify-center gap-2 px-6 py-2 bg-accent hover:bg-accent/90 text-white rounded-xl transition-all font-medium disabled:opacity-50"
|
||||
>
|
||||
<Save className="w-4 h-4" />
|
||||
{isCreating || isUpdating ? "Kaydediliyor..." : "Kaydet"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { getUsers } from "@/app/actions/users";
|
||||
import UsersClient from "./UsersClient";
|
||||
|
||||
export default async function UsersPage() {
|
||||
const users = await getUsers();
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto w-full">
|
||||
<h1 className="text-3xl font-bold font-serif mb-6 text-dark">Kullanıcı Yönetimi</h1>
|
||||
<UsersClient initialUsers={users} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user