From 655ec610fcce8dd7748f10772d520bdff4f7c78e Mon Sep 17 00:00:00 2001 From: Arne Rief Date: Fri, 19 Dec 2025 20:03:03 +0100 Subject: Basic setup & login --- frontend/src/pages/Login.tsx | 148 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 frontend/src/pages/Login.tsx (limited to 'frontend/src/pages/Login.tsx') diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx new file mode 100644 index 0000000..2e99a2f --- /dev/null +++ b/frontend/src/pages/Login.tsx @@ -0,0 +1,148 @@ +import { useEffect, useState, type ChangeEvent, type FormEvent } from "react"; +import { useNavigate } from "react-router-dom"; +import { FadeLoader } from "react-spinners"; +import Logo from "../components/Logo"; +import API_URL from "../config"; +import "../styles/Button.css"; +import "../styles/Login.css"; +import type { + ErrorResponse, + LoginFormData, + LoginResponse, +} from "../types/login"; + +const EMPTY_FORM_DATA: LoginFormData = { + email: "", + password: "", +}; + +function Login() { + const navigate = useNavigate(); + + const [errorMessage, setErrorMessage] = useState(""); + const [formData, setFormData] = useState(EMPTY_FORM_DATA); + const [isLoading, setIsLoading] = useState(false); + + function handleUserInput(event: ChangeEvent) { + if (errorMessage) { + setErrorMessage(""); + } + + setFormData((oldFormData) => ({ + ...oldFormData, + [event.target.name]: event.target.value, + })); + } + + async function handleLogin(event: FormEvent) { + event.preventDefault(); + setErrorMessage(""); + + if (!formData.email || !formData.password) { + setErrorMessage("E-mail address and password are required."); + return; + } + + setIsLoading(true); + + try { + const response = await fetch(`${API_URL}/auth/login`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(formData), + }); + + if (!response.ok) { + const errorData: ErrorResponse = await response.json(); + throw new Error( + errorData.message || + `Login failed, status: ${response.status}` + ); + } + + const data: LoginResponse = await response.json(); + + localStorage.setItem("token-robot-tracker", data.token); + localStorage.setItem("user", JSON.stringify(data.user)); + setFormData(EMPTY_FORM_DATA); + + navigate("/dashboard", { replace: true }); + } catch (error) { + console.error(error); + + if (error instanceof Error) { + setErrorMessage(error.message); + } else { + setErrorMessage("An unexpected error occurred."); + } + } finally { + setIsLoading(false); + } + } + + // Clear local storage on component mounting + useEffect(() => { + localStorage.removeItem("token-robot-tracker"); + localStorage.removeItem("user"); + }, []); + + return ( +
+
+ +

🤖 Please log in to use the app 🤖

+ + {/* "noValidate" to enable manual errorMessage in handleLogin; alternatively omit "required" */} +
+
+ + +
+ +
+ + +
+ + {errorMessage && ( +
{errorMessage}
+ )} + + +
+
+
+ ); +} + +export default Login; -- cgit v1.2.3