Expand README with desktop platform requirements (Windows x86, macOS ARM), helper build commands, gateway utility scripts, and updated local test flow. Add realistic MVP usage section clarifying current platform build status, gateway configuration needs, and admin debug profile behavior with client private key handling.
76 lines
2.8 KiB
TypeScript
76 lines
2.8 KiB
TypeScript
import { FormEvent, useState } from "react";
|
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
|
|
import { api } from "../../api/client";
|
|
import { Page } from "../../components/Page";
|
|
import { Table } from "../../components/Table";
|
|
|
|
const columns = [
|
|
{ key: "username", label: "Username" },
|
|
{ key: "name", label: "Display name" },
|
|
{ key: "role", label: "Role" },
|
|
{ key: "status", label: "Status" }
|
|
];
|
|
|
|
export function UsersPage() {
|
|
const queryClient = useQueryClient();
|
|
const query = useQuery({
|
|
queryKey: ["users"],
|
|
queryFn: api.users
|
|
});
|
|
const [form, setForm] = useState({
|
|
username: "",
|
|
display_name: "",
|
|
email: "",
|
|
password: "",
|
|
role: "user"
|
|
});
|
|
|
|
const createMutation = useMutation({
|
|
mutationFn: api.createUser,
|
|
onSuccess: () => {
|
|
setForm({ username: "", display_name: "", email: "", password: "", role: "user" });
|
|
void queryClient.invalidateQueries({ queryKey: ["users"] });
|
|
}
|
|
});
|
|
|
|
const rows = query.data?.map((user) => ({
|
|
username: user.username,
|
|
name: user.display_name,
|
|
role: user.role,
|
|
status: user.is_active ? "active" : "disabled"
|
|
})) ?? [];
|
|
|
|
function onSubmit(event: FormEvent) {
|
|
event.preventDefault();
|
|
createMutation.mutate(form);
|
|
}
|
|
|
|
return (
|
|
<Page
|
|
title="Users"
|
|
subtitle="Create, disable, and manage platform identities."
|
|
actions={<span className="pill">Admin managed</span>}
|
|
>
|
|
<form className="inline-form" onSubmit={onSubmit}>
|
|
<input placeholder="username" value={form.username} onChange={(event) => setForm((value) => ({ ...value, username: event.target.value }))} />
|
|
<input placeholder="display name" value={form.display_name} onChange={(event) => setForm((value) => ({ ...value, display_name: event.target.value }))} />
|
|
<input placeholder="email" value={form.email} onChange={(event) => setForm((value) => ({ ...value, email: event.target.value }))} />
|
|
<input placeholder="password" type="password" value={form.password} onChange={(event) => setForm((value) => ({ ...value, password: event.target.value }))} />
|
|
<select value={form.role} onChange={(event) => setForm((value) => ({ ...value, role: event.target.value }))}>
|
|
<option value="user">user</option>
|
|
<option value="admin">admin</option>
|
|
</select>
|
|
<button className="button" type="submit" disabled={createMutation.isPending}>Create user</button>
|
|
</form>
|
|
{query.isError ? <p className="notice">Unable to load users from the API.</p> : null}
|
|
{createMutation.isError ? <p className="notice">Unable to create user.</p> : null}
|
|
<Table
|
|
columns={columns}
|
|
rows={rows}
|
|
renderCell={(row, column) => <span>{row[column.key as keyof (typeof rows)[number]]}</span>}
|
|
/>
|
|
</Page>
|
|
);
|
|
}
|