Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/actions/auth/signup/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, {
Expand Down
112 changes: 112 additions & 0 deletions src/components/models/auth/SigninForm.tsx
Original file line number Diff line number Diff line change
@@ -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<SigninInput>({
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 (
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
<Card className="w-full max-w-md">
<CardHeader className="space-y-1">
<CardTitle className="text-2xl font-bold text-center">
Sign in to Sentiopulse
</CardTitle>
<CardDescription className="text-center">
Enter your credentials to access your account
</CardDescription>
</CardHeader>

<Form {...form}>
<form onSubmit={form.handleSubmit(execute)}>
<CardContent className="space-y-4">
<FormInput
control={form.control}
name="email"
label="Email"
placeholder="john@example.com"
type="email"
required
/>
<FormInput
control={form.control}
name="password"
label="Password"
placeholder="Enter your password"
type="password"
required
/>
</CardContent>

<CardFooter className="flex flex-col space-y-4">
<FormButton
className="w-full"
loading={isExecuting}
disabled={
!form.formState.isValid ||
!form.formState.isDirty ||
isExecuting
}
>
Sign in
</FormButton>
<p className="text-center text-sm text-gray-600">
Don&apos;t have an account?{" "}
<Link
href="/signup"
className="text-blue-600 hover:underline font-medium"
>
Sign up
</Link>
</p>
</CardFooter>
</form>
</Form>
</Card>
</div>
);
}
151 changes: 151 additions & 0 deletions src/components/models/auth/SignupForm.tsx
Original file line number Diff line number Diff line change
@@ -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<SignupInput>({
mode: "onChange",
resolver: zodResolver(signupSchema),
defaultValues: {
name: "",
email: "",
password: "",
confirmPassword: "",
terms: false,
},
});
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 (
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
<Card className="w-full max-w-md">
<CardHeader className="space-y-1">
<CardTitle className="text-2xl font-bold text-center">
Create your account
</CardTitle>
</CardHeader>

<Form {...form}>
<form onSubmit={form.handleSubmit(execute)}>
<CardContent className="space-y-4">
<FormInput
control={form.control}
name="name"
label="Full Name"
placeholder="John Doe"
required
/>
<FormInput
control={form.control}
name="email"
label="Email"
placeholder="john@example.com"
type="email"
required
/>
<FormInput
control={form.control}
name="password"
label="Password"
placeholder="Create a strong password"
type="password"
required
/>
<FormInput
control={form.control}
name="confirmPassword"
label="Confirm Password"
placeholder="Confirm your password"
type="password"
required
/>
<FormCheckbox
control={form.control}
name="terms"
label={
<span>
I agree to the{" "}
<Link
href="/terms"
className="text-blue-600 hover:underline"
>
Terms of Service
</Link>{" "}
and{" "}
<Link
href="/privacy"
className="text-blue-600 hover:underline"
>
Privacy Policy
</Link>
</span>
}
required
/>
Comment on lines +99 to +121
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Checkbox onChange likely sets non-boolean values — fix in FormCheckbox.

Current FormCheckbox uses onChange={field.onChange} on a native checkbox. Controller expects you to pass checked boolean; otherwise RHF may store "on" or the event, breaking z.boolean() and preventing valid submission.

Update FormCheckbox:

-              <input
+              <input
                 id={name}
                 type="checkbox"
                 className="h-4 w-4 text-blue-600 border-gray-300 rounded"
-                checked={!!field.value}
-                onChange={field.onChange}
+                checked={!!field.value}
+                onChange={(e) => field.onChange((e.target as HTMLInputElement).checked)}
                 ref={field.ref}
                 required={required}
               />

File: src/components/shared/Form/FormCheckbox.tsx

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<FormCheckbox
control={form.control}
name="terms"
label={
<span>
I agree to the{" "}
<Link
href="/terms"
className="text-blue-600 hover:underline"
>
Terms of Service
</Link>{" "}
and{" "}
<Link
href="/privacy"
className="text-blue-600 hover:underline"
>
Privacy Policy
</Link>
</span>
}
required
/>
<input
id={name}
type="checkbox"
className="h-4 w-4 text-blue-600 border-gray-300 rounded"
checked={!!field.value}
onChange={(e) => field.onChange((e.target as HTMLInputElement).checked)}
ref={field.ref}
required={required}
/>
🤖 Prompt for AI Agents
In src/components/shared/Form/FormCheckbox.tsx (update this file), the component
currently wires the native checkbox directly to field.onChange which passes the
event (or "on") into react-hook-form and causes non-boolean values to be stored;
change the input to call field.onChange(e.target.checked) and bind
checked={!!field.value} (defaulting to false) instead of spreading the whole
field onto the input for onChange/checked, and keep other props (onBlur, name,
ref) from field; this ensures RHF receives a boolean and zod/z.boolean()
validation works correctly.

</CardContent>

<CardFooter className="flex flex-col space-y-4">
<FormButton
className="w-full"
loading={isExecuting}
disabled={
!form.formState.isValid ||
!form.formState.isDirty ||
isExecuting
}
>
Create account
</FormButton>
<p className="text-center text-sm text-gray-600">
Already have an account?{" "}
<Link
href="/signin"
className="text-blue-600 hover:underline font-medium"
>
Sign in
</Link>
</p>
</CardFooter>
</form>
</Form>
</Card>
</div>
);
}
118 changes: 4 additions & 114 deletions src/components/pages/signin/SigninPageContainer.tsx
Original file line number Diff line number Diff line change
@@ -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<typeof signinSchema>;

export function SigninPageContainer() {
const form = useForm<SignInFormValues>({
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 (
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
<Card className="w-full max-w-md">
<CardHeader className="space-y-1">
<CardTitle className="text-2xl font-bold text-center">
Sign in to Sentiopulse
</CardTitle>
<CardDescription className="text-center">
Enter your credentials to access your account
</CardDescription>
</CardHeader>

<Form {...form}>
<form onSubmit={form.handleSubmit(execute)}>
<CardContent className="space-y-4">
<FormInput
control={form.control}
name="email"
label="Email"
placeholder="john@example.com"
type="email"
required
/>
<FormInput
control={form.control}
name="password"
label="Password"
placeholder="Enter your password"
type="password"
required
/>
</CardContent>

<CardFooter className="flex flex-col space-y-4">
<FormButton
className="w-full"
loading={isExecuting}
disabled={
!form.formState.isValid ||
!form.formState.isDirty ||
isExecuting
}
>
Sign in
</FormButton>
<p className="text-center text-sm text-gray-600">
Don&apos;t have an account?{" "}
<Link
href="/signup"
className="text-blue-600 hover:underline font-medium"
>
Sign up
</Link>
</p>
</CardFooter>
</form>
</Form>
</Card>
</div>
);
}
export function SigninPageContainer(){
return <SigninForm/>
}
Loading