ui: add protected dashboard CRUD screens and assignment docs

This commit is contained in:
Syahdan 2026-04-29 18:10:31 +07:00
parent bf42037619
commit cb0d4d7809
24 changed files with 2662 additions and 101 deletions

View file

@ -0,0 +1,190 @@
import { Button } from "@minmon/ui/components/button";
import { useForm } from "@tanstack/react-form";
import { useMutation } from "@tanstack/react-query";
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { toast } from "sonner";
import { FormCard, PageSection, TextAreaField, TextField } from "@/components/dashboard-ui";
import { queryClient, trpcClient } from "@/utils/trpc";
export const Route = createFileRoute("/dashboard/servers/new")({
component: RouteComponent,
});
type ServerFormValues = {
name: string;
primaryIpAddress: string;
secondaryIpAddress: string;
operatingSystem: string;
location: string;
provider: string;
description: string;
status: "active" | "maintenance" | "inactive";
};
function RouteComponent() {
const navigate = useNavigate();
const createServer = useMutation({
mutationFn: async (values: ServerFormValues) =>
trpcClient.servers.create.mutate({
...values,
secondaryIpAddress: values.secondaryIpAddress || null,
operatingSystem: values.operatingSystem || null,
location: values.location || null,
provider: values.provider || null,
description: values.description || null,
}),
onSuccess: async (created) => {
toast.success("Server created");
await queryClient.invalidateQueries();
navigate({ to: "/dashboard/servers/$serverId", params: { serverId: created.id } });
},
onError: (error) => {
toast.error(error.message);
},
});
const defaultValues: ServerFormValues = {
name: "",
primaryIpAddress: "",
secondaryIpAddress: "",
operatingSystem: "",
location: "",
provider: "",
description: "",
status: "active",
};
const form = useForm({
defaultValues,
onSubmit: async ({ value }) => {
await createServer.mutateAsync(value);
},
});
return (
<PageSection title="Add server" description="Create a new managed server record.">
<form
className="grid gap-4"
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
form.handleSubmit();
}}
>
<FormCard
title="Server details"
description="Server name and primary IP are required."
footer={
<>
<Button type="button" variant="ghost" onClick={() => navigate({ to: "/dashboard/servers" })}>
Cancel
</Button>
<form.Subscribe selector={(state) => ({ isSubmitting: state.isSubmitting })}>
{({ isSubmitting }) => <Button type="submit">{isSubmitting ? "Saving..." : "Save server"}</Button>}
</form.Subscribe>
</>
}
>
<div className="grid gap-4 md:grid-cols-2">
<form.Field name="name">
{(field) => (
<TextField
label="Server name"
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
placeholder="Production VPS"
/>
)}
</form.Field>
<form.Field name="primaryIpAddress">
{(field) => (
<TextField
label="Primary IP address"
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
placeholder="203.0.113.10"
/>
)}
</form.Field>
<form.Field name="secondaryIpAddress">
{(field) => (
<TextField
label="Secondary IP address"
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
placeholder="203.0.113.11"
/>
)}
</form.Field>
<form.Field name="operatingSystem">
{(field) => (
<TextField
label="Operating system"
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
placeholder="Ubuntu 24.04 LTS"
/>
)}
</form.Field>
<form.Field name="location">
{(field) => (
<TextField
label="Location"
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
placeholder="Singapore"
/>
)}
</form.Field>
<form.Field name="provider">
{(field) => (
<TextField
label="Provider"
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
placeholder="DigitalOcean"
/>
)}
</form.Field>
<form.Field name="status">
{(field) => (
<TextField
label="Status"
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value as "active" | "maintenance" | "inactive")}
placeholder="active"
/>
)}
</form.Field>
</div>
<form.Field name="description">
{(field) => (
<TextAreaField
label="Description"
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
placeholder="Describe the purpose of this server."
/>
)}
</form.Field>
</FormCard>
</form>
</PageSection>
);
}