summaryrefslogtreecommitdiff
path: root/frontend/src/pages
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/pages')
-rw-r--r--frontend/src/pages/Dashboard.tsx5
-rw-r--r--frontend/src/pages/Login.tsx148
2 files changed, 153 insertions, 0 deletions
diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx
new file mode 100644
index 0000000..f306c53
--- /dev/null
+++ b/frontend/src/pages/Dashboard.tsx
@@ -0,0 +1,5 @@
+function Dashboard() {
+ return <h1>Placeholder</h1>;
+}
+
+export default Dashboard;
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<string>("");
+ const [formData, setFormData] = useState<LoginFormData>(EMPTY_FORM_DATA);
+ const [isLoading, setIsLoading] = useState<boolean>(false);
+
+ function handleUserInput(event: ChangeEvent<HTMLInputElement>) {
+ if (errorMessage) {
+ setErrorMessage("");
+ }
+
+ setFormData((oldFormData) => ({
+ ...oldFormData,
+ [event.target.name]: event.target.value,
+ }));
+ }
+
+ async function handleLogin(event: FormEvent<HTMLFormElement>) {
+ 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 (
+ <div className="login-page">
+ <div className="login-card">
+ <Logo />
+ <p className="subtitle">🤖 Please log in to use the app 🤖</p>
+
+ {/* "noValidate" to enable manual errorMessage in handleLogin; alternatively omit "required" */}
+ <form className="login-form" onSubmit={handleLogin} noValidate>
+ <div className="form-group">
+ <label htmlFor="email">E-Mail</label>
+ <input
+ type="email"
+ id="email"
+ name="email"
+ value={formData?.email || ""}
+ onChange={handleUserInput}
+ required
+ placeholder="Your e-mail..."
+ disabled={isLoading}
+ />
+ </div>
+
+ <div className="form-group">
+ <label htmlFor="password">Password</label>
+ <input
+ type="password"
+ id="password"
+ name="password"
+ value={formData?.password || ""}
+ onChange={handleUserInput}
+ required
+ placeholder="Your password..."
+ disabled={isLoading}
+ />
+ </div>
+
+ {errorMessage && (
+ <div className="error-message">{errorMessage}</div>
+ )}
+
+ <button
+ className="btn btn-start"
+ disabled={isLoading}
+ type="submit"
+ >
+ {isLoading ? (
+ <div className="loading-spinner-container">
+ <FadeLoader />
+ </div>
+ ) : (
+ "Login"
+ )}
+ </button>
+ </form>
+ </div>
+ </div>
+ );
+}
+
+export default Login;