import { useId, useState, type FormEvent } from "react"; import { useQuery } from "@tanstack/react-query"; import * as stylex from "@stylexjs/stylex"; import { forwardZoneCreateMutation, forwardZoneDeleteMutation, forwardZoneUpdateMutation, forwardZonesQuery, } from "@/lib/queries"; import type { ForwardZone, ForwardZoneInput } from "@/lib/types"; import InlineError from "@/lib/InlineError"; import ConfirmDialog from "@/ui/ConfirmDialog"; import { useCrudForm } from "@/ui/useCrudForm"; import QueryPanel from "./QueryPanel"; import { styles as shared } from "@/ui/styles"; import { colors } from "@/ui/tokens.stylex"; const styles = stylex.create({ formHeading: { fontWeight: 500, }, fieldLabel: { display: "block", fontSize: "0.875rem", lineHeight: "1.25rem", fontWeight: 500, }, buttonRow: { display: "flex", gap: "0.5rem", }, toolbar: { display: "flex", alignItems: "center", justifyContent: "space-between", marginTop: "1rem", }, intro: { fontSize: "0.875rem", lineHeight: "1.25rem", color: colors.textMuted, }, table: { width: "100%", textAlign: "left", fontSize: "0.875rem", lineHeight: "1.25rem", }, headRow: { borderBottomWidth: 1, borderBottomStyle: "solid", borderBottomColor: colors.border, color: colors.textMuted, }, headCell: { paddingBlock: "0.5rem", paddingRight: "1rem", fontWeight: 500, }, headCellLast: { paddingBlock: "0.5rem", }, bodyRow: { borderBottomWidth: 1, borderBottomStyle: "solid", borderBottomColor: colors.border, }, cell: { paddingBlock: "0.5rem", paddingRight: "1rem", }, emptyCell: { paddingBlock: "1rem", color: colors.textMuted, }, actionCell: { paddingBlock: "0.5rem", textAlign: "right", whiteSpace: "nowrap", }, dangerText: { color: colors.danger, }, dimWhenDisabled: { opacity: { default: 1, ":disabled": 0.5 }, }, }); function ZoneForm({ initial, busy, error, onSubmit, onCancel, }: { initial?: ForwardZone; busy: boolean; error: unknown; onSubmit: (input: ForwardZoneInput) => void; onCancel: () => void; }) { const id = useId(); const [zone, setZone] = useState(initial?.zone ?? ""); const [resolver, setResolver] = useState(initial?.resolver ?? ""); function submit(event: FormEvent) { event.preventDefault(); onSubmit({ zone: zone.trim(), resolver: resolver.trim() }); } return (

{initial === undefined ? "New forward zone" : `Edit ${initial.zone}`}

setZone(event.target.value)} placeholder="lan.home" {...stylex.props(shared.input, shared.focusRing)} />
setResolver(event.target.value)} placeholder="udp://192.168.1.1:53" {...stylex.props(shared.input, shared.focusRing)} />
); } export default function ZonesTab() { const query = useQuery(forwardZonesQuery()); const { create, update, remove, form, openForm, closeForm, onSubmit, onDelete, pendingDelete, confirmPendingDelete, cancelPendingDelete, } = useCrudForm({ create: forwardZoneCreateMutation, update: forwardZoneUpdateMutation, remove: forwardZoneDeleteMutation, confirmDelete: (zone) => `Delete forward zone "${zone.zone}"?`, }); return (

Names under these zones go to their own resolver. Changes apply live.

{form?.mode === "create" && ( )} {(zones) => (
{zones.length === 0 && ( )} {zones.map((zone) => ( {form?.mode === "edit" && form.entity.id === zone.id ? ( ) : ( <> )} ))}
Zone Resolver Actions
No forward zones yet.
{zone.zone} {zone.resolver}
)}
); }