diff --git a/src/app/auth/error/page.tsx b/src/app/auth/error/page.tsx deleted file mode 100644 index 7552b02..0000000 --- a/src/app/auth/error/page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function AuthError() { - return
Authentication Error
; -} diff --git a/src/components/auth/action.ts b/src/components/auth/action.ts new file mode 100644 index 0000000..e4718fa --- /dev/null +++ b/src/components/auth/action.ts @@ -0,0 +1,50 @@ +"use server"; + +import { revalidatePath } from "next/cache"; +import { createSupabaseClient } from "@/lib/supabase/serverComponentClient"; +import { redirect } from "next/navigation"; + +export async function login(formData: FormData) { + const supabase = await createSupabaseClient(); + + const data = { + email: formData.get("email") as string, + password: formData.get("password") as string, + }; + + const { error } = await supabase.auth.signInWithPassword(data); + + if (error) { + throw error; + } + + revalidatePath("/", "layout"); + redirect("/"); +} + +export async function signup(formData: FormData) { + const supabase = await createSupabaseClient(); + + const data = { + email: formData.get("email") as string, + password: formData.get("password") as string, + }; + + const { error } = await supabase.auth.signUp(data); + + if (error) { + throw error; + } + + revalidatePath("/", "layout"); + redirect("/"); +} + +export async function logout() { + const supabase = await createSupabaseClient(); + const { error } = await supabase.auth.signOut(); + + if (error) { + throw new Error("Logout failed: " + error.message); + } +} diff --git a/src/components/auth/loginForm.tsx b/src/components/auth/loginForm.tsx index c0c7ac1..3cc7c2c 100644 --- a/src/components/auth/loginForm.tsx +++ b/src/components/auth/loginForm.tsx @@ -1,40 +1,70 @@ "use client"; -import React from "react"; -import { createSupabaseClient } from "@/lib/supabase/clientComponentClient"; -import { useState } from "react"; +import React, { useState } from "react"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; -import { useRouter } from "next/navigation"; +import { login } from "./action"; +import { LoginFormSchema } from "@/types/schemas/authentication.schema"; +import toast from "react-hot-toast"; export function LoginForm() { - const router = useRouter(); - const supabase = createSupabaseClient(); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); + const [errors, setErrors] = useState<{ email?: string; password?: string; server?: string }>({}); - const handleLogin = async (event: React.MouseEvent) => { + const handleSubmit = async (event: React.FormEvent) => { event.preventDefault(); - await supabase.auth.signInWithPassword({ - email, - password, - }); - router.push("/"); + + const formData = { email, password }; + + const result = LoginFormSchema.safeParse(formData); + + if (!result.success) { + const formErrors: { email?: string; password?: string } = {}; + result.error.errors.forEach((error) => { + formErrors[error.path[0] as keyof typeof formErrors] = error.message; + }); + setErrors(formErrors); + return; + } + + setErrors({}); + + const form = new FormData(); + form.append("email", email); + form.append("password", password); + + try { + await login(form); + toast.success("Login succesfully!"); + } catch (authError: any) { + setErrors((prevErrors) => ({ + ...prevErrors, + server: authError.message || "An error occurred during login.", + })); + } }; return ( -
- setEmail(e.target.value)} placeholder="Email" /> - setPassword(e.target.value)} - placeholder="Password" - /> - -
+ ); } diff --git a/src/components/auth/logoutButton.tsx b/src/components/auth/logoutButton.tsx index d84b562..502d1b8 100644 --- a/src/components/auth/logoutButton.tsx +++ b/src/components/auth/logoutButton.tsx @@ -1,24 +1,30 @@ "use client"; -import { createSupabaseClient } from "@/lib/supabase/clientComponentClient"; -import { usePathname } from "next/navigation"; -import { useRouter } from "next/navigation"; +import { logout } from "./action"; // Adjust the import path accordingly +import { usePathname, useRouter } from "next/navigation"; +import toast from "react-hot-toast"; export function LogoutButton() { - const supabase = createSupabaseClient(); const pathname = usePathname(); const router = useRouter(); const handleLogout = async () => { - await supabase.auth.signOut(); - - if (pathname === "/") { - window.location.reload(); - } else { - await router.push("/"); - window.location.reload(); + try { + await logout(); + if (pathname === "/") { + window.location.reload(); + } else { + await router.push("/"); + window.location.reload(); + } + } catch (error: any) { + toast.error(error.message || "An error occurred during logout."); } }; - return ; + return ( +
+ +
+ ); } diff --git a/src/components/auth/signupForm.tsx b/src/components/auth/signupForm.tsx index c4f2885..a589f15 100644 --- a/src/components/auth/signupForm.tsx +++ b/src/components/auth/signupForm.tsx @@ -1,50 +1,65 @@ "use client"; -import React from "react"; -import { createSupabaseClient } from "@/lib/supabase/clientComponentClient"; -import { useState } from "react"; +import React, { useState } from "react"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { useRouter } from "next/navigation"; import toast from "react-hot-toast"; +import { signup } from "./action"; +import { signupSchema } from "@/types/schemas/authentication.schema"; export function SignupForm() { const router = useRouter(); - const supabase = createSupabaseClient(); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); + const [error, setError] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); - const handleSignup = async (event: React.MouseEvent) => { + const handleSignup = async (event: React.FormEvent) => { event.preventDefault(); - if (password !== confirmPassword) { - alert("Passwords do not match!"); + const parsedData = signupSchema.safeParse({ + email, + password, + confirmPassword, + }); + + if (!parsedData.success) { + setError(parsedData.error.errors[0].message); return; } - const { error } = await supabase.auth.signUp({ - email, - password, - }); + const formData = new FormData(); + formData.append("email", email); + formData.append("password", password); + formData.append("confirmPassword", confirmPassword); - if (error) { - toast.error(error.message); - } else { + try { + await signup(formData); toast.success("Account created successfully!"); router.push("/"); + } catch (error: any) { + setError(error.message); } }; return ( -
- setEmail(e.target.value)} placeholder="Email" /> +
+ setEmail(e.target.value)} + placeholder="Email" + required + /> setPassword(e.target.value)} placeholder="Password" + required /> setConfirmPassword(e.target.value)} placeholder="Confirm Password" + required /> - -
+ ); } diff --git a/src/types/schemas/authentication.schema.ts b/src/types/schemas/authentication.schema.ts new file mode 100644 index 0000000..5733333 --- /dev/null +++ b/src/types/schemas/authentication.schema.ts @@ -0,0 +1,17 @@ +import * as z from "zod"; + +export const LoginFormSchema = z.object({ + email: z.string().email("Invalid email address"), + password: z.string().min(6, "Password must be at least 6 characters"), +}); + +export const signupSchema = z + .object({ + email: z.string().email("Invalid email format"), + password: z.string().min(6, "Password must be at least 6 characters long"), + confirmPassword: z.string().min(6, "Confirm password must be at least 6 characters long"), + }) + .refine((data) => data.password === data.confirmPassword, { + message: "Passwords must match", + path: ["confirmPassword"], + });