milestone 9: react spa admin ui, frontend ci and embedded dist

This commit is contained in:
2026-08-02 13:04:09 +02:00
parent 5253c47303
commit 617cc966a2
82 changed files with 11833 additions and 17 deletions
@@ -0,0 +1,86 @@
import { useState } from "react";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { clientUpdateMutation } from "@/lib/queries";
import type { Client, Group } from "@/lib/types";
import InlineError from "@/lib/InlineError";
interface Props {
client: Client;
groups: Group[];
onClose: () => void;
}
const inputClass =
"mt-1 w-full rounded border border-zinc-300 bg-white px-2 py-1.5 text-sm dark:border-zinc-700 dark:bg-zinc-900";
export default function ClientEditDialog({ client, groups, onClose }: Props) {
const queryClient = useQueryClient();
const mutation = useMutation(clientUpdateMutation(queryClient));
const [name, setName] = useState(client.name);
const [groupId, setGroupId] = useState(client.group_id);
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4">
<div
role="dialog"
aria-modal="true"
aria-label={`Edit client ${client.ip}`}
className="w-full max-w-md rounded-lg border border-zinc-200 bg-white p-6 shadow-lg dark:border-zinc-700 dark:bg-zinc-900"
>
<h2 className="text-lg font-semibold">Edit {client.ip}</h2>
<form
className="mt-4 space-y-4"
onSubmit={(event) => {
event.preventDefault();
mutation.mutate(
{ id: client.id, edit: { name: name.trim(), group_id: groupId } },
{ onSuccess: onClose },
);
}}
>
<label className="block text-sm font-medium">
Name
<input
type="text"
value={name}
onChange={(event) => setName(event.target.value)}
className={inputClass}
autoFocus
/>
</label>
<label className="block text-sm font-medium">
Group
<select
value={String(groupId)}
onChange={(event) => setGroupId(Number(event.target.value))}
className={inputClass}
>
{groups.map((group) => (
<option key={group.id} value={String(group.id)}>
{group.name}
</option>
))}
</select>
</label>
<InlineError error={mutation.error} />
<div className="flex justify-end gap-2">
<button
type="button"
onClick={onClose}
className="rounded border border-zinc-300 px-3 py-1.5 text-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 dark:border-zinc-700"
>
Cancel
</button>
<button
type="submit"
disabled={mutation.isPending}
className="rounded bg-blue-600 px-3 py-1.5 text-sm font-medium text-white disabled:opacity-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
>
Save
</button>
</div>
</form>
</div>
</div>
);
}