import { useEffect, useState, useCallback } from 'react';
import PageHeader from '../../components/PageHeader';
import { listUsers, createUser, deleteUser, listTokens, createToken, revokeToken, type AppUser, type TokenInfo } from '../../services/admin.service';
import { ROLE_LABELS, roleAtLeast, type Role, type CurrentUser } from '../../services/auth.service';
import styles from './Admin.module.scss';
const fmt = (v: string | null) => (v ? new Date(v).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' }) : '—');
export default function Admin({ user }: { user: CurrentUser }) {
const isAdmin = user.role === 'admin';
const [tab, setTab] = useState<'users' | 'tokens'>('users');
useEffect(() => { if (!isAdmin) setTab('users'); }, [isAdmin]);
return (
{isAdmin && }
{tab === 'users' ?
:
}
);
}
function UsersPanel({ me }: { me: CurrentUser }) {
const [users, setUsers] = useState([]);
const [error, setError] = useState(null);
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [role, setRole] = useState('viewer');
const [busy, setBusy] = useState(false);
const load = useCallback(() => { listUsers().then(setUsers).catch(() => setError('Failed to load users')); }, []);
useEffect(() => { load(); }, [load]);
// Leadership can grant up to 'lead'; only admin can grant 'admin'.
const grantable: Role[] = me.role === 'admin' ? ['viewer', 'pm', 'lead', 'admin'] : ['viewer', 'pm', 'lead'];
const submit = async (e: React.FormEvent) => {
e.preventDefault();
setBusy(true); setError(null);
try {
const res = await createUser(username.trim(), password, role);
if (res.user) { setUsername(''); setPassword(''); setRole('viewer'); load(); }
else setError(res.message || 'Could not create user');
} catch { setError('Could not create user'); }
finally { setBusy(false); }
};
const remove = async (u: string) => {
if (!confirm(`Delete user "${u}"?`)) return;
await deleteUser(u).catch(() => {});
load();
};
return (
Users ({users.length})
| Username | Role | Last login | |
{users.map(u => (
| {u.username} |
{ROLE_LABELS[u.role]} |
{fmt(u.lastLoginAt)} |
{u.username !== me.username && }
|
))}
);
}
function TokensPanel() {
const [tokens, setTokens] = useState([]);
const [label, setLabel] = useState('');
const [minted, setMinted] = useState(null);
const [busy, setBusy] = useState(false);
const load = useCallback(() => { listTokens().then(setTokens).catch(() => {}); }, []);
useEffect(() => { load(); }, [load]);
const create = async (e: React.FormEvent) => {
e.preventDefault();
setBusy(true);
try {
const res = await createToken(label.trim() || 'sync-extension');
setMinted(res.token); setLabel(''); load();
} finally { setBusy(false); }
};
const revoke = async (id: number) => { if (confirm('Revoke this token?')) { await revokeToken(id).catch(() => {}); load(); } };
return (
Tokens ({tokens.length})
| Label | ID | Last used | Expires | |
{tokens.map(t => (
| {t.label} |
{t.tokenId} |
{fmt(t.lastUsedAt)} |
{fmt(t.expiresAt)} |
{t.revoked ? revoked : }
|
))}
);
}