diff --git a/.env b/.env.example similarity index 64% rename from .env rename to .env.example index ea7fcd9..f5dc9ac 100644 --- a/.env +++ b/.env.example @@ -1,9 +1,9 @@ # Discord OAuth Configuration -DISCORD_CLIENT_ID=1454649755513655491 -DISCORD_CLIENT_SECRET=TqGBxbyE3NBoJCp1riuu2eJe6Y_0zwtu +DISCORD_CLIENT_ID= +DISCORD_CLIENT_SECRET= # Session Security -SESSION_SECRET=dhu2rb9gt82vrn9th2847t2nv45t8v29n4g5tu4gtib +SESSION_SECRET= # Note: Replace the placeholder values above with your actual Discord application credentials # Get these from: https://discord.com/developers/applications diff --git a/.gitignore b/.gitignore index 496a62b..f3bf665 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ dist-ssr *.local *.env +.env # Editor directories and files diff --git a/App.tsx b/App.tsx index 750045b..6886e9e 100644 --- a/App.tsx +++ b/App.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from 'react'; +import { Routes, Route, useNavigate, useLocation } from 'react-router-dom'; import Layout from './components/Layout'; import Dashboard from './pages/Dashboard'; import PlayerProfile from './pages/PlayerProfile'; @@ -9,20 +10,16 @@ import Cities from './pages/Cities'; import CityProfile from './pages/CityProfile'; import ProjectProfile from './pages/ProjectProfile'; import DatapackGenerator from './pages/DatapackGenerator'; -import DatabaseManager from './pages/DatabaseManager'; // Ensure this file exists or remove import if not +import DatabaseManager from './pages/DatabaseManager'; import LinkPlayer from './pages/LinkPlayer'; import AdminPage from './pages/Admin'; import { dbService } from './services/DatabaseService'; import { authService } from './services/AuthService'; import { DiscordUser } from './types'; -import { Icons } from './components/IconSet'; function App() { - const [activeTab, setActiveTab] = useState('dashboard'); - const [selectedPlayerId, setSelectedPlayerId] = useState(null); - const [selectedCityId, setSelectedCityId] = useState(null); - const [selectedProjectId, setSelectedProjectId] = useState(null); - const [selectedOrgId, setSelectedOrgId] = useState(null); + const navigate = useNavigate(); + const location = useLocation(); // Auth state const [user, setUser] = useState(null); @@ -51,171 +48,138 @@ function App() { // If user is logged in but not linked, redirect to link page if (currentUser && !currentUser.linkedPlayerUuid) { - setActiveTab('link-player'); + navigate('/link-player'); } else if (currentUser && currentUser.linkedPlayerUuid) { - // User is fully authenticated, redirect to dashboard if on link page - if (activeTab === 'link-player') { - setActiveTab('dashboard'); + // User is fully authenticated, redirect from link page if needed + if (location.pathname === '/link-player') { + navigate('/'); } } }); return unsub; - }, [activeTab]); - - const handleNavigate = (tab: string) => { - setActiveTab(tab); - if (tab !== 'players') setSelectedPlayerId(null); - if (tab !== 'cities') setSelectedCityId(null); - if (tab !== 'projects') setSelectedProjectId(null); - if (tab !== 'organizations') setSelectedOrgId(null); - }; + }, [navigate, location.pathname]); const navigateToPlayer = (id: string) => { - setSelectedPlayerId(id); - setActiveTab('players'); + navigate(`/players/${id}`); }; const navigateToProject = (id: string) => { - setSelectedProjectId(id); - setActiveTab('projects'); + navigate(`/projects/${id}`); }; const navigateToOrg = (id: string) => { const org = orgs.find(o => o.id === id); if (org?.type === 'City') { - setSelectedCityId(id); - setActiveTab('cities'); + navigate(`/cities/${id}`); } else { - setSelectedOrgId(id); - setActiveTab('organizations'); + navigate(`/organizations/${id}`); } }; - const renderContent = () => { - // Show link player page if user is logged in but not linked - if (activeTab === 'link-player') { - return ; - } - - if (activeTab === 'dashboard') return ; - - if (activeTab === 'projects') { - if (selectedProjectId) { - const project = projects.find(p => p.id === selectedProjectId); - if (project) return ( - setSelectedProjectId(null)} - onSelectPlayer={navigateToPlayer} - onSelectOrg={navigateToOrg} - /> - ); - } - return ; - } - - if (activeTab === 'organizations') { - if (selectedOrgId) { - const org = orgs.find(o => o.id === selectedOrgId); - if (org) return ( - setSelectedOrgId(null)} - backLabel="Zurück zum Verzeichnis" - onSelectPlayer={navigateToPlayer} - onSelectProject={navigateToProject} - /> - ); - } - return ; - } - - if (activeTab === 'setup') return ; - - if (activeTab === 'datapack') return ; - - if (activeTab === 'cities') { - if (selectedCityId) { - const city = orgs.find(o => o.id === selectedCityId); - if (city) return ( - setSelectedCityId(null)} - backLabel="Zurück zu Städte" - onSelectPlayer={navigateToPlayer} - onSelectProject={navigateToProject} - /> - ); - } - return ; - } - - if (activeTab === 'players') { - if (selectedPlayerId) { - const player = players.find(p => p.uuid === selectedPlayerId); - if (player) return setSelectedPlayerId(null)} />; - } - - return ( -
-
-

