Added routing, jwt handling and Dashboard.jsx

This commit is contained in:
lobo committed 2026-04-04 10:49:14 +02:00
1 parent af4eb58e35
commit 6e2a23c3ef
6 files changed
+164 -44

No files matched your search

+10
View File
@@ -12,6 +12,16 @@
"ERROR_EMAIL": "Gib eine gültige Email-Adresse ein mit '@' und mit '.'.",
"ERROR_PASSWORD": "Das Passwort darf nicht leer sein."
},
"INFO": {
"ERROR": {
"LOAD_PROFILE": "Fehler beim Laden der Profildaten.",
"NETWORK_FAILURE": "Netzwerkfehler. Konnte keine Verbindung zum Server herstellen."
}
},
"DASHBOARD": {
"LOADING": "Lade Dashboard ...",
"HELLO": "Hallo"
},
"welcome": "Dein Game-Backlog unter Kontrolle",
"login": "Anmelden"
}
+10
View File
@@ -12,6 +12,16 @@
"ERROR_EMAIL": "Enter a valid email address with '@' and with '.'.",
"ERROR_PASSWORD": "The password must have at least one character."
},
"INFO": {
"ERROR": {
"LOAD_PROFILE": "Error during loading of the user profile data.",
"NETWORK_FAILURE": "Network failure. Could not connect to server."
}
},
"DASHBOARD": {
"LOADING": "Loading dashboard ...",
"HELLO": "Hello"
},
"welcome": "Control your game backlog",
"login": "Login"
}
+35 -35
View File
@@ -1,10 +1,11 @@
import {BrowserRouter, Link, Route, Routes} from 'react-router-dom';
import {Link, Route, Routes} from 'react-router-dom';
import {useTranslation} from 'react-i18next';
import {useEffect, useState} from 'react';
import LandingPage from './pages/LandingPage';
import LoginPage from './pages/LoginPage';
import ImprintPage from './pages/ImprintPage';
import Dashboard from "./pages/Dashboard.jsx";
function App() {
const {t, i18n} = useTranslation();
@@ -28,43 +29,42 @@ function App() {
};
return (
<BrowserRouter>
<div className="min-h-screen flex flex-col bg-slate-50 dark:bg-slate-950 transition-colors duration-300 font-sans">
<header className="p-4 flex justify-between items-center border-b border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900 sticky top-0 z-50">
<Link to="/" className="font-black text-blue-600 dark:text-blue-500 text-2xl flex items-center tracking-tight">
<i className="mdi mdi-controller mr-2 text-3xl"></i> Game Log
<div className="min-h-screen flex flex-col bg-slate-50 dark:bg-slate-950 transition-colors duration-300 font-sans">
<header className="p-4 flex justify-between items-center border-b border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900 sticky top-0 z-50">
<Link to="/" className="font-black text-blue-600 dark:text-blue-500 text-2xl flex items-center tracking-tight">
<i className="mdi mdi-controller mr-2 text-3xl"></i> Game Log
</Link>
<div className="flex gap-5 items-center">
<button onClick={toggleLanguage} className="text-sm font-bold text-slate-600 dark:text-slate-300 hover:text-blue-500 transition-colors">
{i18n.language.toUpperCase()}
</button>
<button onClick={() => {
setIsDarkMode(!isDarkMode);
}} className="text-xl text-slate-600 dark:text-slate-300 hover:text-blue-500 transition-colors">
<i className={isDarkMode ? "mdi mdi-weather-sunny" : "mdi mdi-weather-night"}></i>
</button>
<Link to="/login" className="bg-blue-600 hover:bg-blue-500 text-white px-5 py-2 rounded-lg font-bold transition-all shadow-md shadow-blue-500/20">
{t('login')}
</Link>
</div>
</header>
<div className="flex gap-5 items-center">
<button onClick={toggleLanguage} className="text-sm font-bold text-slate-600 dark:text-slate-300 hover:text-blue-500 transition-colors">
{i18n.language.toUpperCase()}
</button>
<button onClick={() => {
setIsDarkMode(!isDarkMode);
}} className="text-xl text-slate-600 dark:text-slate-300 hover:text-blue-500 transition-colors">
<i className={isDarkMode ? "mdi mdi-weather-sunny" : "mdi mdi-weather-night"}></i>
</button>
<Link to="/login" className="bg-blue-600 hover:bg-blue-500 text-white px-5 py-2 rounded-lg font-bold transition-all shadow-md shadow-blue-500/20">
{t('login')}
</Link>
</div>
</header>
<main className="grow flex flex-col">
<Routes>
<Route path="/" element={<LandingPage/>}/>
<Route path="/login" element={<LoginPage/>}/>
<Route path="/dashboard" element={<Dashboard/>}/>
<Route path="/impressum" element={<ImprintPage/>}/>
</Routes>
</main>
<main className="grow flex flex-col">
<Routes>
<Route path="/" element={<LandingPage/>}/>
<Route path="/login" element={<LoginPage/>}/>
<Route path="/impressum" element={<ImprintPage/>}/>
</Routes>
</main>
<footer className="p-6 text-center border-t border-slate-200 dark:border-slate-800 text-slate-500 dark:text-slate-400 text-sm bg-white dark:bg-slate-900">
<Link to="/impressum" className="hover:text-blue-500 font-semibold transition-colors">
{t('IMPRINT.TITLE')}
</Link>
</footer>
</div>
</BrowserRouter>
<footer className="p-6 text-center border-t border-slate-200 dark:border-slate-800 text-slate-500 dark:text-slate-400 text-sm bg-white dark:bg-slate-900">
<Link to="/impressum" className="hover:text-blue-500 font-semibold transition-colors">
{t('IMPRINT.TITLE')}
</Link>
</footer>
</div>
);
}
+6 -3
View File
@@ -1,5 +1,6 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import {StrictMode} from 'react';
import {createRoot} from 'react-dom/client';
import {BrowserRouter} from 'react-router-dom';
import App from './App.jsx';
import './i18n';
import './style.css';
@@ -7,6 +8,8 @@ import '@mdi/font/css/materialdesignicons.css';
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
<BrowserRouter>
<App/>
</BrowserRouter>
</StrictMode>
);
+96
View File
@@ -0,0 +1,96 @@
import React, {useEffect, useState} from 'react';
import {useNavigate} from 'react-router-dom';
import {useTranslation} from 'react-i18next';
const Dashboard = () => {
const {t} = useTranslation();
const navigate = useNavigate();
const [userData, setUserData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchUserData = async () => {
const token = localStorage.getItem('tbp_jwt');
if (!token) {
navigate('/login');
return;
}
try {
const response = await fetch(`${import.meta.env.VITE_API_URL}/api/users/me?v=1`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (response.ok) {
const data = await response.json();
setUserData(data);
} else if (response.status === 401) {
localStorage.removeItem('tbp_jwt');
navigate('/login');
} else {
setError(t('INFO.ERROR.LOAD_PROFILE'));
}
} catch (err) {
console.error('Dashboard fetch error:', err);
setError(t('INFO.ERROR.NETWORK_FAILURE'));
} finally {
setIsLoading(false);
}
};
fetchUserData();
}, [navigate]);
if (isLoading) {
return (
<div className="flex min-h-screen items-center justify-center bg-slate-50 dark:bg-slate-900 text-slate-900 dark:text-white">
{t('DASHBOARD.LOADING')}
</div>
);
}
if (error) {
return (
<div className="flex min-h-screen items-center justify-center bg-slate-50 dark:bg-slate-900 text-red-500">
{error}
</div>
);
}
return (
<div className="min-h-screen bg-slate-50 dark:bg-slate-900 p-8">
<div className="max-w-4xl mx-auto space-y-8">
<div className="bg-white dark:bg-slate-800 rounded-xl shadow-sm p-8 border border-slate-200 dark:border-slate-700">
<h1 className="text-3xl font-bold text-slate-900 dark:text-white mb-2">
{t('DASHBOARD.HELLO')} {userData?.email.split('@')[0]} 👋
</h1>
<p className="text-lg text-slate-600 dark:text-slate-300">
{userData?.dashboardMessage}
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div className="bg-white dark:bg-slate-800 h-40 rounded-xl shadow-sm border border-slate-200 dark:border-slate-700 flex items-center justify-center">
<span className="text-slate-400 dark:text-slate-500">Tile 1 (Coming soon)</span>
</div>
<div className="bg-white dark:bg-slate-800 h-40 rounded-xl shadow-sm border border-slate-200 dark:border-slate-700 flex items-center justify-center">
<span className="text-slate-400 dark:text-slate-500">Tile 2 (Coming soon)</span>
</div>
<div className="bg-white dark:bg-slate-800 h-40 rounded-xl shadow-sm border border-slate-200 dark:border-slate-700 flex items-center justify-center">
<span className="text-slate-400 dark:text-slate-500">Settings (Coming soon)</span>
</div>
</div>
</div>
</div>
);
};
export default Dashboard;
+7 -6
View File
@@ -1,8 +1,10 @@
import {useTranslation} from 'react-i18next';
import {useState} from 'react';
import {useNavigate} from 'react-router-dom';
export default function LoginPage() {
const {t} = useTranslation();
const navigate = useNavigate();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
@@ -35,12 +37,11 @@ export default function LoginPage() {
if (response.ok) {
// HTTP 200 OK
setFeedback({
message: `Success: ${data.message} - Welcome ${data.email}!`,
isError: false
});
// TODO: Save JWT token or trigger routing here in the future
localStorage.setItem('tbp_jwt', data.token);
setFeedback({message: 'Login successful!', isError: false});
setTimeout(() => {
navigate('/dashboard');
}, 500);
} else {
// HTTP 401 Unauthorized or other errors
setFeedback({