feat: some changes and admin
This commit is contained in:
100
nextjs/src/app/admin/(dashboard)/users/UsersAdminClient.tsx
Normal file
100
nextjs/src/app/admin/(dashboard)/users/UsersAdminClient.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
'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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user