mirror of
https://github.com/tale/headplane.git
synced 2026-08-27 15:37:04 +00:00
feat: switch to a new form hook
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { type } from "arktype";
|
||||
import { Split } from "lucide-react";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import Button from "~/components/button";
|
||||
import Chip from "~/components/chip";
|
||||
@@ -9,30 +9,40 @@ import Switch from "~/components/switch";
|
||||
import Text from "~/components/text";
|
||||
import Title from "~/components/title";
|
||||
import Tooltip from "~/components/tooltip";
|
||||
import { useForm } from "~/hooks/use-form";
|
||||
import cn from "~/utils/cn";
|
||||
|
||||
const nsSchema = type({
|
||||
ns: "string.ip",
|
||||
split_name: "string > 0",
|
||||
});
|
||||
|
||||
interface Props {
|
||||
nameservers: Record<string, string[]>;
|
||||
}
|
||||
|
||||
export default function AddNameserver({ nameservers }: Props) {
|
||||
const [split, setSplit] = useState(false);
|
||||
const [ns, setNs] = useState("");
|
||||
const [domain, setDomain] = useState("");
|
||||
const form = useForm({
|
||||
schema: nsSchema,
|
||||
defaultValues: { split_name: "global" },
|
||||
validate: (values) => {
|
||||
const ns = values.ns as string;
|
||||
const domain = values.split_name as string;
|
||||
if (!ns) return undefined;
|
||||
|
||||
const isInvalid = useMemo(() => {
|
||||
if (ns === "") return false;
|
||||
// Test if it's a valid IPv4 or IPv6 address
|
||||
const ipv4 = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/;
|
||||
const ipv6 = /^([0-9a-fA-F:]+:+)+[0-9a-fA-F]+$/;
|
||||
if (!ipv4.test(ns) && !ipv6.test(ns)) return true;
|
||||
const isSplit = domain !== "global";
|
||||
const isDuplicate = isSplit
|
||||
? nameservers[domain]?.includes(ns)
|
||||
: Object.values(nameservers).some((nsList) => nsList.includes(ns));
|
||||
|
||||
if (split) {
|
||||
return nameservers[domain]?.includes(ns);
|
||||
}
|
||||
if (isDuplicate) {
|
||||
return { ns: "This nameserver already exists." };
|
||||
}
|
||||
|
||||
return Object.values(nameservers).some((nsList) => nsList.includes(ns));
|
||||
}, [nameservers, ns]);
|
||||
return undefined;
|
||||
},
|
||||
});
|
||||
const split = (form.values.split_name as string) !== "global";
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
@@ -41,12 +51,10 @@ export default function AddNameserver({ nameservers }: Props) {
|
||||
<Title className="mb-4">Add nameserver</Title>
|
||||
<input name="action_id" type="hidden" value="add_ns" />
|
||||
<Input
|
||||
{...form.field("ns")}
|
||||
description="Use this IPv4 or IPv6 address to resolve names."
|
||||
invalid={isInvalid}
|
||||
required
|
||||
label="Nameserver"
|
||||
name="ns"
|
||||
onChange={setNs}
|
||||
placeholder="1.2.3.4"
|
||||
/>
|
||||
<div className="mt-8 flex items-center justify-between">
|
||||
@@ -67,16 +75,20 @@ export default function AddNameserver({ nameservers }: Props) {
|
||||
</div>
|
||||
<Text className="text-sm">This nameserver will only be used for some domains.</Text>
|
||||
</div>
|
||||
<Switch label="Split DNS" onCheckedChange={setSplit} />
|
||||
<Switch
|
||||
label="Split DNS"
|
||||
onCheckedChange={(checked) => {
|
||||
form.setValue("split_name", checked ? "" : "global");
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{split ? (
|
||||
<>
|
||||
<Text className="mt-8 font-semibold">Domain</Text>
|
||||
<Input
|
||||
required={split === true}
|
||||
{...form.field("split_name")}
|
||||
required
|
||||
label="Domain"
|
||||
name="split_name"
|
||||
onChange={setDomain}
|
||||
placeholder="example.com"
|
||||
/>
|
||||
<Text className="text-sm">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { type } from "arktype";
|
||||
|
||||
import Button from "~/components/button";
|
||||
import Code from "~/components/code";
|
||||
@@ -7,33 +7,49 @@ import Input from "~/components/input";
|
||||
import Select from "~/components/select";
|
||||
import Text from "~/components/text";
|
||||
import Title from "~/components/title";
|
||||
import { useForm } from "~/hooks/use-form";
|
||||
|
||||
const recordSchema = type({
|
||||
record_type: "'A' | 'AAAA'",
|
||||
record_name: "string > 0",
|
||||
record_value: "string > 0",
|
||||
});
|
||||
|
||||
interface Props {
|
||||
records: { name: string; type: "A" | "AAAA" | string; value: string }[];
|
||||
}
|
||||
|
||||
export default function AddRecord({ records }: Props) {
|
||||
const [type, setType] = useState<"A" | "AAAA" | string>("A");
|
||||
const [name, setName] = useState("");
|
||||
const [ip, setIp] = useState("");
|
||||
const form = useForm({
|
||||
schema: recordSchema,
|
||||
defaultValues: { record_type: "A" },
|
||||
validate: (values) => {
|
||||
const name = values.record_name as string;
|
||||
const ip = values.record_value as string;
|
||||
if (name.length === 0 || ip.length === 0) return undefined;
|
||||
|
||||
const isDuplicate = useMemo(() => {
|
||||
if (name.length === 0 || ip.length === 0) return false;
|
||||
const lookup = records.find((record) => record.name === name);
|
||||
if (!lookup) return false;
|
||||
const lookup = records.find((r) => r.name === name);
|
||||
if (lookup?.value === ip) {
|
||||
return {
|
||||
record_name: "This record already exists.",
|
||||
record_value: "This record already exists.",
|
||||
};
|
||||
}
|
||||
|
||||
return lookup.value === ip;
|
||||
}, [records, name, ip]);
|
||||
return undefined;
|
||||
},
|
||||
});
|
||||
const name = form.values.record_name as string;
|
||||
const ip = form.values.record_value as string;
|
||||
const recordType = form.values.record_type as string;
|
||||
const isDuplicate =
|
||||
!!form.errors.record_name?.includes("already exists") &&
|
||||
!!form.errors.record_value?.includes("already exists");
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<Button>Add DNS record</Button>
|
||||
<DialogPanel
|
||||
onSubmit={() => {
|
||||
setName("");
|
||||
setIp("");
|
||||
}}
|
||||
>
|
||||
<DialogPanel onSubmit={() => form.reset()}>
|
||||
<Title>Add DNS record</Title>
|
||||
<Text>Enter the domain and IP address for the new DNS record.</Text>
|
||||
<div className="mt-4 flex flex-col gap-2">
|
||||
@@ -42,9 +58,9 @@ export default function AddRecord({ records }: Props) {
|
||||
required
|
||||
label="Record Type"
|
||||
name="record_type"
|
||||
defaultValue={type}
|
||||
defaultValue={recordType}
|
||||
onValueChange={(v) => {
|
||||
if (v) setType(v as "A" | "AAAA");
|
||||
if (v) form.setValue("record_type", v);
|
||||
}}
|
||||
items={[
|
||||
{ value: "A", label: "A" },
|
||||
@@ -52,20 +68,16 @@ export default function AddRecord({ records }: Props) {
|
||||
]}
|
||||
/>
|
||||
<Input
|
||||
{...form.field("record_name")}
|
||||
required
|
||||
label="Domain"
|
||||
placeholder="test.example.com"
|
||||
name="record_name"
|
||||
onChange={setName}
|
||||
invalid={isDuplicate}
|
||||
/>
|
||||
<Input
|
||||
{...form.field("record_value")}
|
||||
required
|
||||
label="IP Address"
|
||||
placeholder={type === "AAAA" ? "2001:db8::ff00:42:8329" : "101.101.101.101"}
|
||||
name="record_value"
|
||||
onChange={setIp}
|
||||
invalid={isDuplicate}
|
||||
placeholder={recordType === "AAAA" ? "2001:db8::ff00:42:8329" : "101.101.101.101"}
|
||||
/>
|
||||
{isDuplicate ? (
|
||||
<p className="text-sm opacity-50">
|
||||
|
||||
Reference in New Issue
Block a user