diff --git a/frontend/app/login/page.tsx b/frontend/app/login/page.tsx index 041d21d..1566307 100644 --- a/frontend/app/login/page.tsx +++ b/frontend/app/login/page.tsx @@ -5,6 +5,7 @@ import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import { isSupabaseConfigured, supabase } from "@/app/lib/supabase"; import { useToast } from "@/app/context/ToastContext"; +import { z } from "zod"; export default function Login() { const router = useRouter(); @@ -13,6 +14,8 @@ export default function Login() { const [password, setPassword] = useState(""); const [loading, setLoading] = useState(false); const [nextPath, setNextPath] = useState(null); + const [emailError, setEmailError] = useState(""); + const [passwordError, setPasswordError] = useState(""); const redirectPath = nextPath && nextPath.startsWith("/") ? nextPath : "/dashboard"; @@ -32,56 +35,76 @@ export default function Login() { return; } - setLoading(true); + setLoading(true); - const { data, error } = await supabase.auth.signInWithPassword({ - email, - password, - }); + const { data, error } = await supabase.auth.signInWithPassword({ + email, + password, + }); - console.log("LOGIN DATA:", data); - console.log("LOGIN ERROR:", error); + setLoading(false); - setLoading(false); + if (error) { + showToast(error.message, "error"); + return; + } + + const { data: { session } } = await supabase.auth.getSession(); + + if (session) { + router.push("/dashboard"); + router.refresh(); + } + }; - if (error) { - console.error(error); - alert(error.message); - showToast(error.message, "error"); - return; - } + const emailSchema = z.string().email("Invalid email address"); + const passwordSchema = z.string().min(8, "Password must be at least 8 characters"); - const { - data: { session }, - } = await supabase.auth.getSession(); + const validateEmail = () => { + const result = emailSchema.safeParse(email); + setEmailError(result.success ? "" : (result.error?.errors?.[0]?.message ?? "Invalid email")); + }; - console.log("SESSION:", session); + const validatePassword = () => { + const result = passwordSchema.safeParse(password); + setPasswordError(result.success ? "" : (result.error?.errors?.[0]?.message ?? "Invalid password")); + }; - if (session) { - router.push("/dashboard"); - router.refresh(); - } -}; + const isFormValid = + emailSchema.safeParse(email).success && + passwordSchema.safeParse(password).success; return (
-
{ e.preventDefault(); handleLogin(); }} className="bg-zinc-900 p-6 rounded-xl w-80" > + { e.preventDefault(); handleLogin(); }} + className="bg-zinc-900 p-6 rounded-xl w-80" + >

Login

setEmail(e.target.value)} + value={email} + onChange={(e) => { setEmail(e.target.value); setEmailError(""); }} + onBlur={validateEmail} /> - + {emailError && ( +

{emailError}

+ )} + setPassword(e.target.value)} + value={password} + onChange={(e) => { setPassword(e.target.value); setPasswordError(""); }} + onBlur={validatePassword} /> + {passwordError && ( +

{passwordError}

+ )}
- +
); -} +} \ No newline at end of file diff --git a/frontend/app/signup/page.tsx b/frontend/app/signup/page.tsx index c6b7616..52f7c50 100644 --- a/frontend/app/signup/page.tsx +++ b/frontend/app/signup/page.tsx @@ -4,6 +4,7 @@ import Link from "next/link"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import { supabase } from "@/app/lib/supabase"; +import { signupSchema } from "@/lib/validations/auth"; function generatePassword(length = 14): string { const upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; @@ -45,6 +46,9 @@ export default function Signup() { const [copied, setCopied] = useState(false); const [errorMsg, setErrorMsg] = useState(""); const [successMsg, setSuccessMsg] = useState(""); + const [emailError, setEmailError] = useState(""); + const [usernameError, setUsernameError] = useState(""); + const [passwordStrength, setPasswordStrength] = useState<"" | "weak" | "medium" | "strong">(""); useEffect(() => { const params = new URLSearchParams(window.location.search); @@ -138,6 +142,26 @@ export default function Signup() { } }; + const validateEmailField = () => { + const result = signupSchema.shape.email.safeParse(email); + setEmailError(result.success ? "" : (result.error?.errors?.[0]?.message ?? "Invalid email")); + }; + + const validateUsernameField = () => { + const result = signupSchema.shape.username.safeParse(username); + setUsernameError(result.success ? "" : (result.error?.errors?.[0]?.message ?? "Invalid username")); + }; + + const getPasswordStrength = (pwd: string): "" | "weak" | "medium" | "strong" => { + if (!pwd) return ""; + const hasUpper = /[A-Z]/.test(pwd); + const hasLower = /[a-z]/.test(pwd); + const hasNumber = /[0-9]/.test(pwd); + if (pwd.length >= 10 && hasUpper && hasLower && hasNumber) return "strong"; + if (pwd.length >= 8) return "medium"; + return "weak"; + }; + const inputClass = "w-full p-2 mb-3 bg-zinc-800 rounded border border-zinc-700 focus:outline-none focus:border-indigo-500"; @@ -157,20 +181,28 @@ export default function Signup() { {/* Username */} setUsername(e.target.value.replace(/\s/g, ""))} + onChange={(e) => { setUsername(e.target.value.replace(/\s/g, "")); setUsernameError(""); }} + onBlur={validateUsernameField} /> + {usernameError && ( +

{usernameError}

+ )} {/* Email */} setEmail(e.target.value)} + onChange={(e) => { setEmail(e.target.value); setEmailError(""); }} + onBlur={validateEmailField} /> + {emailError && ( +

{emailError}

+ )} {/* Mobile */} setPassword(e.target.value)} + onChange={(e) => { + setPassword(e.target.value); + setPasswordStrength(getPasswordStrength(e.target.value)); + }} />