Files
moy-qr/app/admin/(dashboard)/users/UserList.tsx
T

157 lines
6.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useActionState, useState } from 'react'
import { changePassword, deleteUser } from './actions'
import { KeyRound, Trash2, ChevronDown, ChevronUp, Shield, User as UserIcon } from 'lucide-react'
type User = {
id: number
username: string
role: string
created_at: Date
}
function ChangePasswordForm({ userId }: { userId: number }) {
const action = changePassword.bind(null, userId)
const [state, formAction, isPending] = useActionState(action, undefined)
return (
<form action={formAction} className="p-5 bg-slate-50/80 border-t border-slate-100 space-y-4">
{state?.success && (
<p className="text-sm font-medium text-emerald-700 bg-emerald-50 px-4 py-2.5 rounded-xl border border-emerald-100">{state.message}</p>
)}
{state?.error && (
<p className="text-sm font-medium text-red-700 bg-red-50 px-4 py-2.5 rounded-xl border border-red-100">{state.error}</p>
)}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className="block text-xs font-semibold text-slate-600 mb-1.5 uppercase tracking-wider">Yeni Şifre</label>
<input
type="password"
name="new_password"
required
autoComplete="new-password"
placeholder="Min. 6 karakter"
className="w-full px-3 py-2 bg-white border border-slate-200 rounded-xl text-sm focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 outline-none transition-all"
/>
</div>
<div>
<label className="block text-xs font-semibold text-slate-600 mb-1.5 uppercase tracking-wider">Şifre Tekrar</label>
<input
type="password"
name="confirm_password"
required
autoComplete="new-password"
placeholder="Tekrar giriniz"
className="w-full px-3 py-2 bg-white border border-slate-200 rounded-xl text-sm focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 outline-none transition-all"
/>
</div>
</div>
<div className="flex justify-end pt-1">
<button
type="submit"
disabled={isPending}
className="bg-amber-500 hover:bg-amber-600 text-white text-sm font-medium py-2 px-5 rounded-xl transition-all shadow-sm hover:shadow hover:-translate-y-0.5 disabled:opacity-70 disabled:hover:translate-y-0 flex items-center gap-2"
>
<KeyRound size={16} />
{isPending ? 'Güncelleniyor...' : 'Şifreyi Güncelle'}
</button>
</div>
</form>
)
}
function UserRow({ user }: { user: User }) {
const [expanded, setExpanded] = useState(false)
async function handleDelete() {
if (!confirm(`"${user.username}" kullanıcısını silmek istediğinize emin misiniz?`)) return
const res = await deleteUser(user.id)
if (res?.error) alert(res.error)
}
return (
<div className="border border-slate-100 rounded-2xl overflow-hidden bg-white hover:border-slate-200 hover:shadow-[0_2px_15px_-3px_rgba(0,0,0,0.05)] transition-all duration-300">
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between p-4 sm:px-6 sm:py-5 gap-4">
<div className="flex items-center gap-4">
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-indigo-50 to-blue-50 flex items-center justify-center text-indigo-600 font-bold text-lg uppercase shadow-sm border border-indigo-100/50">
{user.username[0]}
</div>
<div>
<p className="font-bold text-slate-800 text-base">{user.username}</p>
<p className="text-xs font-medium text-slate-400 mt-0.5">
Katılım: {new Date(user.created_at).toLocaleDateString('tr-TR', { day: 'numeric', month: 'long', year: 'numeric' })}
</p>
</div>
</div>
<div className="flex flex-wrap items-center gap-2 sm:gap-3 w-full sm:w-auto">
<span className={`inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-bold uppercase tracking-wider ${
user.role === 'admin' ? 'bg-purple-50 text-purple-600 border border-purple-100' : 'bg-slate-50 text-slate-500 border border-slate-100'
}`}>
<Shield size={14} />
{user.role}
</span>
<div className="flex-1 sm:hidden"></div>
<button
onClick={() => setExpanded(v => !v)}
className={`flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-xl transition-all ${
expanded
? 'bg-amber-100 text-amber-700 shadow-inner'
: 'text-amber-600 bg-amber-50 hover:bg-amber-100'
}`}
>
<KeyRound size={16} />
<span className="hidden sm:inline">Şifre İşlemleri</span>
{expanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
</button>
<button
onClick={handleDelete}
className="p-2 text-red-500 hover:text-red-700 hover:bg-red-50 rounded-xl transition-colors"
title="Kullanıcıyı Sil"
>
<Trash2 size={18} />
</button>
</div>
</div>
{expanded && <ChangePasswordForm userId={user.id} />}
</div>
)
}
export default function UserList({ users }: { users: User[] }) {
return (
<div className="bg-white rounded-2xl shadow-[0_2px_10px_-3px_rgba(6,81,237,0.1)] border border-slate-100 p-6 md:p-8">
<div className="flex items-center gap-3 mb-6">
<div className="p-2 bg-indigo-50 text-indigo-600 rounded-lg">
<UserIcon size={20} />
</div>
<div>
<h2 className="text-lg font-bold text-slate-800 tracking-tight">Sistem Kullanıcıları</h2>
<p className="text-xs text-slate-500">Toplam {users.length} kullanıcı kayıtlı.</p>
</div>
</div>
{users.length === 0 ? (
<div className="text-center py-12 px-4 border-2 border-dashed border-slate-200 rounded-2xl">
<div className="w-12 h-12 bg-slate-50 text-slate-400 rounded-xl flex items-center justify-center mx-auto mb-3">
<UserIcon size={24} />
</div>
<p className="text-sm font-medium text-slate-600">Henüz kullanıcı yok.</p>
<p className="text-xs text-slate-400 mt-1">Sisteme giriş yapabilmek için yukarıdan yeni bir kullanıcı ekleyin.</p>
</div>
) : (
<div className="space-y-4">
{users.map(user => (
<UserRow key={user.id} user={user} />
))}
</div>
)}
</div>
)
}