101 lines
2.7 KiB
TypeScript
101 lines
2.7 KiB
TypeScript
'use client';
|
|
|
|
import { useCallback, useEffect, useState } from 'react';
|
|
|
|
import Link from 'next/link';
|
|
|
|
import style from '@/styles/admin.module.scss';
|
|
|
|
type UserRow = {
|
|
id: number;
|
|
email: string;
|
|
is_approved: number | boolean;
|
|
is_admin: number | boolean;
|
|
created_at: string;
|
|
};
|
|
|
|
export function UsersAdminClient() {
|
|
const [users, setUsers] = useState<UserRow[]>([]);
|
|
const [error, setError] = useState('');
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
const load = useCallback(async () => {
|
|
setError('');
|
|
const res = await fetch('/api/admin/users', { credentials: 'include' });
|
|
const data = await res.json().catch(() => ({}));
|
|
if (!res.ok) {
|
|
setError((data as { error?: string }).error ?? 'Failed to load');
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
setUsers((data as { users: UserRow[] }).users ?? []);
|
|
setLoading(false);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
load();
|
|
}, [load]);
|
|
|
|
async function patch(id: number, body: { is_approved?: boolean; is_admin?: boolean }) {
|
|
setError('');
|
|
const res = await fetch(`/api/admin/users/${id}`, {
|
|
method: 'PATCH',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'include',
|
|
body: JSON.stringify(body),
|
|
});
|
|
const data = await res.json().catch(() => ({}));
|
|
if (!res.ok) {
|
|
setError((data as { error?: string }).error ?? 'Update failed');
|
|
return;
|
|
}
|
|
await load();
|
|
}
|
|
|
|
return (
|
|
<div className={style.card}>
|
|
<p>
|
|
<Link href="/admin">← Dashboard</Link>
|
|
</p>
|
|
<h1 className={style.title}>Users</h1>
|
|
<p className={style.sub}>Approve new accounts and grant admin role.</p>
|
|
{error ? <p className={style.error}>{error}</p> : null}
|
|
{loading ? <p>Loading…</p> : null}
|
|
{!loading && (
|
|
<table className={style.table}>
|
|
<thead>
|
|
<tr>
|
|
<th>Email</th>
|
|
<th>Approved</th>
|
|
<th>Admin</th>
|
|
<th>Created</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{users.map((u) => (
|
|
<tr key={u.id}>
|
|
<td>{u.email}</td>
|
|
<td>
|
|
<input
|
|
type="checkbox"
|
|
checked={Boolean(u.is_approved)}
|
|
onChange={(e) => patch(u.id, { is_approved: e.target.checked })}
|
|
/>
|
|
</td>
|
|
<td>
|
|
<input
|
|
type="checkbox"
|
|
checked={Boolean(u.is_admin)}
|
|
onChange={(e) => patch(u.id, { is_admin: e.target.checked })}
|
|
/>
|
|
</td>
|
|
<td>{String(u.created_at)}</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|