161 lines
5.8 KiB
JavaScript
161 lines
5.8 KiB
JavaScript
import { NavLink, Navigate, Outlet, Route, Routes, useLocation } from 'react-router-dom'
|
|
import { LogOut } from 'lucide-react'
|
|
import { useAuth } from './context/AuthContext.jsx'
|
|
import { getMainNavItems } from './config/appNav.js'
|
|
import LoginScreen from './pages/LoginScreen.jsx'
|
|
import SetupScreen from './pages/SetupScreen.jsx'
|
|
import HomePage from './pages/HomePage.jsx'
|
|
import DialoguePage from './pages/DialoguePage.jsx'
|
|
import JournalSpacesPage from './pages/JournalSpacesPage.jsx'
|
|
import JournalSpacePage from './pages/JournalSpacePage.jsx'
|
|
import JournalTrashPage from './pages/JournalTrashPage.jsx'
|
|
import JournalDayPage from './pages/JournalDayPage.jsx'
|
|
import JournalEditorPage from './pages/JournalEditorPage.jsx'
|
|
import JournalSourcePage from './pages/JournalSourcePage.jsx'
|
|
import SettingsPage from './pages/SettingsPage.jsx'
|
|
import AdminHomePage from './pages/AdminHomePage.jsx'
|
|
import AdminUsersPage from './pages/AdminUsersPage.jsx'
|
|
import AdminPromptsPage from './pages/AdminPromptsPage.jsx'
|
|
import AdminPlaceholdersPage from './pages/AdminPlaceholdersPage.jsx'
|
|
import AdminFeaturesPage from './pages/AdminFeaturesPage.jsx'
|
|
import AdminDialoguePage from './pages/AdminDialoguePage.jsx'
|
|
import AdminProvidersPage from './pages/AdminProvidersPage.jsx'
|
|
import AdminIdentitiesPage from './pages/AdminIdentitiesPage.jsx'
|
|
import RequireAdmin from './layouts/RequireAdmin.jsx'
|
|
import AdminShell from './layouts/AdminShell.jsx'
|
|
import { UnsavedProvider, useUnsavedChanges } from './context/UnsavedChanges.jsx'
|
|
|
|
function navItemActive(pathname, item, routerIsActive) {
|
|
if (item.to.startsWith('/admin')) return pathname.startsWith('/admin')
|
|
if (item.to === '/journal') return pathname.startsWith('/journal')
|
|
return routerIsActive
|
|
}
|
|
|
|
function BottomNav({ isAdmin }) {
|
|
const items = getMainNavItems(isAdmin)
|
|
const loc = useLocation()
|
|
return (
|
|
<nav className="bottom-nav">
|
|
{items.map((item) => {
|
|
const Icon = item.icon
|
|
return (
|
|
<NavLink
|
|
key={item.to}
|
|
to={item.to}
|
|
className={({ isActive }) =>
|
|
navItemActive(loc.pathname, item, isActive) ? 'nav-item active' : 'nav-item'
|
|
}
|
|
end={item.to === '/'}
|
|
>
|
|
<Icon size={20} />
|
|
<span>{item.label}</span>
|
|
</NavLink>
|
|
)
|
|
})}
|
|
</nav>
|
|
)
|
|
}
|
|
|
|
function DesktopSidebar({ isAdmin, onLogout, name }) {
|
|
const items = getMainNavItems(isAdmin)
|
|
const loc = useLocation()
|
|
return (
|
|
<aside className="desktop-sidebar">
|
|
<div className="brand">Kanshō</div>
|
|
<nav>
|
|
{items.map((item) => {
|
|
const Icon = item.icon
|
|
return (
|
|
<NavLink
|
|
key={item.to}
|
|
to={item.to}
|
|
className={({ isActive }) =>
|
|
navItemActive(loc.pathname, item, isActive) ? 'nav-item active' : 'nav-item'
|
|
}
|
|
end={item.to === '/'}
|
|
>
|
|
<Icon size={18} />
|
|
<span>{item.label}</span>
|
|
</NavLink>
|
|
)
|
|
})}
|
|
</nav>
|
|
<div className="sidebar-footer">
|
|
<span className="user-name">{name}</span>
|
|
<button type="button" className="ghost" onClick={onLogout}>
|
|
<LogOut size={16} /> Abmelden
|
|
</button>
|
|
</div>
|
|
</aside>
|
|
)
|
|
}
|
|
|
|
function AppShell() {
|
|
const { session, isAdmin, logout } = useAuth()
|
|
const { confirmLeave } = useUnsavedChanges()
|
|
const onLogout = () => {
|
|
if (!confirmLeave()) return
|
|
logout()
|
|
}
|
|
return (
|
|
<div className="app-shell">
|
|
<DesktopSidebar isAdmin={isAdmin} onLogout={onLogout} name={session?.profile?.name} />
|
|
<div className="app-main">
|
|
<header className="mobile-header">
|
|
<strong>Kanshō</strong>
|
|
<button type="button" className="ghost" onClick={onLogout}>Abmelden</button>
|
|
</header>
|
|
<main className="page">
|
|
<Outlet />
|
|
</main>
|
|
<BottomNav isAdmin={isAdmin} />
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default function App() {
|
|
const { loading, needsSetup, session } = useAuth()
|
|
|
|
if (loading) {
|
|
return <div className="centered">Lädt …</div>
|
|
}
|
|
if (needsSetup) {
|
|
return <SetupScreen />
|
|
}
|
|
if (!session) {
|
|
return <LoginScreen />
|
|
}
|
|
|
|
return (
|
|
<UnsavedProvider>
|
|
<Routes>
|
|
<Route element={<AppShell />}>
|
|
<Route path="/" element={<HomePage />} />
|
|
<Route path="/journal" element={<JournalSpacesPage />} />
|
|
<Route path="/journal/:spaceId" element={<JournalSpacePage />} />
|
|
<Route path="/journal/:spaceId/trash" element={<JournalTrashPage />} />
|
|
<Route path="/journal/:spaceId/:dayId" element={<JournalDayPage />} />
|
|
<Route path="/journal/:spaceId/:dayId/entry" element={<JournalEditorPage />} />
|
|
<Route path="/journal/:spaceId/:dayId/source/:conversationId" element={<JournalSourcePage />} />
|
|
<Route path="/settings" element={<SettingsPage />} />
|
|
<Route element={<RequireAdmin />}>
|
|
<Route path="/dialog" element={<DialoguePage />} />
|
|
<Route path="/admin" element={<AdminShell />}>
|
|
<Route index element={<AdminHomePage />} />
|
|
<Route path="users" element={<AdminUsersPage />} />
|
|
<Route path="prompts" element={<AdminPromptsPage />} />
|
|
<Route path="placeholders" element={<AdminPlaceholdersPage />} />
|
|
<Route path="features" element={<AdminFeaturesPage />} />
|
|
<Route path="providers" element={<AdminProvidersPage />} />
|
|
<Route path="identities" element={<AdminIdentitiesPage />} />
|
|
<Route path="dialogue" element={<AdminDialoguePage />} />
|
|
</Route>
|
|
</Route>
|
|
</Route>
|
|
<Route path="*" element={<Navigate to="/" replace />} />
|
|
</Routes>
|
|
</UnsavedProvider>
|
|
)
|
|
}
|