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 { db } from "~/server/db";
import { users } from "~/server/db/schema";
import { api } from "~/trpc/server";
import LoginForm from "./Login";
@ -6,5 +8,17 @@ export default async function LoginPage() {
const loggedIn = await api.auth.me.query().catch(() => null);
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 />;
}

View file

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

View file

@ -25,8 +25,6 @@ export default function RootLayout({
}: {
children: React.ReactNode;
}) {
console.log(outfit);
return (
<html lang="en">
<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 { AnimatePresence, motion } from "framer-motion";
import { type ReactNode } from "react";
import { type PropsWithChildren, type ReactNode } from "react";
import {
useForm as useFormHook,
useFormState,
@ -80,8 +80,10 @@ export function SimpleFormField<
required?: boolean;
render?: ControllerProps<TFieldValues, TContext>["render"];
className?: string;
type?: PropsWithChildren<HTMLInputElement>["type"];
}) {
const render = props.render ?? (({ field }) => <Input {...field} />);
const render =
props.render ?? (({ field }) => <Input {...field} type={props.type} />);
return (
<UIFormField

View file

@ -68,10 +68,24 @@ module.exports = {
from: { height: "var(--radix-accordion-content-height)" },
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: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
shake: "shake 0.75s infinite linear",
},
},
},