ref: use react hook form for setup
This commit is contained in:
parent
f94e0c3611
commit
e52597c349
5 changed files with 78 additions and 56 deletions
|
@ -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 />;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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`}>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue