Files
catherine-league/nextjs/src/app/admin/(dashboard)/users/UsersAdminClient.tsx
2026-03-31 16:09:03 +09:00

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>
);
}