feat: setup database-driven admin user management

This commit is contained in:
2026-06-10 04:25:54 +03:00
parent f542efb324
commit b712760825
23 changed files with 804 additions and 40 deletions
+201
View File
@@ -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>
);
}