From e9c1b32dda986dd173ab97324060f31cf5ca4d83 Mon Sep 17 00:00:00 2001 From: Nicholas Kelson Date: Fri, 26 Sep 2025 05:12:16 -0600 Subject: [PATCH 1/2] feat: Add Sign In and Sign Up forms with validation and error handling --- src/components/models/auth/SigninForm.tsx | 112 +++++++++++++ src/components/models/auth/SignupForm.tsx | 151 +++++++++++++++++ .../pages/signin/SigninPageContainer.tsx | 118 +------------ .../pages/signup/SignupPageContainer.tsx | 157 +----------------- src/components/shared/Form/FormCheckbox.tsx | 2 - src/components/shared/Form/FormInput.tsx | 2 - 6 files changed, 271 insertions(+), 271 deletions(-) create mode 100644 src/components/models/auth/SigninForm.tsx create mode 100644 src/components/models/auth/SignupForm.tsx diff --git a/src/components/models/auth/SigninForm.tsx b/src/components/models/auth/SigninForm.tsx new file mode 100644 index 0000000..25c8561 --- /dev/null +++ b/src/components/models/auth/SigninForm.tsx @@ -0,0 +1,112 @@ +"use client"; + +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { signinAction } from "@/actions/auth/signin/action"; +import { useForm } from "react-hook-form"; +import { useAction } from "next-safe-action/hooks"; +import { Form } from "@/components/ui/form"; +import { FormInput } from "@/components/shared/Form/FormInput"; +import { signinSchema, SigninInput } from "@/actions/auth/signin/schema"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { toast } from "sonner"; +import { FormButton } from "@/components/shared/Form/FormButton"; + +export function SigninForm() { + const form = useForm({ + mode: "onChange", + resolver: zodResolver(signinSchema), + defaultValues: { + email: "", + password: "", + }, + }); + const router = useRouter(); + + const { execute, isExecuting } = useAction(signinAction, { + onSuccess: () => { + toast.success("Signed in successfully!"); + router.push("/"); + }, + onError: (error) => { + const fieldErrors = error.error.validationErrors?.fieldErrors; + const errorMessage = + error.error.serverError ?? + (fieldErrors + ? Object.entries(fieldErrors) + .map(([key, value]) => `${key}: ${value}`) + .join(", ") + : "An unknown error occurred"); + toast.error(errorMessage); + }, + }); + + return ( +
+ + + + Sign in to Sentiopulse + + + Enter your credentials to access your account + + + +
+ + + + + + + + + Sign in + +

+ Don't have an account?{" "} + + Sign up + +

+
+
+ +
+
+ ); +} diff --git a/src/components/models/auth/SignupForm.tsx b/src/components/models/auth/SignupForm.tsx new file mode 100644 index 0000000..9afb078 --- /dev/null +++ b/src/components/models/auth/SignupForm.tsx @@ -0,0 +1,151 @@ +"use client"; + +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { + Card, + CardContent, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { signupAction } from "@/actions/auth/signup/action"; +import { useForm } from "react-hook-form"; +import { useAction } from "next-safe-action/hooks"; +import { Form } from "@/components/ui/form"; +import { FormInput } from "@/components/shared/Form/FormInput"; +import { FormCheckbox } from "@/components/shared/Form/FormCheckbox"; +import { signupSchema, SignupInput } from "@/actions/auth/signup/schema"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { toast } from "sonner"; +import { FormButton } from "@/components/shared/Form/FormButton"; + +export function SignupForm() { + const form = useForm({ + mode: "onChange", + resolver: zodResolver(signupSchema), + defaultValues: { + name: "", + email: "", + password: "", + confirmPassword: "", + terms: true, + }, + }); + const router = useRouter(); + + const { execute, isExecuting } = useAction(signupAction, { + onSuccess: () => { + toast.success("Account created successfully!"); + form.reset(); + router.push("/"); + }, + onError: (error) => { + const fieldErrors = error.error.validationErrors?.fieldErrors; + const errorMessage = + error.error.serverError ?? + (fieldErrors + ? Object.entries(fieldErrors) + .map(([key, value]) => `${key}: ${value}`) + .join(", ") + : "An unknown error occurred"); + toast.error(errorMessage); + }, + }); + + return ( +
+ + + + Create your account + + + +
+ + + + + + + + I agree to the{" "} + + Terms of Service + {" "} + and{" "} + + Privacy Policy + + + } + required + /> + + + + + Create account + +

+ Already have an account?{" "} + + Sign in + +

+
+
+ +
+
+ ); +} diff --git a/src/components/pages/signin/SigninPageContainer.tsx b/src/components/pages/signin/SigninPageContainer.tsx index cc9af05..52f6ee0 100644 --- a/src/components/pages/signin/SigninPageContainer.tsx +++ b/src/components/pages/signin/SigninPageContainer.tsx @@ -1,115 +1,5 @@ -"use client"; +import { SigninForm } from "@/components/models/auth/SigninForm"; -import Link from "next/link"; -import { useRouter } from "next/navigation"; -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { signinAction } from "@/actions/auth/signin/action"; -import { useForm } from "react-hook-form"; -import { useAction } from "next-safe-action/hooks"; -import { Form } from "@/components/ui/form"; -import FormInput from "@/components/shared/Form/FormInput"; -import { signinSchema } from "@/actions/auth/signin/schema"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { z } from "zod"; -import { toast } from "sonner"; -import { FormButton } from "@/components/shared/Form/FormButton"; - -type SignInFormValues = z.infer; - -export function SigninPageContainer() { - const form = useForm({ - mode: "onChange", - resolver: zodResolver(signinSchema), - defaultValues: { - email: "", - password: "", - }, - }); - const router = useRouter(); - - const { execute, isExecuting } = useAction(signinAction, { - onSuccess: () => { - toast.success("Signed in successfully!"); - router.push("/"); - }, - onError: (error) => { - const fieldErrors = error.error.validationErrors?.fieldErrors; - const errorMessage = - error.error.serverError ?? - (fieldErrors - ? Object.entries(fieldErrors) - .map(([key, value]) => `${key}: ${value}`) - .join(", ") - : "An unknown error occurred"); - toast.error(errorMessage); - }, - }); - - return ( -
- - - - Sign in to Sentiopulse - - - Enter your credentials to access your account - - - -
- - - - - - - - - Sign in - -

- Don't have an account?{" "} - - Sign up - -

-
-
- -
-
- ); -} +export function SigninPageContainer(){ + return +} \ No newline at end of file diff --git a/src/components/pages/signup/SignupPageContainer.tsx b/src/components/pages/signup/SignupPageContainer.tsx index f32a16d..239949e 100644 --- a/src/components/pages/signup/SignupPageContainer.tsx +++ b/src/components/pages/signup/SignupPageContainer.tsx @@ -1,154 +1,5 @@ -"use client"; +import { SignupForm } from "@/components/models/auth/SignupForm"; -import Link from "next/link"; -import { useRouter } from "next/navigation"; -import { - Card, - CardContent, - CardFooter, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { signupAction } from "@/actions/auth/signup/action"; -import { useForm } from "react-hook-form"; -import { useAction } from "next-safe-action/hooks"; -import { Form } from "@/components/ui/form"; -import FormInput from "@/components/shared/Form/FormInput"; -import FormCheckbox from "@/components/shared/Form/FormCheckbox"; -import { signupSchema } from "@/actions/auth/signup/schema"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { z } from "zod"; -import { toast } from "sonner"; -import { FormButton } from "@/components/shared/Form/FormButton"; - -type SignUpFormValues = z.infer; - -export function SignupPageContainer() { - const form = useForm({ - mode: "onChange", - resolver: zodResolver(signupSchema), - defaultValues: { - name: "", - email: "", - password: "", - confirmPassword: "", - terms: true, - }, - }); - const router = useRouter(); - - const { execute, isExecuting } = useAction(signupAction, { - onSuccess: () => { - toast.success("Account created successfully!"); - form.reset(); - router.push("/"); - }, - onError: (error) => { - const fieldErrors = error.error.validationErrors?.fieldErrors; - const errorMessage = - error.error.serverError ?? - (fieldErrors - ? Object.entries(fieldErrors) - .map(([key, value]) => `${key}: ${value}`) - .join(", ") - : "An unknown error occurred"); - toast.error(errorMessage); - }, - }); - - return ( -
- - - - Create your account - - - -
- - - - - - - - I agree to the{" "} - - Terms of Service - {" "} - and{" "} - - Privacy Policy - - - } - required - /> - - - - - Create account - -

- Already have an account?{" "} - - Sign in - -

-
-
- -
-
- ); -} +export function SignupPageContainer(){ + return +} \ No newline at end of file diff --git a/src/components/shared/Form/FormCheckbox.tsx b/src/components/shared/Form/FormCheckbox.tsx index c683695..41338ad 100644 --- a/src/components/shared/Form/FormCheckbox.tsx +++ b/src/components/shared/Form/FormCheckbox.tsx @@ -49,5 +49,3 @@ export function FormCheckbox({ /> ); } - -export default FormCheckbox; diff --git a/src/components/shared/Form/FormInput.tsx b/src/components/shared/Form/FormInput.tsx index 515dee1..58f5bf5 100644 --- a/src/components/shared/Form/FormInput.tsx +++ b/src/components/shared/Form/FormInput.tsx @@ -79,5 +79,3 @@ export function FormInput({ /> ); } - -export default FormInput; From c464f48d7729b8c876574448c142b280dc135993 Mon Sep 17 00:00:00 2001 From: Nicholas Kelson Date: Fri, 26 Sep 2025 06:12:07 -0600 Subject: [PATCH 2/2] fix: Update terms field default value to false and refine validation message in signup schema --- src/actions/auth/signup/schema.ts | 4 ++-- src/components/models/auth/SignupForm.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/actions/auth/signup/schema.ts b/src/actions/auth/signup/schema.ts index fc139f7..716ccc1 100644 --- a/src/actions/auth/signup/schema.ts +++ b/src/actions/auth/signup/schema.ts @@ -10,8 +10,8 @@ export const signupSchema = z .regex(/(?=.*[A-Z])/, 'Password must contain at least one uppercase letter') .regex(/(?=.*\d)/, 'Password must contain at least one number'), confirmPassword: z.string().min(1, 'Please confirm your password'), - terms: z.literal(true, { - errorMap: () => ({ message: 'You must accept the terms' }) + terms: z.boolean().refine(val => val === true, { + message: 'You must accept the terms' }), }) .refine((data) => data.password === data.confirmPassword, { diff --git a/src/components/models/auth/SignupForm.tsx b/src/components/models/auth/SignupForm.tsx index 9afb078..3eed4a1 100644 --- a/src/components/models/auth/SignupForm.tsx +++ b/src/components/models/auth/SignupForm.tsx @@ -29,7 +29,7 @@ export function SignupForm() { email: "", password: "", confirmPassword: "", - terms: true, + terms: false, }, }); const router = useRouter();