2025-04-17 15:18:06 +01:00
// tells React if you're using its "server-side rendering" features. this component runs only on the client-side (browser)
2025-03-19 19:20:18 +00:00
"use client" ;
2025-04-17 15:18:06 +01:00
// importing React hooks or utilities that enhance functionality inside the component
2025-03-19 19:20:18 +00:00
import { useState , FormEvent , useRef , MouseEvent , useEffect } from "react" ;
2025-04-17 15:18:06 +01:00
/ *
useState : Used to manage state ( data that changes over time ) .
FormEvent : TypeScript definition for form - related events like submission .
useRef : Used to access the DOM ( the web page elements ) directly .
MouseEvent : TypeScript definition for mouse - related events like clicks .
useEffect : Hook for running side effects ( e . g . , code that runs when something changes )
* /
2025-03-19 19:20:18 +00:00
2025-04-17 15:18:06 +01:00
// defining a type for the props (inputs) that AuthModal expects
2025-03-19 19:20:18 +00:00
interface AuthModalProps {
2025-04-17 15:18:06 +01:00
isOpen : boolean ; // bool for if the modal should be visible
onClose : ( ) = > void ; //A function that will be executed to close the modal
2025-03-19 19:20:18 +00:00
}
2025-04-17 15:18:06 +01:00
// creates a React functional component
export default function AuthModal ( { isOpen , onClose } : AuthModalProps ) { //AuthModalProps ensures TypeScript validates the props to match the type definition above
2025-03-19 19:20:18 +00:00
const [ isLogin , setIsLogin ] = useState < boolean > ( true ) ;
const modalRef = useRef < HTMLDivElement > ( null ) ;
2025-04-17 15:18:06 +01:00
const [ isFailed , setIsFailed ] = useState < boolean > ( false ) ;
2025-04-28 09:48:02 +01:00
const [ failMessage , setFailMessage ] = useState < boolean > ( false ) ;
2025-04-17 15:18:06 +01:00
/ *
useState is a React Hook that declares state variables in a functional component . It returns a two - element array
The state variable ( isLogin ) : Represents the current state value ( e . g . , true initially ) . This is the value you can use in your component .
The state updater function ( setIsLogin ) : A function that allows you to update the state variable . React takes care of re - rendering the component when the state is updated
* /
/ *
modalRef allows direct access to the modal DOM element ( the container div for the modal ) . This is useful for detecting if the user clicks outside of the modal
* /
2025-03-19 19:20:18 +00:00
2025-04-17 15:18:06 +01:00
// useEffect runs code after the component renders or when a dependency changes (in this case, isOpen as seen in the end [])
2025-03-19 19:20:18 +00:00
useEffect ( ( ) = > {
2025-04-17 15:18:06 +01:00
if ( isOpen ) setIsLogin ( true ) ; // runs when isOpen changes, if it is true, the login is shown
2025-03-19 19:20:18 +00:00
} , [ isOpen ] ) ;
2025-04-17 15:18:06 +01:00
if ( ! isOpen ) return null ; // if is open is false, the model isnt shown
2025-03-19 19:20:18 +00:00
2025-04-17 15:18:06 +01:00
// this is an arrow function. e: is used to specify that an event object is expected
2025-03-19 19:20:18 +00:00
const handleOverlayClick = ( e : MouseEvent < HTMLDivElement > ) = > {
if ( modalRef . current && ! modalRef . current . contains ( e . target as Node ) ) {
onClose ( ) ;
}
} ;
2025-04-17 15:18:06 +01:00
/ *
. current gives a reference to the actual DOM element ( the inner modal container )
e . target refers to the specific element the mouse click event occurred on
. contains ( e . target ) checks if the clicked element ( e . target ) is inside the modal ( modalRef . current )
as Node specifies e . target is a Node type to TypeScript
? what is a node
/ ! . . . m e a n s w e g e t t r u e i f t h e c l i c k i s o u t s i d e t h e m o d a l
Logic : if modalRef . current exists and the click target ( e . target ) is not inside the modal , then the condition is true
This means the user clicked outside the modal
* /
// LS - The following bit contains the more important code for what I'm focused on
/ *
Note : handleSubmit is typically used as a event handler for submitting a form in react .
For example :
< form onSubmit = { handleSubmit } >
< input type = "text" name = "example" / >
< button type = "submit" > Submit < / button >
< / form >
* /
/ *
e is the parameter passsed into the function
async indicates the function runs asyncronously , meaning it performs tasks which take time .
an example of this would be API calls
* /
2025-03-19 19:20:18 +00:00
const handleSubmit = async ( e : FormEvent < HTMLFormElement > ) = > {
2025-04-17 15:18:06 +01:00
e . preventDefault ( ) ; // stops page from refreshing
setIsFailed ( false )
const formData = new FormData ( e . currentTarget ) ; // new variable of class FormData is created. This is part of the standard Web API included in modern web browsers
const email = formData . get ( "email" ) as string ; // gets email from form response
const password = formData . get ( "password" ) as string ; // gets password from form response
const name = isLogin ? undefined : ( formData . get ( "name" ) as string ) ; // if the form is in login mode, the name is undefine, otherwise the name is a value obtained from the response
2025-03-19 19:20:18 +00:00
2025-04-17 15:18:06 +01:00
let endpoint = isLogin ? "/api/login" : "/api/signup" ; // sets endpoint for backend code (either sign up or login)
const body = isLogin ? { email , password } : { name : name ! , email , password } ; // creates a json body for the backend
2025-03-19 19:20:18 +00:00
try {
2025-04-17 15:18:06 +01:00
console . log ( "Sending data to API" ) ;
const res = await fetch ( endpoint , { // sends a request to the server at the end point
method : "POST" , // Post is used since the form submission modifies the server side state
headers : { "Content-Type" : "application/json" } , //indicates it expects a json object returned
body : JSON.stringify ( body ) , // converts the body to a JSON string to be sent
2025-03-19 19:20:18 +00:00
} ) ;
2025-04-17 15:18:06 +01:00
if ( res . ok ) { //res.ok checks if the response is between 200-299
2025-03-19 19:20:18 +00:00
console . log ( "Success!" ) ;
2025-04-17 15:18:06 +01:00
onClose ( ) ; // closes UI
2025-04-28 09:48:02 +01:00
} else if ( res . status >= 400 && res . status < 500 ) {
const responseBody = await res . json ( )
console . log ( "4xx error:" , responseBody . message )
setFailMessage ( responseBody . message )
2025-04-17 15:18:06 +01:00
setIsFailed ( true )
} else {
console . error ( "Error:" , await res . text ( ) ) ; // logs error with error message sent to console
2025-03-19 19:20:18 +00:00
}
2025-04-17 15:18:06 +01:00
} catch ( error ) { // catches any errors (e.g. Not connected to network)
2025-03-19 19:20:18 +00:00
console . error ( "Request failed:" , error instanceof Error ? error.message : String ( error ) ) ;
}
} ;
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" >
2025-04-28 09:48:02 +01:00
< button onClick = { ( ) = > { setIsFailed ( false ) ; onClose ( ) ; } } className = "absolute text-xl top-0 right-2 text-gray-500 hover:text-gray-700" aria-label = "Close modal" >
2025-03-19 19:20:18 +00:00
×
< / 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-gray-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-gray-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-gray-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 >
2025-04-17 15:18:06 +01:00
< div >
{ isFailed && (
< div >
2025-04-28 09:48:02 +01:00
< label className = "block text-sm font-medium text-red-700" > { failMessage } < / label >
2025-04-17 15:18:06 +01:00
< / div >
)
}
< / div >
2025-03-19 19:20:18 +00:00
< 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 >
) ;
}