diff --git a/app/api/auth/signup/route.ts b/app/api/auth/signup/route.ts index 44a6e51..f00a725 100644 --- a/app/api/auth/signup/route.ts +++ b/app/api/auth/signup/route.ts @@ -2,23 +2,23 @@ import { NextResponse } from "next/server"; import bcrypt from "bcryptjs"; import connectDB from "@/lib/connectToDatabase"; import User from "@/models/User"; +import { signUpFormSchema } from "@/lib/formSchemas/authFormSchemas"; -export async function POST(request: Request) { +export async function POST(req: Request) { try { - const { username, email, password } = await request.json(); + const body = await req.json(); - // Validate input - if (!username || !email || !password) { + const result = signUpFormSchema.safeParse(body); + if (!result.success) { return NextResponse.json( - { message: "Missing required fields" }, + { message: "Invalid data", errors: result.error.flatten().fieldErrors }, { status: 400 } ); } - // Connect to database - await connectDB(); + const { username, email, password } = result.data; - // Check if user already exists + await connectDB(); const existingUser = await User.findOne({ $or: [{ email }, { username }], }); @@ -30,10 +30,8 @@ export async function POST(request: Request) { ); } - // Hash password const hashedPassword = await bcrypt.hash(password, 12); - // Create user const user = await User.create({ username, email, @@ -43,7 +41,6 @@ export async function POST(request: Request) { // Remove password from response const { password: _, ...userWithoutPassword } = user.toObject(); - return NextResponse.json( { message: "User created successfully", user: userWithoutPassword }, { status: 201 } diff --git a/app/user/signin/page.tsx b/app/user/signin/page.tsx index b5dba34..24e1ca9 100644 --- a/app/user/signin/page.tsx +++ b/app/user/signin/page.tsx @@ -7,8 +7,6 @@ import { Tabs, TabsContent } from "@/components/ui/tabs"; import { Eye, EyeOff } from "lucide-react"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; -import { z } from "zod"; -import { useRouter } from "next/navigation"; import { Form, FormControl, @@ -18,22 +16,16 @@ import { FormMessage, } from "@/components/ui/form"; import { Logo } from "@/components/Logo"; - -//Form validation scheme -const formSchema = z.object({ - email: z.string().email("Invalid email address."), - password: z.string().min(6, "Password must be at least 6 characters."), -}); +import { + SignInFormData, + signInFormSchema, +} from "@/lib/formSchemas/authFormSchemas"; export default function Home() { - const router = useRouter(); - - //Use state to toggle password visibility const [showPassword, setShowPassword] = useState(false); - //Define form - const form = useForm>({ - resolver: zodResolver(formSchema), + const form = useForm({ + resolver: zodResolver(signInFormSchema), defaultValues: { email: "", password: "", @@ -41,16 +33,14 @@ export default function Home() { }); //Define a submit handler - async function onSubmit(values: z.infer) { + async function onSubmit(values: SignInFormData) { const result = await signIn("credentials", { - redirect: false, email: values.email, password: values.password, + callbackUrl: "/", }); if (result?.error) { - console.error(result.error); - form.setError("email", { type: "manual", message: "Invalid email or password", @@ -63,9 +53,6 @@ export default function Home() { return; } - - router.push("/"); - router.refresh(); } return ( diff --git a/app/user/signup/page.tsx b/app/user/signup/page.tsx index 131eb4c..a06a0af 100644 --- a/app/user/signup/page.tsx +++ b/app/user/signup/page.tsx @@ -1,5 +1,4 @@ "use client"; - import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; @@ -7,8 +6,6 @@ import { Tabs, TabsContent } from "@/components/ui/tabs"; import { Eye, EyeOff } from "lucide-react"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; -import { useRouter } from "next/navigation"; -import { z } from "zod"; import { Form, FormControl, @@ -19,47 +16,35 @@ import { } from "@/components/ui/form"; import { Logo } from "@/components/Logo"; import { signIn } from "next-auth/react"; +import { + usernameSchema, + emailSchema, + passwordSchema, +} from "@/lib/formSchemas/authFormSchemas"; +import { z } from "zod"; -//Form validation scheme -const formSchema = z +const signUpFormSchemaWithConfirmPassword = z .object({ - username: z - .string() - .min(3, "Username must be at least 3 characters.") - .max(20, "Username cannot exceed 20 characters.") - .regex( - /^[a-zA-Z0-9_-]+$/, - "Username can only contain letters, numbers, dashes, and underscores." - ), - email: z.string().email("Invalid email address."), - password: z - .string() - .min(8, "Password must be at least 8 characters.") - .regex(/[a-z]/, "Password must contain at least one lowercase letter.") - .regex(/[A-Z]/, "Password must contain at least one uppercase letter.") - .regex( - /[^a-zA-Z0-9]/, - "Password must contain at least one special character." - ), - confirmPassword: z - .string() - .min(8, "Password confirmation must be at least 8 characters."), + username: usernameSchema, + email: emailSchema, + password: passwordSchema, + confirmPassword: z.string(), }) .refine((data) => data.password === data.confirmPassword, { + message: "Passwords do not match", path: ["confirmPassword"], - message: "Passwords do not match.", }); -export default function Home() { - const router = useRouter(); +type SignUpFormWithConfirmPasswordData = z.infer< + typeof signUpFormSchemaWithConfirmPassword +>; - //Use state to toggle password visibility +export default function Home() { const [showPassword, setShowPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); - //Define form - const form = useForm>({ - resolver: zodResolver(formSchema), + const form = useForm({ + resolver: zodResolver(signUpFormSchemaWithConfirmPassword), defaultValues: { username: "", email: "", @@ -69,7 +54,7 @@ export default function Home() { }); //Define a submit handler - async function onSubmit(values: z.infer) { + async function onSubmit(values: SignUpFormWithConfirmPasswordData) { try { const response = await fetch("/api/auth/signup", { method: "POST", diff --git a/lib/formSchemas/authFormSchemas.ts b/lib/formSchemas/authFormSchemas.ts new file mode 100644 index 0000000..fdf8070 --- /dev/null +++ b/lib/formSchemas/authFormSchemas.ts @@ -0,0 +1,37 @@ +import { z } from "zod"; + +export const usernameSchema = z + .string() + .min(3, "Username must be at least 3 characters.") + .max(20, "Username cannot exceed 20 characters.") + .regex( + /^[a-zA-Z0-9_-]+$/, + "Username can only contain letters, numbers, dashes, and underscores." + ); +export const emailSchema = z.string().email("Invalid email address."); +export const passwordSchema = z + .string() + .min(8, "Password must be at least 8 characters.") + .regex(/[a-z]/, "Password must contain at least one lowercase letter.") + .regex(/[A-Z]/, "Password must contain at least one uppercase letter.") + .regex( + /[^a-zA-Z0-9]/, + "Password must contain at least one special character." + ); + +// Sign In +export const signInFormSchema = z.object({ + email: emailSchema, + password: passwordSchema, +}); + +// Sign Up +export const signUpFormSchema = z.object({ + username: usernameSchema, + email: emailSchema, + password: passwordSchema, +}); + +// Types for form data +export type SignInFormData = z.infer; +export type SignUpFormData = z.infer;