ADD: added basic backend function plus a mockup for a cli interface

This commit is contained in:
2025-12-13 21:44:48 +01:00
parent c6de2481e6
commit a047d57824
21 changed files with 657 additions and 51 deletions

View File

@@ -1,19 +1,37 @@
import { useState } from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { AuthProvider } from "./components/AuthContext";
import ProtectedRoute from "./components/ProtectedRoute";
import Landing from "./pages/Landing";
// import Dashboard from "./pages/Dashboard";
import Dashboard from "./pages/Dashboard"
import LoginModal from "./components/LoginModal";
export default function App() {
const [token, setToken] = useState(localStorage.getItem("token"));
const [showLogin, setShowLogin] = useState(false);
// const [showLogin, setShowLogin] = useState(false);
const [modalOpen, setModalOpen] = useState(false);
if (!token)
return (
<>
<Landing onLogin={() => setShowLogin(true)} />
{showLogin && <LoginModal onSuccess={setToken} />}
</>
<AuthProvider>
<BrowserRouter>
<LoginModal isOpen={modalOpen} onClose={() => setModalOpen(false)} />
<Routes>
<Route path="/" element={<Landing onLogin={() => setModalOpen(true)} />} />
<Route path="/dashboard" element={ <ProtectedRoute><Dashboard /></ProtectedRoute> }
/>
</Routes>
</BrowserRouter>
</AuthProvider>
// <>
// <Landing onLogin={() => setShowLogin(true)} />
// {showLogin && <LoginModal onSuccess={setToken} />}
// </>
);
return <Dashboard />;

View File

@@ -0,0 +1,20 @@
const API_URL = 'http://localhost:8080';
export async function loginUser(email:string, password: string) {
const res = await fetch(`${API_URL}/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
if (!res.ok) throw new Error('Login fehlgeschlagen');
return res.json(); // { token: string }
}
export async function fetchUserProfile(token: string) {
const res = await fetch(`${API_URL}/profile`, {
headers: { 'Authorization': `Bearer ${token}` },
});
if (!res.ok) throw new Error('Profil konnte nicht geladen werden');
return res.json(); // { id: number, email: string, name: string }
}

View File

@@ -0,0 +1,67 @@
import { createContext, useState, useContext, useEffect } from "react";
import type { JSX } from 'react';
import { getUserFromToken } from '../utils/jwt';
import { loginUser } from "../api/user";
type AuthUser = { token: string } | null;
type AuthContextType = {
token: string | null;
userId: string | null;
login: (token: string, userId: string, role?: string[]) => void;
logout: () => void;
};
const AuthContext = createContext<AuthContextType | null>(null);
export const AuthProvider = ({ children }: { children: JSX.Element }) => {
const [token, setToken] = useState<string | null>(null);
const [userId, setUserId] = useState<string | null>(null);
const [userEmail, setuserEmail] = useState<string | null>(null);
useEffect(() => {
const storedToken = localStorage.getItem('token');
const storedUserId = localStorage.getItem('userId');
if (!storedToken) {
return;
}
if (storedToken && storedUserId) {
setToken(storedToken);
setUserId(storedUserId);
}
const user = getUserFromToken(storedToken);
if (!user) {
logout(); // z.B. localStorage.clear() + navigate("/login")
return;
}
// ⏳ Logout bei Ablauf
const timeout = setTimeout(() => {
logout();
}, user.exp * 1000 - Date.now());
return () => clearTimeout(timeout);
}, []);
const login = (token: string, userId: string, role: string[] = []) => {
setToken(token);
setUserId(userId);
localStorage.setItem('token', token);
localStorage.setItem('userId', userId);
// localStorage.setItem('role', JSON.stringify(role)); // Store array as string
};
const logout = () => {
setToken(null);
setUserId(null);
localStorage.removeItem('token');
localStorage.removeItem('userId');
};
return (
<AuthContext.Provider value={{ token, userId, login, logout }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);

View File

@@ -1,23 +1,51 @@
import {loginUser} from "../api/user";
export default function LoginModal({ onSuccess }: any) {
const login = async () => {
const res = await fetch("http://localhost:8080/login", { method: "POST" });
const data = await res.json();
localStorage.setItem("token", data.token);
onSuccess(data.token);
};
export default function LoginModal({ isOpen, onSuccess }: any) {
if (!isOpen) return null; // 👈 THIS is the key
return (
<div className="fixed inset-0 bg-black/40 flex items-center justify-center">
<div className="bg-white rounded-2xl p-8 w-96 shadow-xl">
<h2 className="text-2xl font-bold mb-6">Login</h2>
<button
onClick={login}
className="w-full bg-indigo-600 text-white py-3 rounded-xl hover:bg-indigo-700"
>
Login as Demo
</button>
</div>
return(
<div className="fixed inset-0 z-50 bg-black/40 flex items-center justify-center">
<div className="bg-white rounded-2xl p-8 w-96 shadow-xl">
<h2 className="text-2xl font-bold mb-6">Login</h2>
<form
onSubmit={async (e: any) => {
e.preventDefault();
const fd = new FormData(e.currentTarget);
const email = fd.get("email");
const password = fd.get("password");
const res = await loginUser(email as string, password as string);
const data = await res.json();
localStorage.setItem("token", data.token);
onSuccess(data.token);
}}
className="space-y-4"
>
<input
name="email"
type="email"
placeholder="Email"
required
className="w-full border rounded-xl px-3 py-2"
/>
<input
name="password"
type="password"
placeholder="Password"
required
className="w-full border rounded-xl px-3 py-2"
/>
<button
type="submit"
className="w-full bg-indigo-600 text-white py-3 rounded-xl hover:bg-indigo-700"
>
Login
</button>
</form>
</div>
</div>
);
}

View File

@@ -0,0 +1,10 @@
import { Navigate } from "react-router-dom";
import { useAuth } from "../components/AuthContext";
import type { JSX } from 'react';
const ProtectedRoute = ({ children }: { children: JSX.Element }) => {
const { token } = useAuth() as { token?: string | null };
return token ? children : <Navigate to="/" />;
};
export default ProtectedRoute;

View File

@@ -0,0 +1,23 @@
// utils/jwt.ts
import { jwtDecode } from 'jwt-decode';
export interface TokenPayload {
userId: string;
email: string;
role: string[];
exp: number;
}
export function getUserFromToken(token: string): TokenPayload | null {
try {
const decoded = jwtDecode<TokenPayload>(token);
if (decoded.exp && decoded.exp < Date.now() / 1000) {
console.warn("Token ist abgelaufen");
return null;
}
return decoded;
} catch (error) {
console.error("Fehler beim Decodieren des Tokens:", error);
return null;
}
}