Bürgerverzeichnis

-
- - -
-
- -
- {players.map(player => ( -
setSelectedPlayerId(player.uuid)} - className="group bg-surface border border-border p-4 rounded-xl cursor-pointer hover:border-accentInfo/50 transition-all duration-200 hover:shadow-card" - > -
-
- -
-
-
{player.username}
-
- {player.tags.slice(0, 2).map(t => {t})} -
-
-
-
- ))} -
-
- ); - } - - // Admin panel (only for admins) - if (activeTab === 'admin') { - return setActiveTab('dashboard')} />; - } - - // Placeholder for database manager if user navigates there (needs explicit page or component) - if (activeTab === 'database') { - return
Datenbank wird jetzt über das Backend (SQLite) verwaltet.
- } - - return ( -
- -

Modul {activeTab} wird derzeit gewartet.

-
- ); + const handleNavigate = (path: string) => { + navigate(path); }; + // Determine active tab from current path + const getActiveTab = () => { + const path = location.pathname; + if (path === '/') return 'dashboard'; + if (path.startsWith('/players')) return 'players'; + if (path.startsWith('/cities')) return 'cities'; + if (path.startsWith('/projects')) return 'projects'; + if (path.startsWith('/organizations')) return 'organizations'; + if (path.startsWith('/admin')) return 'admin'; + if (path === '/setup') return 'setup'; + if (path === '/datapack') return 'datapack'; + if (path === '/link-player') return 'link-player'; + return 'dashboard'; + }; + + const activeTab = getActiveTab(); + return ( - {renderContent()} + + {/* Auth Route */} + } /> + + {/* Main Routes */} + } /> + } /> + } /> + + {/* Player Routes */} + +
+

Bürgerverzeichnis

+
+ +
+
+ +
+ {players.map(player => ( +
navigateToPlayer(player.uuid)} + className="group bg-surface border border-border p-4 rounded-xl cursor-pointer hover:border-accentInfo/50 transition-all duration-200 hover:shadow-card" + > +
+
+ +
+
+
{player.username}
+
+ {player.tags.slice(0, 2).map(t => {t})} +
+
+
+
+ ))} +
+ + } /> + } /> + + {/* City Routes */} + navigateToOrg(id)} />} /> + } /> + + {/* Organization Routes */} + navigateToOrg(id)} />} /> + } /> + + {/* Project Routes */} + navigateToProject(id)} />} /> + navigate('/projects')} + onSelectPlayer={navigateToPlayer} + onSelectOrg={navigateToOrg} + />} /> + + {/* Admin Routes */} + navigate('/')} />} /> + + {/* Fallback */} + +
404
+

Seite nicht gefunden

+ + + } /> +
); } diff --git a/components/Layout.tsx b/components/Layout.tsx index f1f4fe3..b70f18d 100644 --- a/components/Layout.tsx +++ b/components/Layout.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from 'react'; +import { Link, useLocation } from 'react-router-dom'; import { Icons } from './IconSet'; import { authService } from '../services/AuthService'; import { DiscordUser } from '../types'; @@ -12,14 +13,14 @@ interface LayoutProps { const NavItem = ({ active, label, - onClick + to }: { active: boolean; label: string; - onClick: () => void; + to: string; }) => ( - + ); const Layout: React.FC = ({ children, activeTab, onNavigate }) => { @@ -59,13 +60,13 @@ const Layout: React.FC = ({ children, activeTab, onNavigate }) => { {/* Desktop Nav */} @@ -99,16 +100,16 @@ const Layout: React.FC = ({ children, activeTab, onNavigate }) => { {/* Mobile Nav Dropdown */} {mobileMenuOpen && (
-
{ onNavigate('dashboard'); setMobileMenuOpen(false); }} className="block py-2 text-textMuted hover:text-textMain">Übersicht
-
{ onNavigate('cities'); setMobileMenuOpen(false); }} className="block py-2 text-textMuted hover:text-textMain">Städte
-
{ onNavigate('players'); setMobileMenuOpen(false); }} className="block py-2 text-textMuted hover:text-textMain">Bürger
-
{ onNavigate('organizations'); setMobileMenuOpen(false); }} className="block py-2 text-textMuted hover:text-textMain">Organisationen
-
{ onNavigate('projects'); setMobileMenuOpen(false); }} className="block py-2 text-textMuted hover:text-textMain">Unternehmen
+ setMobileMenuOpen(false)} className="block py-2 text-textMuted hover:text-textMain">Übersicht + setMobileMenuOpen(false)} className="block py-2 text-textMuted hover:text-textMain">Städte + setMobileMenuOpen(false)} className="block py-2 text-textMuted hover:text-textMain">Bürger + setMobileMenuOpen(false)} className="block py-2 text-textMuted hover:text-textMain">Organisationen + setMobileMenuOpen(false)} className="block py-2 text-textMuted hover:text-textMain">Unternehmen {user?.isAdmin && ( -
{ onNavigate('admin'); setMobileMenuOpen(false); }} className="block py-2 text-red-400 hover:text-red-300">Admin
+ setMobileMenuOpen(false)} className="block py-2 text-red-400 hover:text-red-300">Admin )} -
{ onNavigate('datapack'); setMobileMenuOpen(false); }} className="block py-2 text-textMain">Datapack holen
-
{ onNavigate('setup'); setMobileMenuOpen(false); }} className="block py-2 text-accentInfo font-mono text-sm border-t border-white/5 pt-4">{"Admin Setup >_"}
+ setMobileMenuOpen(false)} className="block py-2 text-textMain">Datapack holen + setMobileMenuOpen(false)} className="block py-2 text-accentInfo font-mono text-sm border-t border-white/5 pt-4">{"Admin Setup >_"}
)} diff --git a/index.tsx b/index.tsx index 6ca5361..409d9d5 100644 --- a/index.tsx +++ b/index.tsx @@ -1,5 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; import App from './App'; const rootElement = document.getElementById('root'); @@ -10,6 +11,8 @@ if (!rootElement) { const root = ReactDOM.createRoot(rootElement); root.render( - + + + -); \ No newline at end of file +); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f163f0d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,963 @@ +{ + "name": "obsidian-|-rp-plattform", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "obsidian-|-rp-plattform", + "version": "0.0.0", + "dependencies": { + "react": "^19.2.3", + "react-dom": "^19.2.3", + "react-router-dom": "^7.11.0" + }, + "devDependencies": { + "@types/node": "^22.14.0", + "@vitejs/plugin-react": "^5.0.0", + "typescript": "~5.8.2", + "vite": "^6.2.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.53", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.54.0", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.54.0", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.3", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.5", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.53", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.11", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001761", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.2.3", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.3", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.3" + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.11.0.tgz", + "integrity": "sha512-uI4JkMmjbWCZc01WVP2cH7ZfSzH91JAZUDd7/nIprDgWxBV1TkkmLToFh7EbMTcMak8URFRa2YoBL/W8GWnCTQ==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.11.0.tgz", + "integrity": "sha512-e49Ir/kMGRzFOOrYQBdoitq3ULigw4lKbAyKusnvtDu2t4dBX4AGYPrzNvorXmVuOyeakai6FUPW5MmibvVG8g==", + "license": "MIT", + "dependencies": { + "react-router": "7.11.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/rollup": { + "version": "4.54.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.54.0", + "@rollup/rollup-android-arm64": "4.54.0", + "@rollup/rollup-darwin-arm64": "4.54.0", + "@rollup/rollup-darwin-x64": "4.54.0", + "@rollup/rollup-freebsd-arm64": "4.54.0", + "@rollup/rollup-freebsd-x64": "4.54.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", + "@rollup/rollup-linux-arm-musleabihf": "4.54.0", + "@rollup/rollup-linux-arm64-gnu": "4.54.0", + "@rollup/rollup-linux-arm64-musl": "4.54.0", + "@rollup/rollup-linux-loong64-gnu": "4.54.0", + "@rollup/rollup-linux-ppc64-gnu": "4.54.0", + "@rollup/rollup-linux-riscv64-gnu": "4.54.0", + "@rollup/rollup-linux-riscv64-musl": "4.54.0", + "@rollup/rollup-linux-s390x-gnu": "4.54.0", + "@rollup/rollup-linux-x64-gnu": "4.54.0", + "@rollup/rollup-linux-x64-musl": "4.54.0", + "@rollup/rollup-openharmony-arm64": "4.54.0", + "@rollup/rollup-win32-arm64-msvc": "4.54.0", + "@rollup/rollup-win32-ia32-msvc": "4.54.0", + "@rollup/rollup-win32-x64-gnu": "4.54.0", + "@rollup/rollup-win32-x64-msvc": "4.54.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "dev": true, + "license": "ISC" + } + } +} diff --git a/package.json b/package.json index f3d0435..fa776a4 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ }, "dependencies": { "react": "^19.2.3", - "react-dom": "^19.2.3" + "react-dom": "^19.2.3", + "react-router-dom": "^7.11.0" }, "devDependencies": { "@types/node": "^22.14.0", diff --git a/pages/CityProfile.tsx b/pages/CityProfile.tsx index b9805a7..5a12118 100644 --- a/pages/CityProfile.tsx +++ b/pages/CityProfile.tsx @@ -1,23 +1,17 @@ import React, { useState, useEffect } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; import { Organization, Project, Player } from '../types'; import { Icons } from '../components/IconSet'; import { dbService } from '../services/DatabaseService'; -interface CityProfileProps { - city: Organization; - onBack: () => void; - backLabel?: string; - onSelectPlayer: (id: string) => void; - onSelectProject: (id: string) => void; -} +const CityProfile: React.FC = () => { + const { id } = useParams<{ id: string }>(); + const navigate = useNavigate(); + const [city, setCity] = useState(null); + const backLabel = 'Zurück zu Städte'; -const CityProfile: React.FC = ({ - city, - onBack, - backLabel = 'Zurück', - onSelectPlayer, - onSelectProject -}) => { + const onSelectPlayer = (playerId: string) => navigate(`/players/${playerId}`); + const onSelectProject = (projectId: string) => navigate(`/projects/${projectId}`); const [activeTab, setActiveTab] = useState<'overview' | 'residents' | 'ventures'>('overview'); const [residents, setResidents] = useState([]); const [ventures, setVentures] = useState([]); @@ -25,6 +19,26 @@ const CityProfile: React.FC = ({ const [loading, setLoading] = useState(true); useEffect(() => { + if (!id) { + navigate('/cities'); + return; + } + + // Load city data + const cityData = dbService.getOrg(id); + if (cityData) { + setCity(cityData); + } else { + navigate('/cities'); + return; + } + + setLoading(false); + }, [id, navigate]); + + useEffect(() => { + if (!city) return; + const loadCityData = () => { // Load residents (players in this city) const allPlayers = dbService.getPlayers(); @@ -46,8 +60,6 @@ const CityProfile: React.FC = ({ ...city.cityStats }; setCityStats(stats); - - setLoading(false); }; // Initial load @@ -56,11 +68,21 @@ const CityProfile: React.FC = ({ // Subscribe to updates const unsub = dbService.subscribe(loadCityData); return unsub; - }, [city.id]); + }, [city]); + + if (loading || !city) { + return ( +
+
+
+
+
+ ); + } return (
- diff --git a/pages/PlayerProfile.tsx b/pages/PlayerProfile.tsx index 3efddaf..bba4a76 100644 --- a/pages/PlayerProfile.tsx +++ b/pages/PlayerProfile.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; import { Player } from '../types'; import { dbService } from '../services/DatabaseService'; import { authService } from '../services/AuthService'; @@ -6,24 +7,22 @@ import InventoryGrid from '../components/InventoryGrid'; import EditModal from '../components/EditModal'; import { Icons } from '../components/IconSet'; -interface PlayerProfileProps { - player: Player; - onBack: () => void; -} - -const PlayerProfile: React.FC = ({ player: initialPlayer, onBack }) => { - const [player, setPlayer] = useState(initialPlayer); +const PlayerProfile: React.FC = () => { + const { id } = useParams<{ id: string }>(); + const navigate = useNavigate(); + const [player, setPlayer] = useState(null); const [currentUser, setCurrentUser] = useState(authService.getUser()); - + // Edit State const [isEditStoryOpen, setIsEditStoryOpen] = useState(false); const [isEditTagsOpen, setIsEditTagsOpen] = useState(false); const [isEditOrgOpen, setIsEditOrgOpen] = useState(false); const [ownedProjects, setOwnedProjects] = useState([]); - + const [loading, setLoading] = useState(true); + // Is this the logged-in user's profile? - const isOwner = currentUser?.linkedPlayerUuid === player.uuid; - const playerOrg = dbService.getOrg(player.stats.organizationId || ''); + const isOwner = currentUser?.linkedPlayerUuid === player?.uuid; + const playerOrg = player ? dbService.getOrg(player.stats.organizationId || '') : null; // Check if player is already linked to anyone in our mock/real DB logic // Since the Player object doesn't expose 'discordId' publicly in types yet (it's hidden in DB), @@ -31,9 +30,25 @@ const PlayerProfile: React.FC = ({ player: initialPlayer, on const canClaim = !!currentUser && !currentUser.linkedPlayerUuid && !isOwner; useEffect(() => { - // Refresh player data from "DB" to ensure we have latest local edits - const freshData = dbService.getPlayer(player.uuid); - if (freshData) setPlayer(freshData); + if (!id) { + navigate('/players'); + return; + } + + // Load player data + const playerData = dbService.getPlayer(id); + if (playerData) { + setPlayer(playerData); + } else { + navigate('/players'); + return; + } + + setLoading(false); + }, [id, navigate]); + + useEffect(() => { + if (!player) return; // Load owned projects const allProjects = dbService.getProjects(); @@ -43,7 +58,17 @@ const PlayerProfile: React.FC = ({ player: initialPlayer, on // Subscribe to auth to show/hide edit buttons const unsub = authService.subscribe(u => setCurrentUser(u)); return unsub; - }, [player.uuid, player.username]); + }, [player]); + + if (loading || !player) { + return ( +
+
+
+
+
+ ); + } const handleSaveStory = (newStory: string) => { // Update local DB @@ -113,7 +138,7 @@ const PlayerProfile: React.FC = ({ player: initialPlayer, on return (
- {canClaim && ( diff --git a/pages/ProjectProfile.tsx b/pages/ProjectProfile.tsx index 515beaf..ecdbfa7 100644 --- a/pages/ProjectProfile.tsx +++ b/pages/ProjectProfile.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; import { Project, ShopItem } from '../types'; import { Icons, ItemIcon } from '../components/IconSet'; import { dbService } from '../services/DatabaseService'; @@ -11,19 +12,11 @@ import BannerManagementModal from '../components/BannerManagementModal'; import GalleryManagementModal from '../components/GalleryManagementModal'; import DeleteProjectModal from '../components/DeleteProjectModal'; -interface ProjectProfileProps { - project: Project; - onBack: () => void; - onSelectPlayer: (id: string) => void; - onSelectOrg: (id: string) => void; -} - -const ProjectProfile: React.FC = ({ - project, - onBack, - onSelectPlayer, - onSelectOrg -}) => { +const ProjectProfile: React.FC = () => { + const { id } = useParams<{ id: string }>(); + const navigate = useNavigate(); + const [project, setProject] = useState(null); + const [loading, setLoading] = useState(true); const [activeTab, setActiveTab] = useState<'overview' | 'shop' | 'manage'>('overview'); const [user, setUser] = useState(null); const [org, setOrg] = useState(null); @@ -43,6 +36,26 @@ const ProjectProfile: React.FC = ({ }, []); useEffect(() => { + if (!id) { + navigate('/projects'); + return; + } + + // Load project data + const projectData = dbService.getProject(id); + if (projectData) { + setProject(projectData); + } else { + navigate('/projects'); + return; + } + + setLoading(false); + }, [id, navigate]); + + useEffect(() => { + if (!project) return; + // Load associated org and owner player if (project.associatedOrgId) { const foundOrg = dbService.getOrg(project.associatedOrgId); @@ -55,16 +68,37 @@ const ProjectProfile: React.FC = ({ setOwnerPlayer(owner); }, [project]); - const hasShop = project.shopCatalog && project.shopCatalog.length > 0; - const isOwner = user?.linkedPlayerUuid && ownerPlayer && dbService.getPlayer(user.linkedPlayerUuid)?.username === project.owner; + const onSelectPlayer = (playerId: string) => navigate(`/players/${playerId}`); + const onSelectOrg = (orgId: string) => { + const org = dbService.getOrgs().find(o => o.id === orgId); + if (org?.type === 'City') { + navigate(`/cities/${orgId}`); + } else { + navigate(`/organizations/${orgId}`); + } + }; + const onBack = () => navigate('/projects'); + + const hasShop = project?.shopCatalog && project.shopCatalog.length > 0; + const isOwner = user?.linkedPlayerUuid && ownerPlayer && dbService.getPlayer(user.linkedPlayerUuid)?.username === project?.owner; // Group shop items - const services = project.shopCatalog?.filter(i => i.type === 'service') || []; - const products = project.shopCatalog?.filter(i => i.type !== 'service') || []; + const services = project?.shopCatalog?.filter(i => i.type === 'service') || []; + const products = project?.shopCatalog?.filter(i => i.type !== 'service') || []; + + if (loading || !project) { + return ( +
+
+
+
+
+ ); + } return (
-