86 lines
3.0 KiB
TypeScript
86 lines
3.0 KiB
TypeScript
import { ForbiddenError, apiGet } from "@/lib/server-api";
|
|
import { fmtDateTime } from "@/lib/format";
|
|
import type { Account, Me, UserSummary } from "@/lib/types";
|
|
import { AccountActions, CreateAccountForm } from "./forms";
|
|
|
|
export default async function AccountsPage() {
|
|
const me = await apiGet<Me>("/auth/me");
|
|
if (me.account.role !== "owner") {
|
|
return (
|
|
<>
|
|
<h1>Contas do painel</h1>
|
|
<p className="muted">Somente contas owner gerenciam as contas do painel.</p>
|
|
</>
|
|
);
|
|
}
|
|
|
|
let accounts: Account[] = [];
|
|
let users: UserSummary[] = [];
|
|
try {
|
|
[accounts, users] = await Promise.all([
|
|
apiGet<{ accounts: Account[] }>("/accounts").then((r) => r.accounts),
|
|
apiGet<{ users: UserSummary[] }>("/users").then((r) => r.users),
|
|
]);
|
|
} catch (err) {
|
|
if (!(err instanceof ForbiddenError)) throw err;
|
|
}
|
|
const userOptions = users.map((u) => ({ id: u.id, name: u.display_name }));
|
|
|
|
return (
|
|
<>
|
|
<h1>Contas do painel</h1>
|
|
<p className="muted">
|
|
<strong>owner</strong> gerencia contas; <strong>admin</strong> altera usuários, lembretes, feriados e
|
|
configurações; <strong>leitura</strong> só consulta. Uma conta de leitura ligada a um usuário do Telegram enxerga e
|
|
altera somente os dados desse usuário. Sempre existe pelo menos um owner ativo. <strong>Desativar</strong> bloqueia o
|
|
acesso e pode ser revertido; <strong>Excluir</strong> apaga a conta de vez.
|
|
</p>
|
|
|
|
<div className="table-wrap">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Usuário</th>
|
|
<th>Papel</th>
|
|
<th>Usuário do Telegram</th>
|
|
<th>Situação</th>
|
|
<th>Último login</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{accounts.map((a) => (
|
|
<tr key={a.id}>
|
|
<td>
|
|
{a.username}
|
|
{a.id === me.account.id && <span className="muted"> (você)</span>}
|
|
</td>
|
|
<td>{a.role === "viewer" ? "leitura" : a.role}</td>
|
|
<td>{userOptions.find((u) => u.id === a.user_id)?.name ?? <span className="muted">—</span>}</td>
|
|
<td>
|
|
{a.disabled ? (
|
|
<span className="badge error">desativada</span>
|
|
) : a.locked_until && new Date(a.locked_until) > new Date() ? (
|
|
<span className="badge warn">bloqueada até {fmtDateTime(a.locked_until)}</span>
|
|
) : (
|
|
<span className="badge ok">ativa</span>
|
|
)}
|
|
</td>
|
|
<td>{fmtDateTime(a.last_login_at)}</td>
|
|
<td>
|
|
<AccountActions account={a} isSelf={a.id === me.account.id} users={userOptions} />
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<section className="card">
|
|
<h2>Nova conta</h2>
|
|
<CreateAccountForm users={userOptions} />
|
|
</section>
|
|
</>
|
|
);
|
|
}
|