mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
replace react-hook-form with tanstack form
This commit is contained in:
@@ -17,9 +17,17 @@ export async function configureAuth(
|
|||||||
try {
|
try {
|
||||||
if (!enableAuth) {
|
if (!enableAuth) {
|
||||||
await fs.remove(path.join(clientDir, "src/components/sign-up-form.tsx"));
|
await fs.remove(path.join(clientDir, "src/components/sign-up-form.tsx"));
|
||||||
|
await fs.remove(path.join(clientDir, "src/components/sign-in-form.tsx"));
|
||||||
|
await fs.remove(path.join(clientDir, "src/components/auth-forms.tsx"));
|
||||||
await fs.remove(path.join(clientDir, "src/components/user-menu.tsx"));
|
await fs.remove(path.join(clientDir, "src/components/user-menu.tsx"));
|
||||||
await fs.remove(path.join(clientDir, "src/lib/auth-client.ts"));
|
await fs.remove(path.join(clientDir, "src/lib/auth-client.ts"));
|
||||||
await fs.remove(path.join(clientDir, "src/lib/schemas.ts"));
|
|
||||||
|
const indexRoutePath = path.join(clientDir, "src/routes/index.tsx");
|
||||||
|
const indexRouteContent = await fs.readFile(indexRoutePath, "utf8");
|
||||||
|
const updatedIndexRouteContent = indexRouteContent
|
||||||
|
.replace(/import AuthForms from "@\/components\/auth-forms";\n/, "")
|
||||||
|
.replace(/<AuthForms \/>/, "");
|
||||||
|
await fs.writeFile(indexRoutePath, updatedIndexRouteContent, "utf8");
|
||||||
|
|
||||||
await fs.remove(path.join(serverDir, "src/lib/auth.ts"));
|
await fs.remove(path.join(serverDir, "src/lib/auth.ts"));
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
"@radix-ui/react-label": "^2.1.2",
|
"@radix-ui/react-label": "^2.1.2",
|
||||||
"@radix-ui/react-slot": "^1.1.2",
|
"@radix-ui/react-slot": "^1.1.2",
|
||||||
"@tailwindcss/vite": "^4.0.5",
|
"@tailwindcss/vite": "^4.0.5",
|
||||||
|
"@tanstack/react-form": "^1.0.5",
|
||||||
"@tanstack/react-query": "^5.66.0",
|
"@tanstack/react-query": "^5.66.0",
|
||||||
"@tanstack/react-query-devtools": "^5.66.0",
|
"@tanstack/react-query-devtools": "^5.66.0",
|
||||||
"@tanstack/react-router": "^1.101.0",
|
"@tanstack/react-router": "^1.101.0",
|
||||||
@@ -42,7 +43,6 @@
|
|||||||
"next-themes": "^0.4.4",
|
"next-themes": "^0.4.4",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-hook-form": "^7.54.2",
|
|
||||||
"sonner": "^1.7.4",
|
"sonner": "^1.7.4",
|
||||||
"tailwind-merge": "^2.6.0",
|
"tailwind-merge": "^2.6.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import SignInForm from "./sign-in-form";
|
||||||
|
import SignUpForm from "./sign-up-form";
|
||||||
|
|
||||||
|
export default function AuthForms() {
|
||||||
|
const [showSignIn, setShowSignIn] = useState(false);
|
||||||
|
|
||||||
|
return showSignIn ? (
|
||||||
|
<SignInForm onSwitchToSignUp={() => setShowSignIn(false)} />
|
||||||
|
) : (
|
||||||
|
<SignUpForm onSwitchToSignIn={() => setShowSignIn(true)} />
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import { authClient } from "@/lib/auth-client";
|
||||||
|
import { useForm } from "@tanstack/react-form";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
|
import Loader from "./loader";
|
||||||
|
import { Button } from "./ui/button";
|
||||||
|
import { Input } from "./ui/input";
|
||||||
|
import { Label } from "./ui/label";
|
||||||
|
|
||||||
|
export default function SignInForm({
|
||||||
|
onSwitchToSignUp,
|
||||||
|
}: {
|
||||||
|
onSwitchToSignUp: () => void;
|
||||||
|
}) {
|
||||||
|
const navigate = useNavigate({
|
||||||
|
from: "/",
|
||||||
|
});
|
||||||
|
const { isPending } = authClient.useSession();
|
||||||
|
|
||||||
|
const form = useForm({
|
||||||
|
defaultValues: {
|
||||||
|
email: "",
|
||||||
|
password: "",
|
||||||
|
},
|
||||||
|
onSubmit: async ({ value }) => {
|
||||||
|
await authClient.signIn.email(
|
||||||
|
{
|
||||||
|
email: value.email,
|
||||||
|
password: value.password,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success("Sign in successful");
|
||||||
|
navigate({
|
||||||
|
to: "/dashboard",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.error.message);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
validators: {
|
||||||
|
onSubmit: z.object({
|
||||||
|
email: z.string().email("Invalid email address"),
|
||||||
|
password: z.string().min(6, "Password must be at least 6 characters"),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isPending) {
|
||||||
|
return <Loader />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx-auto mt-10 max-w-md p-6">
|
||||||
|
<h1 className="mb-6 text-center text-3xl font-bold">Welcome Back</h1>
|
||||||
|
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
void form.handleSubmit();
|
||||||
|
}}
|
||||||
|
className="space-y-4"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<form.Field
|
||||||
|
name="email"
|
||||||
|
children={(field) => (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor={field.name}>Email</Label>
|
||||||
|
<Input
|
||||||
|
id={field.name}
|
||||||
|
name={field.name}
|
||||||
|
type="email"
|
||||||
|
value={field.state.value}
|
||||||
|
onBlur={field.handleBlur}
|
||||||
|
onChange={(e) => field.handleChange(e.target.value)}
|
||||||
|
/>
|
||||||
|
{field.state.meta.errors.map((error) => (
|
||||||
|
<p key={error?.message} className="text-red-500">
|
||||||
|
{error?.message}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<form.Field
|
||||||
|
name="password"
|
||||||
|
children={(field) => (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor={field.name}>Password</Label>
|
||||||
|
<Input
|
||||||
|
id={field.name}
|
||||||
|
name={field.name}
|
||||||
|
type="password"
|
||||||
|
value={field.state.value}
|
||||||
|
onBlur={field.handleBlur}
|
||||||
|
onChange={(e) => field.handleChange(e.target.value)}
|
||||||
|
/>
|
||||||
|
{field.state.meta.errors.map((error) => (
|
||||||
|
<p key={error?.message} className="text-red-500">
|
||||||
|
{error?.message}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form.Subscribe>
|
||||||
|
{(state) => (
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
className="w-full"
|
||||||
|
disabled={!state.canSubmit || state.isSubmitting}
|
||||||
|
>
|
||||||
|
{state.isSubmitting ? "Submitting..." : "Sign In"}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</form.Subscribe>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div className="mt-4 text-center">
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
onClick={onSwitchToSignUp}
|
||||||
|
className="text-indigo-600 hover:text-indigo-800"
|
||||||
|
>
|
||||||
|
Need an account? Sign Up
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,157 +1,164 @@
|
|||||||
import { authClient } from "@/lib/auth-client";
|
import { authClient } from "@/lib/auth-client";
|
||||||
import { signInSchema, signUpSchema } from "@/lib/schemas";
|
import { useForm } from "@tanstack/react-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
import { useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import Loader from "./loader";
|
import Loader from "./loader";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from "./ui/form";
|
|
||||||
import { Input } from "./ui/input";
|
import { Input } from "./ui/input";
|
||||||
|
import { Label } from "./ui/label";
|
||||||
|
|
||||||
export default function AuthForm() {
|
export default function SignUpForm({
|
||||||
const navigate = useNavigate({
|
onSwitchToSignIn,
|
||||||
from: "/",
|
}: {
|
||||||
});
|
onSwitchToSignIn: () => void;
|
||||||
const [isSignUp, setIsSignUp] = useState(false);
|
}) {
|
||||||
const { isPending } = authClient.useSession();
|
const navigate = useNavigate({
|
||||||
|
from: "/",
|
||||||
|
});
|
||||||
|
const { isPending } = authClient.useSession();
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof signUpSchema>>({
|
const form = useForm({
|
||||||
resolver: zodResolver(isSignUp ? signUpSchema : signInSchema),
|
defaultValues: {
|
||||||
defaultValues: {
|
email: "",
|
||||||
email: "",
|
password: "",
|
||||||
password: "",
|
name: "",
|
||||||
name: "",
|
},
|
||||||
},
|
onSubmit: async ({ value }) => {
|
||||||
});
|
await authClient.signUp.email(
|
||||||
|
{
|
||||||
|
email: value.email,
|
||||||
|
password: value.password,
|
||||||
|
name: value.name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success("Sign up successful");
|
||||||
|
navigate({
|
||||||
|
to: "/dashboard",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.error.message);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
validators: {
|
||||||
|
onSubmit: z.object({
|
||||||
|
name: z.string().min(2, "Name must be at least 2 characters"),
|
||||||
|
email: z.string().email("Invalid email address"),
|
||||||
|
password: z.string().min(6, "Password must be at least 6 characters"),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const onSubmit = async (values: z.infer<typeof signUpSchema>) => {
|
if (isPending) {
|
||||||
if (isSignUp) {
|
return <Loader />;
|
||||||
await authClient.signUp.email(
|
}
|
||||||
{
|
|
||||||
email: values.email,
|
|
||||||
password: values.password,
|
|
||||||
name: values.name,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
onSuccess: () => {
|
|
||||||
toast.success("Sign up successful");
|
|
||||||
navigate({
|
|
||||||
to: "/dashboard",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onError: (ctx) => {
|
|
||||||
form.setError("email", {
|
|
||||||
type: "manual",
|
|
||||||
message: ctx.error.message,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
await authClient.signIn.email(
|
|
||||||
{
|
|
||||||
email: values.email,
|
|
||||||
password: values.password,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
onSuccess: () => {
|
|
||||||
toast.success("Sign in successful");
|
|
||||||
navigate({
|
|
||||||
to: "/dashboard",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onError: (ctx) => {
|
|
||||||
form.setError("email", {
|
|
||||||
type: "manual",
|
|
||||||
message: ctx.error.message,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isPending) {
|
return (
|
||||||
return <Loader />;
|
<div className="mx-auto mt-10 max-w-md p-6">
|
||||||
}
|
<h1 className="mb-6 text-center text-3xl font-bold">Create Account</h1>
|
||||||
|
|
||||||
return (
|
<form
|
||||||
<div className="mx-auto mt-10 max-w-md p-6">
|
onSubmit={(e) => {
|
||||||
<h1 className="mb-6 text-center text-3xl font-bold">
|
e.preventDefault();
|
||||||
{isSignUp ? "Create Account" : "Welcome Back"}
|
e.stopPropagation();
|
||||||
</h1>
|
void form.handleSubmit();
|
||||||
<Form {...form}>
|
}}
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
className="space-y-4"
|
||||||
{isSignUp && (
|
>
|
||||||
<FormField
|
<div>
|
||||||
control={form.control}
|
<form.Field name="name">
|
||||||
name="name"
|
{(field) => (
|
||||||
render={({ field }) => (
|
<div className="space-y-2">
|
||||||
<FormItem>
|
<Label htmlFor={field.name}>Name</Label>
|
||||||
<FormLabel>Name</FormLabel>
|
<Input
|
||||||
<FormControl>
|
id={field.name}
|
||||||
<Input {...field} />
|
name={field.name}
|
||||||
</FormControl>
|
value={field.state.value}
|
||||||
<FormMessage />
|
onBlur={field.handleBlur}
|
||||||
</FormItem>
|
onChange={(e) => field.handleChange(e.target.value)}
|
||||||
)}
|
/>
|
||||||
/>
|
{field.state.meta.errors.map((error) => (
|
||||||
)}
|
<p key={error?.message} className="text-red-500">
|
||||||
<FormField
|
{error?.message}
|
||||||
control={form.control}
|
</p>
|
||||||
name="email"
|
))}
|
||||||
render={({ field }) => (
|
</div>
|
||||||
<FormItem>
|
)}
|
||||||
<FormLabel>Email</FormLabel>
|
</form.Field>
|
||||||
<FormControl>
|
</div>
|
||||||
<Input type="email" {...field} />
|
|
||||||
</FormControl>
|
<div>
|
||||||
<FormMessage />
|
<form.Field name="email">
|
||||||
</FormItem>
|
{(field) => (
|
||||||
)}
|
<div className="space-y-2">
|
||||||
/>
|
<Label htmlFor={field.name}>Email</Label>
|
||||||
<FormField
|
<Input
|
||||||
control={form.control}
|
id={field.name}
|
||||||
name="password"
|
name={field.name}
|
||||||
render={({ field }) => (
|
type="email"
|
||||||
<FormItem>
|
value={field.state.value}
|
||||||
<FormLabel>Password</FormLabel>
|
onBlur={field.handleBlur}
|
||||||
<FormControl>
|
onChange={(e) => field.handleChange(e.target.value)}
|
||||||
<Input type="password" {...field} />
|
/>
|
||||||
</FormControl>
|
{field.state.meta.errors.map((error) => (
|
||||||
<FormMessage />
|
<p key={error?.message} className="text-red-500">
|
||||||
</FormItem>
|
{error?.message}
|
||||||
)}
|
</p>
|
||||||
/>
|
))}
|
||||||
<Button type="submit" className="w-full">
|
</div>
|
||||||
{isSignUp ? "Sign Up" : "Sign In"}
|
)}
|
||||||
</Button>
|
</form.Field>
|
||||||
</form>
|
</div>
|
||||||
</Form>
|
|
||||||
<div className="mt-4 text-center">
|
<div>
|
||||||
<Button
|
<form.Field name="password">
|
||||||
variant="link"
|
{(field) => (
|
||||||
onClick={() => {
|
<div className="space-y-2">
|
||||||
setIsSignUp(!isSignUp);
|
<Label htmlFor={field.name}>Password</Label>
|
||||||
form.reset();
|
<Input
|
||||||
}}
|
id={field.name}
|
||||||
className="text-indigo-600 hover:text-indigo-800"
|
name={field.name}
|
||||||
>
|
type="password"
|
||||||
{isSignUp
|
value={field.state.value}
|
||||||
? "Already have an account? Sign In"
|
onBlur={field.handleBlur}
|
||||||
: "Need an account? Sign Up"}
|
onChange={(e) => field.handleChange(e.target.value)}
|
||||||
</Button>
|
/>
|
||||||
</div>
|
{field.state.meta.errors.map((error) => (
|
||||||
</div>
|
<p key={error?.message} className="text-red-500">
|
||||||
);
|
{error?.message}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</form.Field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form.Subscribe>
|
||||||
|
{(state) => (
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
className="w-full"
|
||||||
|
disabled={!state.canSubmit || state.isSubmitting}
|
||||||
|
>
|
||||||
|
{state.isSubmitting ? "Submitting..." : "Sign Up"}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</form.Subscribe>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div className="mt-4 text-center">
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
onClick={onSwitchToSignIn}
|
||||||
|
className="text-indigo-600 hover:text-indigo-800"
|
||||||
|
>
|
||||||
|
Already have an account? Sign In
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,179 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import * as LabelPrimitive from "@radix-ui/react-label";
|
|
||||||
import { Slot } from "@radix-ui/react-slot";
|
|
||||||
import {
|
|
||||||
Controller,
|
|
||||||
type ControllerProps,
|
|
||||||
type FieldPath,
|
|
||||||
type FieldValues,
|
|
||||||
FormProvider,
|
|
||||||
useFormContext,
|
|
||||||
} from "react-hook-form";
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
|
|
||||||
const Form = FormProvider;
|
|
||||||
|
|
||||||
type FormFieldContextValue<
|
|
||||||
TFieldValues extends FieldValues = FieldValues,
|
|
||||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
||||||
> = {
|
|
||||||
name: TName;
|
|
||||||
};
|
|
||||||
|
|
||||||
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
|
||||||
{} as FormFieldContextValue,
|
|
||||||
);
|
|
||||||
|
|
||||||
const FormField = <
|
|
||||||
TFieldValues extends FieldValues = FieldValues,
|
|
||||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
||||||
>({
|
|
||||||
...props
|
|
||||||
}: ControllerProps<TFieldValues, TName>) => {
|
|
||||||
return (
|
|
||||||
<FormFieldContext.Provider value={{ name: props.name }}>
|
|
||||||
<Controller {...props} />
|
|
||||||
</FormFieldContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const useFormField = () => {
|
|
||||||
const fieldContext = React.useContext(FormFieldContext);
|
|
||||||
const itemContext = React.useContext(FormItemContext);
|
|
||||||
const { getFieldState, formState } = useFormContext();
|
|
||||||
|
|
||||||
const fieldState = getFieldState(fieldContext.name, formState);
|
|
||||||
|
|
||||||
if (!fieldContext) {
|
|
||||||
throw new Error("useFormField should be used within <FormField>");
|
|
||||||
}
|
|
||||||
|
|
||||||
const { id } = itemContext;
|
|
||||||
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
name: fieldContext.name,
|
|
||||||
formItemId: `${id}-form-item`,
|
|
||||||
formDescriptionId: `${id}-form-item-description`,
|
|
||||||
formMessageId: `${id}-form-item-message`,
|
|
||||||
...fieldState,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
type FormItemContextValue = {
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const FormItemContext = React.createContext<FormItemContextValue>(
|
|
||||||
{} as FormItemContextValue,
|
|
||||||
);
|
|
||||||
|
|
||||||
const FormItem = React.forwardRef<
|
|
||||||
HTMLDivElement,
|
|
||||||
React.HTMLAttributes<HTMLDivElement>
|
|
||||||
>(({ className, ...props }, ref) => {
|
|
||||||
const id = React.useId();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormItemContext.Provider value={{ id }}>
|
|
||||||
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
|
||||||
</FormItemContext.Provider>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
FormItem.displayName = "FormItem";
|
|
||||||
|
|
||||||
const FormLabel = React.forwardRef<
|
|
||||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
|
||||||
>(({ className, ...props }, ref) => {
|
|
||||||
const { error, formItemId } = useFormField();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Label
|
|
||||||
ref={ref}
|
|
||||||
className={cn(error && "text-destructive", className)}
|
|
||||||
htmlFor={formItemId}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
FormLabel.displayName = "FormLabel";
|
|
||||||
|
|
||||||
const FormControl = React.forwardRef<
|
|
||||||
React.ElementRef<typeof Slot>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof Slot>
|
|
||||||
>(({ ...props }, ref) => {
|
|
||||||
const { error, formItemId, formDescriptionId, formMessageId } =
|
|
||||||
useFormField();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Slot
|
|
||||||
ref={ref}
|
|
||||||
id={formItemId}
|
|
||||||
aria-describedby={
|
|
||||||
!error
|
|
||||||
? `${formDescriptionId}`
|
|
||||||
: `${formDescriptionId} ${formMessageId}`
|
|
||||||
}
|
|
||||||
aria-invalid={!!error}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
FormControl.displayName = "FormControl";
|
|
||||||
|
|
||||||
const FormDescription = React.forwardRef<
|
|
||||||
HTMLParagraphElement,
|
|
||||||
React.HTMLAttributes<HTMLParagraphElement>
|
|
||||||
>(({ className, ...props }, ref) => {
|
|
||||||
const { formDescriptionId } = useFormField();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<p
|
|
||||||
ref={ref}
|
|
||||||
id={formDescriptionId}
|
|
||||||
className={cn("text-[0.8rem] text-muted-foreground", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
FormDescription.displayName = "FormDescription";
|
|
||||||
|
|
||||||
const FormMessage = React.forwardRef<
|
|
||||||
HTMLParagraphElement,
|
|
||||||
React.HTMLAttributes<HTMLParagraphElement>
|
|
||||||
>(({ className, children, ...props }, ref) => {
|
|
||||||
const { error, formMessageId } = useFormField();
|
|
||||||
const body = error ? String(error?.message) : children;
|
|
||||||
|
|
||||||
if (!body) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<p
|
|
||||||
ref={ref}
|
|
||||||
id={formMessageId}
|
|
||||||
className={cn("text-[0.8rem] font-medium text-destructive", className)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{body}
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
FormMessage.displayName = "FormMessage";
|
|
||||||
|
|
||||||
export {
|
|
||||||
useFormField,
|
|
||||||
Form,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormControl,
|
|
||||||
FormDescription,
|
|
||||||
FormMessage,
|
|
||||||
FormField,
|
|
||||||
};
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
export const signUpSchema = z.object({
|
|
||||||
name: z.string().min(2, "Name must be at least 2 characters"),
|
|
||||||
email: z.string().email("Invalid email address"),
|
|
||||||
password: z.string().min(6, "Password must be at least 6 characters"),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const signInSchema = z.object({
|
|
||||||
email: z.string().email("Invalid email address"),
|
|
||||||
password: z.string().min(6, "Password must be at least 6 characters"),
|
|
||||||
});
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import SignUp from "@/components/sign-up-form";
|
import AuthForms from "@/components/auth-forms";
|
||||||
import { trpc } from "@/utils/trpc";
|
import { trpc } from "@/utils/trpc";
|
||||||
import { createFileRoute, Link } from "@tanstack/react-router";
|
import { createFileRoute, Link } from "@tanstack/react-router";
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ function HomeComponent() {
|
|||||||
<h3>Welcome Home!</h3>
|
<h3>Welcome Home!</h3>
|
||||||
<Link to="/dashboard">Go to Dashboard</Link>
|
<Link to="/dashboard">Go to Dashboard</Link>
|
||||||
<p>healthCheck: {healthCheck.data}</p>
|
<p>healthCheck: {healthCheck.data}</p>
|
||||||
<SignUp />
|
<AuthForms />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user