'use client' import { useState, useCallback } from 'react' import type { PublicUser, Role } from '@/lib/users' interface Props { initialUsers: PublicUser[] currentUserId: string } export default function UsersClient({ initialUsers, currentUserId }: Props) { const [users, setUsers] = useState(initialUsers) const [error, setError] = useState('') const [successMsg, setSuccessMsg] = useState('') // Create user form state const [creating, setCreating] = useState(false) const [createUsername, setCreateUsername] = useState('') const [createPassword, setCreatePassword] = useState('') const [createRole, setCreateRole] = useState('user') const [createLoading, setCreateLoading] = useState(false) // Password reset state — maps userId → new password string const [pwReset, setPwReset] = useState>({}) const [pwLoading, setPwLoading] = useState(null) function flash(msg: string) { setSuccessMsg(msg) setTimeout(() => setSuccessMsg(''), 3000) } const refreshUsers = useCallback(async () => { const res = await fetch('/api/admin/users') if (res.ok) { setUsers((await res.json()) as PublicUser[]) } }, []) async function handleCreate(e: React.FormEvent) { e.preventDefault() setCreateLoading(true) setError('') const res = await fetch('/api/admin/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: createUsername, password: createPassword, role: createRole }), }) const data = (await res.json()) as { error?: string } setCreateLoading(false) if (!res.ok) { setError(data.error ?? 'Failed to create user') return } setCreateUsername('') setCreatePassword('') setCreateRole('user') setCreating(false) await refreshUsers() flash('User created successfully') } async function handleDelete(id: string, username: string) { if (!confirm(`Delete user "${username}"? This cannot be undone.`)) return setError('') const res = await fetch(`/api/admin/users/${id}`, { method: 'DELETE' }) const data = (await res.json()) as { error?: string } if (!res.ok) { setError(data.error ?? 'Failed to delete user') return } await refreshUsers() flash(`User "${username}" deleted`) } async function handlePasswordReset(id: string) { const pw = pwReset[id] ?? '' if (pw.length < 8) { setError('New password must be at least 8 characters') return } setError('') setPwLoading(id) const res = await fetch(`/api/admin/users/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password: pw }), }) const data = (await res.json()) as { error?: string } setPwLoading(null) if (!res.ok) { setError(data.error ?? 'Failed to update password') return } setPwReset((prev) => ({ ...prev, [id]: '' })) flash('Password updated') } const inputStyle: React.CSSProperties = { background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.1)', color: '#e2e8f0', borderRadius: '0.625rem', padding: '0.5rem 0.75rem', fontSize: '0.875rem', outline: 'none', width: '100%', } return (
{/* Flash messages */} {successMsg && (
{successMsg}
)} {error && (
{error}
)} {/* Header + create button */}

{users.length} user{users.length !== 1 ? 's' : ''}

{/* Create user form */} {creating && (

New User

setCreateUsername(e.target.value)} required placeholder="username" style={inputStyle} />
setCreatePassword(e.target.value)} required placeholder="••••••••" style={inputStyle} />
)} {/* User list */}
{users.map((user) => (
{/* Avatar */}
{user.username[0].toUpperCase()}
{user.username} {user.id === currentUserId && ( you )}
{user.role} Joined {new Date(user.createdAt).toLocaleDateString()}
{/* Delete (not self) */} {user.id !== currentUserId && ( )}
{/* Password reset */}
setPwReset((prev) => ({ ...prev, [user.id]: e.target.value }))} placeholder="New password (min 8 chars)" style={{ ...inputStyle, flex: 1 }} />
))} {users.length === 0 && (

No users yet.

)}
) }