Login backend code started

Site reacts correctly to different inputs with login
This commit is contained in:
Lukeshan Thananchayan 2025-04-17 15:18:06 +01:00
parent 866ea00627
commit fe5c231e89
6 changed files with 876 additions and 43 deletions

706
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -7,14 +7,22 @@
"dev": "next dev --turbopack", "dev": "next dev --turbopack",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint" "lint": "next lint",
"start:server": "dist/index.js"
}, },
"dependencies": { "dependencies": {
"@prisma/client": "^6.4.1", "@prisma/client": "^6.4.1",
"@types/mapbox-gl": "^3.4.1", "@types/mapbox-gl": "^3.4.1",
"bcryptjs": "^3.0.2",
"body-parser": "^2.2.0",
"csv-parser": "^3.2.0",
"express": "^5.1.0",
"fs": "^0.0.1-security",
"jwt-decode": "^4.0.0",
"leaflet": "^1.9.4", "leaflet": "^1.9.4",
"mapbox-gl": "^3.10.0", "mapbox-gl": "^3.10.0",
"next": "15.1.7", "next": "15.1.7",
"path": "^0.12.7",
"prisma": "^6.4.1", "prisma": "^6.4.1",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
@ -24,6 +32,7 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3", "@eslint/eslintrc": "^3",
"@types/express": "^5.0.1",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",

View File

@ -0,0 +1,72 @@
import { NextResponse } from "next/server";
import path from "path";
import fs from "fs";
import csv from "csv-parser";
type User = {
name: string;
email: string;
password: string;
}
/**
* @returns {array} - Array of Objects containing user data
*/
async function readUserCsv(): Promise<User[]>{
return new Promise((resolve, reject) => {
// Dynamic csv location generation
let csvPath = path.dirname(__dirname); // /login
csvPath = path.dirname(csvPath); // /api
csvPath = path.dirname(csvPath); // /app
csvPath = path.dirname(csvPath); // /src
csvPath = path.dirname(csvPath); // /[project] // ? not sure why this dirfolder is here...
csvPath = path.dirname(csvPath); // /termor-tracker
csvPath = path.join(csvPath,"src","databases","Users.csv");
//Forms array for user data
let results : User[] = [];
//Reads data and adds it to results
fs.createReadStream(csvPath)
.pipe(csv())
.on("data", (data) => results.push(data))
.on("end", () => {
resolve(results)
})
.on("error", (error) => {
reject(error)
})
});
}
function findUserByEmail(users: User[], email: string): User | undefined {
return users.find((user) => user.email === email);
}
export async function POST(request: Request) {
try {
const body = await request.json(); // Parse incoming JSON data
const {email, password } = body;
console.log("Login API received data");
const userData = await readUserCsv();
console.log(userData)
console.log("Email:", email); // ! remove
console.log("Password:", password);// ! remove
const foundUser = findUserByEmail(userData,email)
if (foundUser && foundUser.password === password) {
console.log("User Details Correct")
return NextResponse.json({ message: "Login successful!" }, { status: 200 });
} else {
console.log("User email or password is invalid")
return NextResponse.json({ message: "Email and/or password are invalid" }, { status: 401 });
}
} catch (error) {
console.error("Error in signup endpoint:", error);
return NextResponse.json({ message: "Internal Server Error" }, { status: 500 });
}
}

View File

@ -0,0 +1,19 @@
import { NextResponse } from "next/server";
export async function POST(request: Request) {
try {
const body = await request.json(); // Parse incoming JSON data
const { name, email, password } = body;
console.log("Signup API received data:");
console.log("Name:", name);
console.log("Email:", email);
console.log("Password:", password);
// For now, just respond back with a success message
return NextResponse.json({ message: "Signup successful!" }, { status: 201 });
} catch (error) {
console.error("Error in signup endpoint:", error);
return NextResponse.json({ message: "Internal Server Error" }, { status: 500 });
}
}

View File

@ -1,51 +1,102 @@
// tells React if you're using its "server-side rendering" features. this component runs only on the client-side (browser)
"use client"; "use client";
// importing React hooks or utilities that enhance functionality inside the component
import { useState, FormEvent, useRef, MouseEvent, useEffect } from "react"; import { useState, FormEvent, useRef, MouseEvent, useEffect } from "react";
/*
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)
*/
// defining a type for the props (inputs) that AuthModal expects
interface AuthModalProps { interface AuthModalProps {
isOpen: boolean; isOpen: boolean; // bool for if the modal should be visible
onClose: () => void; onClose: () => void; //A function that will be executed to close the modal
} }
// creates a React functional component
export default function AuthModal({ isOpen, onClose }: AuthModalProps) { export default function AuthModal({ isOpen, onClose }: AuthModalProps) { //AuthModalProps ensures TypeScript validates the props to match the type definition above
const [isLogin, setIsLogin] = useState<boolean>(true); const [isLogin, setIsLogin] = useState<boolean>(true);
const modalRef = useRef<HTMLDivElement>(null); const modalRef = useRef<HTMLDivElement>(null);
const [isFailed, setIsFailed] = useState<boolean>(false);
/*
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
*/
// useEffect runs code after the component renders or when a dependency changes (in this case, isOpen as seen in the end [])
useEffect(() => { useEffect(() => {
if (isOpen) setIsLogin(true); if (isOpen) setIsLogin(true); // runs when isOpen changes, if it is true, the login is shown
}, [isOpen]); }, [isOpen]);
if (!isOpen) return null; if (!isOpen) return null; // if is open is false, the model isnt shown
// this is an arrow function. e: is used to specify that an event object is expected
const handleOverlayClick = (e: MouseEvent<HTMLDivElement>) => { const handleOverlayClick = (e: MouseEvent<HTMLDivElement>) => {
if (modalRef.current && !modalRef.current.contains(e.target as Node)) { if (modalRef.current && !modalRef.current.contains(e.target as Node)) {
onClose(); onClose();
} }
}; };
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => { /*
e.preventDefault(); .current gives a reference to the actual DOM element (the inner modal container)
const formData = new FormData(e.currentTarget); e.target refers to the specific element the mouse click event occurred on
const email = formData.get("email") as string; .contains(e.target) checks if the clicked element (e.target) is inside the modal (modalRef.current)
const password = formData.get("password") as string; as Node specifies e.target is a Node type to TypeScript
const name = isLogin ? undefined : (formData.get("name") as string); ? what is a node
/!... means we get true if the click is outside the modal
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
*/
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
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
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
const endpoint = isLogin ? "/api/login" : "/api/signup";
const body = isLogin ? { email, password } : { name: name!, email, password };
try { try {
const res = await fetch(endpoint, { console.log("Sending data to API");
method: "POST", const res = await fetch(endpoint, { // sends a request to the server at the end point
headers: { "Content-Type": "application/json" }, method: "POST", // Post is used since the form submission modifies the server side state
body: JSON.stringify(body), 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
}); });
if (res.ok) { if (res.ok) { //res.ok checks if the response is between 200-299
console.log("Success!"); console.log("Success!");
onClose(); onClose(); // closes UI
} else if (res.status === 401){
console.log("Incorrect details provided")
setIsFailed(true)
} else{ } else{
console.error("Error:", await res.text()); console.error("Error:", await res.text()); // logs error with error message sent to console
} }
} catch (error) { } catch (error) {// catches any errors (e.g. Not connected to network)
console.error("Request failed:", error instanceof Error ? error.message : String(error)); console.error("Request failed:", error instanceof Error ? error.message : String(error));
} }
}; };
@ -89,6 +140,14 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) {
required required
/> />
</div> </div>
<div>
{ isFailed && (
<div>
<label className="block text-sm font-medium text-red-700">Incorrect email or password</label>
</div>
)
}
</div>
<button type="submit" className="w-full bg-blue-600 text-white p-2 rounded-md hover:bg-blue-700 transition"> <button type="submit" className="w-full bg-blue-600 text-white p-2 rounded-md hover:bg-blue-700 transition">
{isLogin ? "Login" : "Sign Up"} {isLogin ? "Login" : "Sign Up"}
</button> </button>

View File

@ -1 +1,5 @@
Users name,email,password
Lukeshan Thananchayan,lukeshan@mail.com,TaylorSwift123
Emily Neighbour Carter,emily@mail.com,HarryStyles000
Izzy Patteron,izzy@mail.com,OliviaRodrigez420
Tim Howitz,tim@mail.com,TravisBarker182
1 Users name email password
2 Lukeshan Thananchayan lukeshan@mail.com TaylorSwift123
3 Emily Neighbour Carter emily@mail.com HarryStyles000
4 Izzy Patteron izzy@mail.com OliviaRodrigez420
5 Tim Howitz tim@mail.com TravisBarker182