147 lines
4.6 KiB
TypeScript
147 lines
4.6 KiB
TypeScript
"use client";
|
||
import axios from "axios";
|
||
import { useStoreActions } from "@hooks/store";
|
||
import { FormEvent, MouseEvent, useEffect, useRef, useState } from "react";
|
||
import { ErrorRes } from "@appTypes/Axios";
|
||
|
||
interface AuthModalProps {
|
||
isOpen: boolean; // bool for if the modal should be visible
|
||
onClose: () => void; //A function that will be executed to close the modal
|
||
}
|
||
|
||
export default function AuthModal({ isOpen, onClose }: AuthModalProps) {
|
||
// todo add login successful message
|
||
const [isLogin, setIsLogin] = useState<boolean>(true);
|
||
const modalRef = useRef<HTMLDivElement>(null);
|
||
const [isFailed, setIsFailed] = useState<boolean>(false);
|
||
const [failMessage, setFailMessage] = useState<string>();
|
||
const setUser = useStoreActions((actions) => actions.setUser);
|
||
|
||
useEffect(() => {
|
||
if (isOpen) setIsLogin(true); // runs when isOpen changes, if it is true, the login is shown
|
||
}, [isOpen]);
|
||
|
||
if (!isOpen) return null; // if is open is false, the model isnt shown
|
||
|
||
const handleOverlayClick = (e: MouseEvent<HTMLDivElement>) => {
|
||
if (modalRef.current && !modalRef.current.contains(e.target as Node)) {
|
||
onClose();
|
||
}
|
||
};
|
||
|
||
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
||
e.preventDefault();
|
||
setIsFailed(false);
|
||
|
||
const formData = new FormData(e.currentTarget);
|
||
const email = formData.get("email") as string;
|
||
const password = formData.get("password") as string;
|
||
const name = isLogin ? undefined : (formData.get("name") as string);
|
||
|
||
try {
|
||
const res = await axios.post(
|
||
`/api/${isLogin ? "login" : "signup"}`,
|
||
isLogin ? { email, password } : { name, email, password },
|
||
{
|
||
headers: { "Content-Type": "application/json" },
|
||
}
|
||
);
|
||
|
||
if (res.status === 200) {
|
||
setUser(res.data.user);
|
||
onClose();
|
||
} else {
|
||
console.error("Unexpected response status:", res.status, res.data);
|
||
setFailMessage("Unexpected error occurred");
|
||
setIsFailed(true);
|
||
}
|
||
} catch (error) {
|
||
const axiosError = error as ErrorRes;
|
||
if (axiosError.response) {
|
||
const { status, data } = axiosError.response;
|
||
if (status >= 400 && status < 500) {
|
||
console.log("4xx error:", data);
|
||
setFailMessage(data.message || "Invalid request");
|
||
setIsFailed(true);
|
||
} else {
|
||
console.error("Server error:", data);
|
||
setFailMessage("Server error occurred");
|
||
setIsFailed(true);
|
||
}
|
||
} else {
|
||
console.error("Request failed:", axiosError.message);
|
||
setFailMessage("Network error occurred");
|
||
setIsFailed(true);
|
||
}
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center z-50" onClick={handleOverlayClick}>
|
||
<div ref={modalRef} className="bg-white rounded-lg shadow-lg p-6 w-full max-w-md relative">
|
||
<button
|
||
onClick={() => {
|
||
setIsFailed(false);
|
||
onClose();
|
||
}}
|
||
className="absolute text-xl top-0 right-2 text-neutral-500 hover:text-neutral-700"
|
||
aria-label="Close modal"
|
||
>
|
||
×
|
||
</button>
|
||
<h2 className="text-2xl font-bold text-center mb-4">{isLogin ? "Login" : "Sign Up"}</h2>
|
||
|
||
{/* Form */}
|
||
<form onSubmit={handleSubmit} className="space-y-4">
|
||
{!isLogin && (
|
||
<div>
|
||
<label className="block text-sm font-medium text-neutral-700">Full Name</label>
|
||
<input
|
||
type="text"
|
||
name="name"
|
||
className="mt-1 w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
required={!isLogin}
|
||
/>
|
||
</div>
|
||
)}
|
||
<div>
|
||
<label className="block text-sm font-medium text-neutral-700">Email</label>
|
||
<input
|
||
type="email"
|
||
name="email"
|
||
className="mt-1 w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
required
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-medium text-neutral-700">Password</label>
|
||
<input
|
||
type="password"
|
||
name="password"
|
||
className="mt-1 w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
required
|
||
/>
|
||
</div>
|
||
<div>
|
||
{isFailed && (
|
||
<div>
|
||
<label className="block text-sm font-medium text-red-700">{failMessage}</label>
|
||
</div>
|
||
)}
|
||
</div>
|
||
<button type="submit" className="w-full bg-blue-600 text-white p-2 rounded-md hover:bg-blue-700 transition">
|
||
{isLogin ? "Login" : "Sign Up"}
|
||
</button>
|
||
</form>
|
||
|
||
<p className="mt-4 text-center text-sm">
|
||
{isLogin ? "Need an account?" : "Already have an account?"}{" "}
|
||
<button onClick={() => setIsLogin(!isLogin)} className="text-blue-600 hover:underline">
|
||
{isLogin ? "Sign Up" : "Login"}
|
||
</button>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|