ref: use react hook form for setup

This commit is contained in:
Derock 2024-04-12 21:23:04 -04:00
parent f94e0c3611
commit e52597c349
No known key found for this signature in database
5 changed files with 78 additions and 56 deletions

View file

@ -1,4 +1,6 @@
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { db } from "~/server/db";
import { users } from "~/server/db/schema";
import { api } from "~/trpc/server"; import { api } from "~/trpc/server";
import LoginForm from "./Login"; import LoginForm from "./Login";
@ -6,5 +8,17 @@ export default async function LoginPage() {
const loggedIn = await api.auth.me.query().catch(() => null); const loggedIn = await api.auth.me.query().catch(() => null);
if (loggedIn) return redirect("/"); if (loggedIn) return redirect("/");
// check if instance is set up or not
const isSetup = await db
.select({ id: users.id })
.from(users)
.limit(1)
.execute()
.then((users) => users.length > 0);
if (!isSetup) {
return redirect("/setup");
}
return <LoginForm />; return <LoginForm />;
} }

View file

@ -1,21 +1,20 @@
"use client"; "use client";
import { useForm } from "@mantine/form";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import { import {
Card, Card,
CardContent,
CardDescription,
CardFooter,
CardHeader, CardHeader,
CardTitle, CardTitle,
CardDescription,
CardContent,
CardFooter,
} from "~/components/ui/card"; } from "~/components/ui/card";
import { Input } from "~/components/ui/input"; import { Form } from "~/components/ui/form";
import { Label } from "~/components/ui/label"; import { SimpleFormField, useForm } from "~/hooks/forms";
import { Required } from "~/components/ui/required";
import { api } from "~/trpc/react"; import { api } from "~/trpc/react";
export function SetupForm() { export function SetupForm() {
@ -27,7 +26,7 @@ export function SetupForm() {
>(); >();
const setupInstance = api.setup.setup.useMutation({ const setupInstance = api.setup.setup.useMutation({
onSuccess: () => { onSuccess: () => {
router.push("/dashboard"); router.push("/login");
toast.success("Successfully setup instance!", { id: toastLoading }); toast.success("Successfully setup instance!", { id: toastLoading });
}, },
@ -36,22 +35,19 @@ export function SetupForm() {
}, },
}); });
const form = useForm({ const form = useForm(
initialValues: { z.object({
username: "", username: z.string().min(3),
password: "", password: z.string().min(3),
}, }),
);
validate: {
password: (value) => value.length === 0 && "Password is required",
username: (value) => value.length === 0 && "Username is required",
},
});
return ( return (
<Card className="m-auto h-fit max-w-xl"> <Card className="m-auto h-fit max-w-xl">
<CardHeader> <CardHeader>
<CardTitle>Setup Hostforge 🚀</CardTitle> <CardTitle>
Setup Hostforge <span className="animate-shake">🚀</span>
</CardTitle>
<CardDescription> <CardDescription>
Welcome to Hostforge a modern self-hosted platform for deploying and Welcome to Hostforge a modern self-hosted platform for deploying and
managing your own applications. managing your own applications.
@ -61,43 +57,41 @@ export function SetupForm() {
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<form <Form {...form}>
id="setupform" <form
className="flex flex-col gap-4" id="setupform"
onSubmit={form.onSubmit((data) => { className="space-y-2"
setToastLoading(toast.loading("Setting up instance...")); onSubmit={form.handleSubmit(async (data) => {
setupInstance.mutate(data); setToastLoading(toast.loading("Setting up instance..."));
})} await setupInstance.mutateAsync(data);
> })}
<div className="flex flex-col gap-2"> >
<Label htmlFor="username"> <SimpleFormField
Username control={form.control}
<Required /> name="username"
</Label> friendlyName="Username"
<Input {...form.getInputProps("username")} id="username" /> description="The username for the admin Hostforge user."
{form.errors.username && ( required
<div className="text-red-500">{form.errors.username}</div> />
)}
</div>
<div className="flex flex-col gap-2"> <SimpleFormField
<Label htmlFor="password"> control={form.control}
Password name="password"
<Required /> friendlyName="Password"
</Label> description="The password for the admin Hostforge user."
<Input required
{...form.getInputProps("password")}
id="password"
type="password" type="password"
/> />
{form.errors.password && ( </form>
<div className="text-red-500">{form.errors.password}</div> </Form>
)}
</div>
</form>
</CardContent> </CardContent>
<CardFooter> <CardFooter>
<Button className="w-full" type="submit" form="setupform"> <Button
className="w-full"
type="submit"
form="setupform"
isLoading={form.formState.isSubmitting}
>
Submit Submit
</Button> </Button>
</CardFooter> </CardFooter>

View file

@ -25,8 +25,6 @@ export default function RootLayout({
}: { }: {
children: React.ReactNode; children: React.ReactNode;
}) { }) {
console.log(outfit);
return ( return (
<html lang="en"> <html lang="en">
<body className={`font-sans ${outfit.variable} min-h-screen min-w-full`}> <body className={`font-sans ${outfit.variable} min-h-screen min-w-full`}>

View file

@ -1,6 +1,6 @@
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { AnimatePresence, motion } from "framer-motion"; import { AnimatePresence, motion } from "framer-motion";
import { type ReactNode } from "react"; import { type PropsWithChildren, type ReactNode } from "react";
import { import {
useForm as useFormHook, useForm as useFormHook,
useFormState, useFormState,
@ -80,8 +80,10 @@ export function SimpleFormField<
required?: boolean; required?: boolean;
render?: ControllerProps<TFieldValues, TContext>["render"]; render?: ControllerProps<TFieldValues, TContext>["render"];
className?: string; className?: string;
type?: PropsWithChildren<HTMLInputElement>["type"];
}) { }) {
const render = props.render ?? (({ field }) => <Input {...field} />); const render =
props.render ?? (({ field }) => <Input {...field} type={props.type} />);
return ( return (
<UIFormField <UIFormField

View file

@ -68,10 +68,24 @@ module.exports = {
from: { height: "var(--radix-accordion-content-height)" }, from: { height: "var(--radix-accordion-content-height)" },
to: { height: 0 }, to: { height: 0 },
}, },
shake: {
"0%": { transform: "translate(1px, 1px) rotate(0deg);" },
"10%": { transform: "translate(-1px, -2px) rotate(-1deg);" },
"20%": { transform: "translate(-2px, 0px) rotate(1deg);" },
"30%": { transform: "translate(2px, 2px) rotate(0deg);" },
"40%": { transform: "translate(1px, -1px) rotate(1deg);" },
"50%": { transform: "translate(-1px, 2px) rotate(-1deg);" },
"60%": { transform: "translate(-2px, 1px) rotate(0deg);" },
"70%": { transform: "translate(2px, 1px) rotate(-1deg);" },
"80%": { transform: "translate(-1px, -1px) rotate(1deg);" },
"90%": { transform: "translate(1px, 2px) rotate(0deg);" },
"100%": { transform: "translate(1px, -2px) rotate(-1deg);" },
},
}, },
animation: { animation: {
"accordion-down": "accordion-down 0.2s ease-out", "accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out",
shake: "shake 0.75s infinite linear",
}, },
}, },
}, },