202 lines
7.9 KiB
TypeScript
202 lines
7.9 KiB
TypeScript
"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>
|
||
);
|
||
}
|