ref: use react hook form for setup
This commit is contained in:
parent
f94e0c3611
commit
e52597c349
|
@ -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 />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
<form
|
||||||
id="setupform"
|
id="setupform"
|
||||||
className="flex flex-col gap-4"
|
className="space-y-2"
|
||||||
onSubmit={form.onSubmit((data) => {
|
onSubmit={form.handleSubmit(async (data) => {
|
||||||
setToastLoading(toast.loading("Setting up instance..."));
|
setToastLoading(toast.loading("Setting up instance..."));
|
||||||
setupInstance.mutate(data);
|
await setupInstance.mutateAsync(data);
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-2">
|
<SimpleFormField
|
||||||
<Label htmlFor="username">
|
control={form.control}
|
||||||
Username
|
name="username"
|
||||||
<Required />
|
friendlyName="Username"
|
||||||
</Label>
|
description="The username for the admin Hostforge user."
|
||||||
<Input {...form.getInputProps("username")} id="username" />
|
required
|
||||||
{form.errors.username && (
|
/>
|
||||||
<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 && (
|
|
||||||
<div className="text-red-500">{form.errors.password}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
|
</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>
|
||||||
|
|
|
@ -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`}>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue