mirror of
https://github.com/ceratic/project_vollidioten_website.git
synced 2026-05-14 00:16:47 +02:00
routing
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
# Discord OAuth Configuration
|
# Discord OAuth Configuration
|
||||||
DISCORD_CLIENT_ID=1454649755513655491
|
DISCORD_CLIENT_ID=
|
||||||
DISCORD_CLIENT_SECRET=TqGBxbyE3NBoJCp1riuu2eJe6Y_0zwtu
|
DISCORD_CLIENT_SECRET=
|
||||||
|
|
||||||
# Session Security
|
# Session Security
|
||||||
SESSION_SECRET=dhu2rb9gt82vrn9th2847t2nv45t8v29n4g5tu4gtib
|
SESSION_SECRET=
|
||||||
|
|
||||||
# Note: Replace the placeholder values above with your actual Discord application credentials
|
# Note: Replace the placeholder values above with your actual Discord application credentials
|
||||||
# Get these from: https://discord.com/developers/applications
|
# Get these from: https://discord.com/developers/applications
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,6 +13,7 @@ dist-ssr
|
|||||||
*.local
|
*.local
|
||||||
|
|
||||||
*.env
|
*.env
|
||||||
|
.env
|
||||||
|
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
|
|||||||
262
App.tsx
262
App.tsx
@@ -1,4 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Routes, Route, useNavigate, useLocation } from 'react-router-dom';
|
||||||
import Layout from './components/Layout';
|
import Layout from './components/Layout';
|
||||||
import Dashboard from './pages/Dashboard';
|
import Dashboard from './pages/Dashboard';
|
||||||
import PlayerProfile from './pages/PlayerProfile';
|
import PlayerProfile from './pages/PlayerProfile';
|
||||||
@@ -9,20 +10,16 @@ import Cities from './pages/Cities';
|
|||||||
import CityProfile from './pages/CityProfile';
|
import CityProfile from './pages/CityProfile';
|
||||||
import ProjectProfile from './pages/ProjectProfile';
|
import ProjectProfile from './pages/ProjectProfile';
|
||||||
import DatapackGenerator from './pages/DatapackGenerator';
|
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 LinkPlayer from './pages/LinkPlayer';
|
||||||
import AdminPage from './pages/Admin';
|
import AdminPage from './pages/Admin';
|
||||||
import { dbService } from './services/DatabaseService';
|
import { dbService } from './services/DatabaseService';
|
||||||
import { authService } from './services/AuthService';
|
import { authService } from './services/AuthService';
|
||||||
import { DiscordUser } from './types';
|
import { DiscordUser } from './types';
|
||||||
import { Icons } from './components/IconSet';
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [activeTab, setActiveTab] = useState('dashboard');
|
const navigate = useNavigate();
|
||||||
const [selectedPlayerId, setSelectedPlayerId] = useState<string | null>(null);
|
const location = useLocation();
|
||||||
const [selectedCityId, setSelectedCityId] = useState<string | null>(null);
|
|
||||||
const [selectedProjectId, setSelectedProjectId] = useState<string | null>(null);
|
|
||||||
const [selectedOrgId, setSelectedOrgId] = useState<string | null>(null);
|
|
||||||
|
|
||||||
// Auth state
|
// Auth state
|
||||||
const [user, setUser] = useState<DiscordUser | null>(null);
|
const [user, setUser] = useState<DiscordUser | null>(null);
|
||||||
@@ -51,171 +48,138 @@ function App() {
|
|||||||
|
|
||||||
// If user is logged in but not linked, redirect to link page
|
// If user is logged in but not linked, redirect to link page
|
||||||
if (currentUser && !currentUser.linkedPlayerUuid) {
|
if (currentUser && !currentUser.linkedPlayerUuid) {
|
||||||
setActiveTab('link-player');
|
navigate('/link-player');
|
||||||
} else if (currentUser && currentUser.linkedPlayerUuid) {
|
} else if (currentUser && currentUser.linkedPlayerUuid) {
|
||||||
// User is fully authenticated, redirect to dashboard if on link page
|
// User is fully authenticated, redirect from link page if needed
|
||||||
if (activeTab === 'link-player') {
|
if (location.pathname === '/link-player') {
|
||||||
setActiveTab('dashboard');
|
navigate('/');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return unsub;
|
return unsub;
|
||||||
}, [activeTab]);
|
}, [navigate, location.pathname]);
|
||||||
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
const navigateToPlayer = (id: string) => {
|
const navigateToPlayer = (id: string) => {
|
||||||
setSelectedPlayerId(id);
|
navigate(`/players/${id}`);
|
||||||
setActiveTab('players');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const navigateToProject = (id: string) => {
|
const navigateToProject = (id: string) => {
|
||||||
setSelectedProjectId(id);
|
navigate(`/projects/${id}`);
|
||||||
setActiveTab('projects');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const navigateToOrg = (id: string) => {
|
const navigateToOrg = (id: string) => {
|
||||||
const org = orgs.find(o => o.id === id);
|
const org = orgs.find(o => o.id === id);
|
||||||
if (org?.type === 'City') {
|
if (org?.type === 'City') {
|
||||||
setSelectedCityId(id);
|
navigate(`/cities/${id}`);
|
||||||
setActiveTab('cities');
|
|
||||||
} else {
|
} else {
|
||||||
setSelectedOrgId(id);
|
navigate(`/organizations/${id}`);
|
||||||
setActiveTab('organizations');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderContent = () => {
|
const handleNavigate = (path: string) => {
|
||||||
// Show link player page if user is logged in but not linked
|
navigate(path);
|
||||||
if (activeTab === 'link-player') {
|
|
||||||
return <LinkPlayer />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activeTab === 'dashboard') return <Dashboard />;
|
|
||||||
|
|
||||||
if (activeTab === 'projects') {
|
|
||||||
if (selectedProjectId) {
|
|
||||||
const project = projects.find(p => p.id === selectedProjectId);
|
|
||||||
if (project) return (
|
|
||||||
<ProjectProfile
|
|
||||||
project={project}
|
|
||||||
onBack={() => setSelectedProjectId(null)}
|
|
||||||
onSelectPlayer={navigateToPlayer}
|
|
||||||
onSelectOrg={navigateToOrg}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return <Projects onSelectProject={setSelectedProjectId} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activeTab === 'organizations') {
|
|
||||||
if (selectedOrgId) {
|
|
||||||
const org = orgs.find(o => o.id === selectedOrgId);
|
|
||||||
if (org) return (
|
|
||||||
<CityProfile
|
|
||||||
city={org}
|
|
||||||
onBack={() => setSelectedOrgId(null)}
|
|
||||||
backLabel="Zurück zum Verzeichnis"
|
|
||||||
onSelectPlayer={navigateToPlayer}
|
|
||||||
onSelectProject={navigateToProject}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return <Organizations onSelectOrg={setSelectedOrgId} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activeTab === 'setup') return <SetupGuide />;
|
|
||||||
|
|
||||||
if (activeTab === 'datapack') return <DatapackGenerator />;
|
|
||||||
|
|
||||||
if (activeTab === 'cities') {
|
|
||||||
if (selectedCityId) {
|
|
||||||
const city = orgs.find(o => o.id === selectedCityId);
|
|
||||||
if (city) return (
|
|
||||||
<CityProfile
|
|
||||||
city={city}
|
|
||||||
onBack={() => setSelectedCityId(null)}
|
|
||||||
backLabel="Zurück zu Städte"
|
|
||||||
onSelectPlayer={navigateToPlayer}
|
|
||||||
onSelectProject={navigateToProject}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return <Cities onSelectCity={setSelectedCityId} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activeTab === 'players') {
|
|
||||||
if (selectedPlayerId) {
|
|
||||||
const player = players.find(p => p.uuid === selectedPlayerId);
|
|
||||||
if (player) return <PlayerProfile player={player} onBack={() => setSelectedPlayerId(null)} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="animate-in fade-in">
|
|
||||||
<div className="flex justify-between items-center mb-6">
|
|
||||||
<h2 className="text-2xl font-bold">Bürgerverzeichnis</h2>
|
|
||||||
<div className="relative">
|
|
||||||
<Icons.Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-textMuted" />
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Filtern nach Tag oder Name..."
|
|
||||||
className="bg-surfaceHighlight border border-border rounded-lg pl-10 pr-4 py-2 text-sm text-textMain focus:border-accentInfo focus:outline-none w-64 transition-colors"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
|
|
||||||
{players.map(player => (
|
|
||||||
<div
|
|
||||||
key={player.uuid}
|
|
||||||
onClick={() => 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"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<div className="w-12 h-12 rounded-md flex items-center justify-center font-bold text-lg text-textMuted group-hover:text-textMain transition-colors">
|
|
||||||
<img src={"https://minotar.net/armor/bust/"+player.username+"/500.png"}></img>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="font-semibold text-textMain group-hover:text-accentInfo transition-colors">{player.username}</div>
|
|
||||||
<div className="text-xs text-textMuted mt-1 flex gap-2">
|
|
||||||
{player.tags.slice(0, 2).map(t => <span key={t}>{t}</span>)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Admin panel (only for admins)
|
|
||||||
if (activeTab === 'admin') {
|
|
||||||
return <AdminPage onBack={() => setActiveTab('dashboard')} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Placeholder for database manager if user navigates there (needs explicit page or component)
|
|
||||||
if (activeTab === 'database') {
|
|
||||||
return <div className="p-10 text-center text-textMuted">Datenbank wird jetzt über das Backend (SQLite) verwaltet.</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col items-center justify-center h-[50vh] text-textMuted">
|
|
||||||
<Icons.Box className="w-12 h-12 mb-4 opacity-20" />
|
|
||||||
<p>Modul <strong>{activeTab}</strong> wird derzeit gewartet.</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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 (
|
return (
|
||||||
<Layout activeTab={activeTab} onNavigate={handleNavigate}>
|
<Layout activeTab={activeTab} onNavigate={handleNavigate}>
|
||||||
{renderContent()}
|
<Routes>
|
||||||
|
{/* Auth Route */}
|
||||||
|
<Route path="/link-player" element={<LinkPlayer />} />
|
||||||
|
|
||||||
|
{/* Main Routes */}
|
||||||
|
<Route path="/" element={<Dashboard />} />
|
||||||
|
<Route path="/setup" element={<SetupGuide />} />
|
||||||
|
<Route path="/datapack" element={<DatapackGenerator />} />
|
||||||
|
|
||||||
|
{/* Player Routes */}
|
||||||
|
<Route path="/players" element={
|
||||||
|
<div className="animate-in fade-in">
|
||||||
|
<div className="flex justify-between items-center mb-6">
|
||||||
|
<h2 className="text-2xl font-bold">Bürgerverzeichnis</h2>
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Filtern nach Tag oder Name..."
|
||||||
|
className="bg-surfaceHighlight border border-border rounded-lg pl-10 pr-4 py-2 text-sm text-textMain focus:border-accentInfo focus:outline-none w-64 transition-colors"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
|
||||||
|
{players.map(player => (
|
||||||
|
<div
|
||||||
|
key={player.uuid}
|
||||||
|
onClick={() => 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"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="w-12 h-12 rounded-md flex items-center justify-center font-bold text-lg text-textMuted group-hover:text-textMain transition-colors">
|
||||||
|
<img src={"https://minotar.net/armor/bust/"+player.username+"/500.png"}></img>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="font-semibold text-textMain group-hover:text-accentInfo transition-colors">{player.username}</div>
|
||||||
|
<div className="text-xs text-textMuted mt-1 flex gap-2">
|
||||||
|
{player.tags.slice(0, 2).map(t => <span key={t}>{t}</span>)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
} />
|
||||||
|
<Route path="/players/:id" element={<PlayerProfile />} />
|
||||||
|
|
||||||
|
{/* City Routes */}
|
||||||
|
<Route path="/cities" element={<Cities onSelectCity={(id) => navigateToOrg(id)} />} />
|
||||||
|
<Route path="/cities/:id" element={<CityProfile />} />
|
||||||
|
|
||||||
|
{/* Organization Routes */}
|
||||||
|
<Route path="/organizations" element={<Organizations onSelectOrg={(id) => navigateToOrg(id)} />} />
|
||||||
|
<Route path="/organizations/:id" element={<CityProfile />} />
|
||||||
|
|
||||||
|
{/* Project Routes */}
|
||||||
|
<Route path="/projects" element={<Projects onSelectProject={(id) => navigateToProject(id)} />} />
|
||||||
|
<Route path="/projects/:id" element={<ProjectProfile
|
||||||
|
onBack={() => navigate('/projects')}
|
||||||
|
onSelectPlayer={navigateToPlayer}
|
||||||
|
onSelectOrg={navigateToOrg}
|
||||||
|
/>} />
|
||||||
|
|
||||||
|
{/* Admin Routes */}
|
||||||
|
<Route path="/admin" element={<AdminPage onBack={() => navigate('/')} />} />
|
||||||
|
|
||||||
|
{/* Fallback */}
|
||||||
|
<Route path="*" element={
|
||||||
|
<div className="flex flex-col items-center justify-center h-[50vh] text-textMuted">
|
||||||
|
<div className="text-6xl mb-4">404</div>
|
||||||
|
<p>Seite nicht gefunden</p>
|
||||||
|
<button
|
||||||
|
onClick={() => navigate('/')}
|
||||||
|
className="mt-4 text-accentInfo hover:underline"
|
||||||
|
>
|
||||||
|
Zurück zur Startseite
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
} />
|
||||||
|
</Routes>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
import { Icons } from './IconSet';
|
import { Icons } from './IconSet';
|
||||||
import { authService } from '../services/AuthService';
|
import { authService } from '../services/AuthService';
|
||||||
import { DiscordUser } from '../types';
|
import { DiscordUser } from '../types';
|
||||||
@@ -12,14 +13,14 @@ interface LayoutProps {
|
|||||||
const NavItem = ({
|
const NavItem = ({
|
||||||
active,
|
active,
|
||||||
label,
|
label,
|
||||||
onClick
|
to
|
||||||
}: {
|
}: {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
label: string;
|
label: string;
|
||||||
onClick: () => void;
|
to: string;
|
||||||
}) => (
|
}) => (
|
||||||
<button
|
<Link
|
||||||
onClick={onClick}
|
to={to}
|
||||||
className={`text-sm font-medium transition-colors duration-200 px-1 py-4 border-b-2 ${
|
className={`text-sm font-medium transition-colors duration-200 px-1 py-4 border-b-2 ${
|
||||||
active
|
active
|
||||||
? 'text-textMain border-accentInfo'
|
? 'text-textMain border-accentInfo'
|
||||||
@@ -27,7 +28,7 @@ const NavItem = ({
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</button>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|
||||||
const Layout: React.FC<LayoutProps> = ({ children, activeTab, onNavigate }) => {
|
const Layout: React.FC<LayoutProps> = ({ children, activeTab, onNavigate }) => {
|
||||||
@@ -59,13 +60,13 @@ const Layout: React.FC<LayoutProps> = ({ children, activeTab, onNavigate }) => {
|
|||||||
|
|
||||||
{/* Desktop Nav */}
|
{/* Desktop Nav */}
|
||||||
<nav className="hidden md:flex items-center gap-6 h-full">
|
<nav className="hidden md:flex items-center gap-6 h-full">
|
||||||
<NavItem active={activeTab === 'dashboard'} label="Übersicht" onClick={() => onNavigate('dashboard')} />
|
<NavItem active={activeTab === 'dashboard'} label="Übersicht" to="/" />
|
||||||
<NavItem active={activeTab === 'cities'} label="Städte" onClick={() => onNavigate('cities')} />
|
<NavItem active={activeTab === 'cities'} label="Städte" to="/cities" />
|
||||||
<NavItem active={activeTab === 'players'} label="Bürger" onClick={() => onNavigate('players')} />
|
<NavItem active={activeTab === 'players'} label="Bürger" to="/players" />
|
||||||
{/* <NavItem active={activeTab === 'organizations'} label="Organisationen" onClick={() => onNavigate('organizations')} />*/}
|
{/* <NavItem active={activeTab === 'organizations'} label="Organisationen" to="/organizations" />*/}
|
||||||
<NavItem active={activeTab === 'projects'} label="Unternehmen" onClick={() => onNavigate('projects')} />
|
<NavItem active={activeTab === 'projects'} label="Unternehmen" to="/projects" />
|
||||||
{user?.isAdmin && (
|
{user?.isAdmin && (
|
||||||
<NavItem active={activeTab === 'admin'} label="Admin" onClick={() => onNavigate('admin')} />
|
<NavItem active={activeTab === 'admin'} label="Admin" to="/admin" />
|
||||||
)}
|
)}
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
@@ -99,16 +100,16 @@ const Layout: React.FC<LayoutProps> = ({ children, activeTab, onNavigate }) => {
|
|||||||
{/* Mobile Nav Dropdown */}
|
{/* Mobile Nav Dropdown */}
|
||||||
{mobileMenuOpen && (
|
{mobileMenuOpen && (
|
||||||
<div className="md:hidden border-t border-border bg-surface px-6 py-4 space-y-4 shadow-xl">
|
<div className="md:hidden border-t border-border bg-surface px-6 py-4 space-y-4 shadow-xl">
|
||||||
<div onClick={() => { onNavigate('dashboard'); setMobileMenuOpen(false); }} className="block py-2 text-textMuted hover:text-textMain">Übersicht</div>
|
<Link to="/" onClick={() => setMobileMenuOpen(false)} className="block py-2 text-textMuted hover:text-textMain">Übersicht</Link>
|
||||||
<div onClick={() => { onNavigate('cities'); setMobileMenuOpen(false); }} className="block py-2 text-textMuted hover:text-textMain">Städte</div>
|
<Link to="/cities" onClick={() => setMobileMenuOpen(false)} className="block py-2 text-textMuted hover:text-textMain">Städte</Link>
|
||||||
<div onClick={() => { onNavigate('players'); setMobileMenuOpen(false); }} className="block py-2 text-textMuted hover:text-textMain">Bürger</div>
|
<Link to="/players" onClick={() => setMobileMenuOpen(false)} className="block py-2 text-textMuted hover:text-textMain">Bürger</Link>
|
||||||
<div onClick={() => { onNavigate('organizations'); setMobileMenuOpen(false); }} className="block py-2 text-textMuted hover:text-textMain">Organisationen</div>
|
<Link to="/organizations" onClick={() => setMobileMenuOpen(false)} className="block py-2 text-textMuted hover:text-textMain">Organisationen</Link>
|
||||||
<div onClick={() => { onNavigate('projects'); setMobileMenuOpen(false); }} className="block py-2 text-textMuted hover:text-textMain">Unternehmen</div>
|
<Link to="/projects" onClick={() => setMobileMenuOpen(false)} className="block py-2 text-textMuted hover:text-textMain">Unternehmen</Link>
|
||||||
{user?.isAdmin && (
|
{user?.isAdmin && (
|
||||||
<div onClick={() => { onNavigate('admin'); setMobileMenuOpen(false); }} className="block py-2 text-red-400 hover:text-red-300">Admin</div>
|
<Link to="/admin" onClick={() => setMobileMenuOpen(false)} className="block py-2 text-red-400 hover:text-red-300">Admin</Link>
|
||||||
)}
|
)}
|
||||||
<div onClick={() => { onNavigate('datapack'); setMobileMenuOpen(false); }} className="block py-2 text-textMain">Datapack holen</div>
|
<Link to="/datapack" onClick={() => setMobileMenuOpen(false)} className="block py-2 text-textMain">Datapack holen</Link>
|
||||||
<div onClick={() => { onNavigate('setup'); setMobileMenuOpen(false); }} className="block py-2 text-accentInfo font-mono text-sm border-t border-white/5 pt-4">{"Admin Setup >_"}</div>
|
<Link to="/setup" onClick={() => setMobileMenuOpen(false)} className="block py-2 text-accentInfo font-mono text-sm border-t border-white/5 pt-4">{"Admin Setup >_"}</Link>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
|
||||||
const rootElement = document.getElementById('root');
|
const rootElement = document.getElementById('root');
|
||||||
@@ -10,6 +11,8 @@ if (!rootElement) {
|
|||||||
const root = ReactDOM.createRoot(rootElement);
|
const root = ReactDOM.createRoot(rootElement);
|
||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<App />
|
<BrowserRouter>
|
||||||
|
<App />
|
||||||
|
</BrowserRouter>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
|
|||||||
963
package-lock.json
generated
Normal file
963
package-lock.json
generated
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,8 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^19.2.3",
|
"react": "^19.2.3",
|
||||||
"react-dom": "^19.2.3"
|
"react-dom": "^19.2.3",
|
||||||
|
"react-router-dom": "^7.11.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.14.0",
|
"@types/node": "^22.14.0",
|
||||||
|
|||||||
@@ -1,23 +1,17 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { Organization, Project, Player } from '../types';
|
import { Organization, Project, Player } from '../types';
|
||||||
import { Icons } from '../components/IconSet';
|
import { Icons } from '../components/IconSet';
|
||||||
import { dbService } from '../services/DatabaseService';
|
import { dbService } from '../services/DatabaseService';
|
||||||
|
|
||||||
interface CityProfileProps {
|
const CityProfile: React.FC = () => {
|
||||||
city: Organization;
|
const { id } = useParams<{ id: string }>();
|
||||||
onBack: () => void;
|
const navigate = useNavigate();
|
||||||
backLabel?: string;
|
const [city, setCity] = useState<Organization | null>(null);
|
||||||
onSelectPlayer: (id: string) => void;
|
const backLabel = 'Zurück zu Städte';
|
||||||
onSelectProject: (id: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CityProfile: React.FC<CityProfileProps> = ({
|
const onSelectPlayer = (playerId: string) => navigate(`/players/${playerId}`);
|
||||||
city,
|
const onSelectProject = (projectId: string) => navigate(`/projects/${projectId}`);
|
||||||
onBack,
|
|
||||||
backLabel = 'Zurück',
|
|
||||||
onSelectPlayer,
|
|
||||||
onSelectProject
|
|
||||||
}) => {
|
|
||||||
const [activeTab, setActiveTab] = useState<'overview' | 'residents' | 'ventures'>('overview');
|
const [activeTab, setActiveTab] = useState<'overview' | 'residents' | 'ventures'>('overview');
|
||||||
const [residents, setResidents] = useState<Player[]>([]);
|
const [residents, setResidents] = useState<Player[]>([]);
|
||||||
const [ventures, setVentures] = useState<Project[]>([]);
|
const [ventures, setVentures] = useState<Project[]>([]);
|
||||||
@@ -25,6 +19,26 @@ const CityProfile: React.FC<CityProfileProps> = ({
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
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 = () => {
|
const loadCityData = () => {
|
||||||
// Load residents (players in this city)
|
// Load residents (players in this city)
|
||||||
const allPlayers = dbService.getPlayers();
|
const allPlayers = dbService.getPlayers();
|
||||||
@@ -46,8 +60,6 @@ const CityProfile: React.FC<CityProfileProps> = ({
|
|||||||
...city.cityStats
|
...city.cityStats
|
||||||
};
|
};
|
||||||
setCityStats(stats);
|
setCityStats(stats);
|
||||||
|
|
||||||
setLoading(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initial load
|
// Initial load
|
||||||
@@ -56,11 +68,21 @@ const CityProfile: React.FC<CityProfileProps> = ({
|
|||||||
// Subscribe to updates
|
// Subscribe to updates
|
||||||
const unsub = dbService.subscribe(loadCityData);
|
const unsub = dbService.subscribe(loadCityData);
|
||||||
return unsub;
|
return unsub;
|
||||||
}, [city.id]);
|
}, [city]);
|
||||||
|
|
||||||
|
if (loading || !city) {
|
||||||
|
return (
|
||||||
|
<div className="max-w-4xl mx-auto animate-in slide-in-from-right-4 duration-300">
|
||||||
|
<div className="flex justify-center py-20">
|
||||||
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-accentInfo"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="animate-in slide-in-from-right-4 duration-300">
|
<div className="animate-in slide-in-from-right-4 duration-300">
|
||||||
<button onClick={onBack} className="flex items-center gap-2 text-sm text-textMuted hover:text-textMain mb-6 transition-colors group">
|
<button onClick={() => navigate('/cities')} className="flex items-center gap-2 text-sm text-textMuted hover:text-textMain mb-6 transition-colors group">
|
||||||
<span className="group-hover:-translate-x-1 transition-transform">←</span> {backLabel}
|
<span className="group-hover:-translate-x-1 transition-transform">←</span> {backLabel}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { Player } from '../types';
|
import { Player } from '../types';
|
||||||
import { dbService } from '../services/DatabaseService';
|
import { dbService } from '../services/DatabaseService';
|
||||||
import { authService } from '../services/AuthService';
|
import { authService } from '../services/AuthService';
|
||||||
@@ -6,24 +7,22 @@ import InventoryGrid from '../components/InventoryGrid';
|
|||||||
import EditModal from '../components/EditModal';
|
import EditModal from '../components/EditModal';
|
||||||
import { Icons } from '../components/IconSet';
|
import { Icons } from '../components/IconSet';
|
||||||
|
|
||||||
interface PlayerProfileProps {
|
const PlayerProfile: React.FC = () => {
|
||||||
player: Player;
|
const { id } = useParams<{ id: string }>();
|
||||||
onBack: () => void;
|
const navigate = useNavigate();
|
||||||
}
|
const [player, setPlayer] = useState<Player | null>(null);
|
||||||
|
|
||||||
const PlayerProfile: React.FC<PlayerProfileProps> = ({ player: initialPlayer, onBack }) => {
|
|
||||||
const [player, setPlayer] = useState(initialPlayer);
|
|
||||||
const [currentUser, setCurrentUser] = useState(authService.getUser());
|
const [currentUser, setCurrentUser] = useState(authService.getUser());
|
||||||
|
|
||||||
// Edit State
|
// Edit State
|
||||||
const [isEditStoryOpen, setIsEditStoryOpen] = useState(false);
|
const [isEditStoryOpen, setIsEditStoryOpen] = useState(false);
|
||||||
const [isEditTagsOpen, setIsEditTagsOpen] = useState(false);
|
const [isEditTagsOpen, setIsEditTagsOpen] = useState(false);
|
||||||
const [isEditOrgOpen, setIsEditOrgOpen] = useState(false);
|
const [isEditOrgOpen, setIsEditOrgOpen] = useState(false);
|
||||||
const [ownedProjects, setOwnedProjects] = useState<any[]>([]);
|
const [ownedProjects, setOwnedProjects] = useState<any[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
// Is this the logged-in user's profile?
|
// Is this the logged-in user's profile?
|
||||||
const isOwner = currentUser?.linkedPlayerUuid === player.uuid;
|
const isOwner = currentUser?.linkedPlayerUuid === player?.uuid;
|
||||||
const playerOrg = dbService.getOrg(player.stats.organizationId || '');
|
const playerOrg = player ? dbService.getOrg(player.stats.organizationId || '') : null;
|
||||||
|
|
||||||
// Check if player is already linked to anyone in our mock/real DB logic
|
// 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),
|
// 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<PlayerProfileProps> = ({ player: initialPlayer, on
|
|||||||
const canClaim = !!currentUser && !currentUser.linkedPlayerUuid && !isOwner;
|
const canClaim = !!currentUser && !currentUser.linkedPlayerUuid && !isOwner;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Refresh player data from "DB" to ensure we have latest local edits
|
if (!id) {
|
||||||
const freshData = dbService.getPlayer(player.uuid);
|
navigate('/players');
|
||||||
if (freshData) setPlayer(freshData);
|
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
|
// Load owned projects
|
||||||
const allProjects = dbService.getProjects();
|
const allProjects = dbService.getProjects();
|
||||||
@@ -43,7 +58,17 @@ const PlayerProfile: React.FC<PlayerProfileProps> = ({ player: initialPlayer, on
|
|||||||
// Subscribe to auth to show/hide edit buttons
|
// Subscribe to auth to show/hide edit buttons
|
||||||
const unsub = authService.subscribe(u => setCurrentUser(u));
|
const unsub = authService.subscribe(u => setCurrentUser(u));
|
||||||
return unsub;
|
return unsub;
|
||||||
}, [player.uuid, player.username]);
|
}, [player]);
|
||||||
|
|
||||||
|
if (loading || !player) {
|
||||||
|
return (
|
||||||
|
<div className="max-w-4xl mx-auto animate-in slide-in-from-right-4 duration-300">
|
||||||
|
<div className="flex justify-center py-20">
|
||||||
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-accentInfo"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const handleSaveStory = (newStory: string) => {
|
const handleSaveStory = (newStory: string) => {
|
||||||
// Update local DB
|
// Update local DB
|
||||||
@@ -113,7 +138,7 @@ const PlayerProfile: React.FC<PlayerProfileProps> = ({ player: initialPlayer, on
|
|||||||
return (
|
return (
|
||||||
<div className="max-w-4xl mx-auto animate-in slide-in-from-right-4 duration-300">
|
<div className="max-w-4xl mx-auto animate-in slide-in-from-right-4 duration-300">
|
||||||
<div className="flex justify-between items-center mb-6">
|
<div className="flex justify-between items-center mb-6">
|
||||||
<button onClick={onBack} className="flex items-center gap-2 text-sm text-textMuted hover:text-textMain transition-colors">
|
<button onClick={() => navigate('/players')} className="flex items-center gap-2 text-sm text-textMuted hover:text-textMain transition-colors">
|
||||||
<span className="text-lg">←</span> Zurück zur Liste
|
<span className="text-lg">←</span> Zurück zur Liste
|
||||||
</button>
|
</button>
|
||||||
{canClaim && (
|
{canClaim && (
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { Project, ShopItem } from '../types';
|
import { Project, ShopItem } from '../types';
|
||||||
import { Icons, ItemIcon } from '../components/IconSet';
|
import { Icons, ItemIcon } from '../components/IconSet';
|
||||||
import { dbService } from '../services/DatabaseService';
|
import { dbService } from '../services/DatabaseService';
|
||||||
@@ -11,19 +12,11 @@ import BannerManagementModal from '../components/BannerManagementModal';
|
|||||||
import GalleryManagementModal from '../components/GalleryManagementModal';
|
import GalleryManagementModal from '../components/GalleryManagementModal';
|
||||||
import DeleteProjectModal from '../components/DeleteProjectModal';
|
import DeleteProjectModal from '../components/DeleteProjectModal';
|
||||||
|
|
||||||
interface ProjectProfileProps {
|
const ProjectProfile: React.FC = () => {
|
||||||
project: Project;
|
const { id } = useParams<{ id: string }>();
|
||||||
onBack: () => void;
|
const navigate = useNavigate();
|
||||||
onSelectPlayer: (id: string) => void;
|
const [project, setProject] = useState<Project | null>(null);
|
||||||
onSelectOrg: (id: string) => void;
|
const [loading, setLoading] = useState(true);
|
||||||
}
|
|
||||||
|
|
||||||
const ProjectProfile: React.FC<ProjectProfileProps> = ({
|
|
||||||
project,
|
|
||||||
onBack,
|
|
||||||
onSelectPlayer,
|
|
||||||
onSelectOrg
|
|
||||||
}) => {
|
|
||||||
const [activeTab, setActiveTab] = useState<'overview' | 'shop' | 'manage'>('overview');
|
const [activeTab, setActiveTab] = useState<'overview' | 'shop' | 'manage'>('overview');
|
||||||
const [user, setUser] = useState<DiscordUser | null>(null);
|
const [user, setUser] = useState<DiscordUser | null>(null);
|
||||||
const [org, setOrg] = useState<any>(null);
|
const [org, setOrg] = useState<any>(null);
|
||||||
@@ -43,6 +36,26 @@ const ProjectProfile: React.FC<ProjectProfileProps> = ({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
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
|
// Load associated org and owner player
|
||||||
if (project.associatedOrgId) {
|
if (project.associatedOrgId) {
|
||||||
const foundOrg = dbService.getOrg(project.associatedOrgId);
|
const foundOrg = dbService.getOrg(project.associatedOrgId);
|
||||||
@@ -55,16 +68,37 @@ const ProjectProfile: React.FC<ProjectProfileProps> = ({
|
|||||||
setOwnerPlayer(owner);
|
setOwnerPlayer(owner);
|
||||||
}, [project]);
|
}, [project]);
|
||||||
|
|
||||||
const hasShop = project.shopCatalog && project.shopCatalog.length > 0;
|
const onSelectPlayer = (playerId: string) => navigate(`/players/${playerId}`);
|
||||||
const isOwner = user?.linkedPlayerUuid && ownerPlayer && dbService.getPlayer(user.linkedPlayerUuid)?.username === project.owner;
|
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
|
// Group shop items
|
||||||
const services = 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') || [];
|
const products = project?.shopCatalog?.filter(i => i.type !== 'service') || [];
|
||||||
|
|
||||||
|
if (loading || !project) {
|
||||||
|
return (
|
||||||
|
<div className="max-w-4xl mx-auto animate-in slide-in-from-right-4 duration-300">
|
||||||
|
<div className="flex justify-center py-20">
|
||||||
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-accentInfo"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="animate-in slide-in-from-right-4 duration-300">
|
<div className="animate-in slide-in-from-right-4 duration-300">
|
||||||
<button onClick={onBack} className="flex items-center gap-2 text-sm text-textMuted hover:text-textMain mb-6 transition-colors group">
|
<button onClick={() => navigate('/projects')} className="flex items-center gap-2 text-sm text-textMuted hover:text-textMain mb-6 transition-colors group">
|
||||||
<span className="group-hover:-translate-x-1 transition-transform">←</span> Zurück zum Verzeichnis
|
<span className="group-hover:-translate-x-1 transition-transform">←</span> Zurück zum Verzeichnis
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user