Files
kadence/frontend/src/App.tsx
T
2026-10-19 09:41:32 +03:00

111 lines
3.6 KiB
TypeScript

import { useEffect, useMemo, useState } from "react";
import { Alert, Box, CssBaseline, LinearProgress, Snackbar, ThemeProvider, useMediaQuery } from "@mui/material";
import { Navigate, Route, Routes } from "react-router-dom";
import AppShell from "./components/AppShell";
import { AuthProvider, useAuth } from "./lib/auth";
import { SettingsProvider, useSettings } from "./lib/settings";
import { buildTheme, type ThemeMode } from "./theme";
import BlockerPage from "./pages/BlockerPage";
import LoginPage from "./pages/LoginPage";
import ReportPage from "./pages/ReportPage";
import SettingsPage from "./pages/SettingsPage";
import TasksPage from "./pages/TasksPage";
import TimerPage from "./pages/TimerPage";
function Shell() {
const { settings, loading, error } = useSettings();
const prefersDark = useMediaQuery("(prefers-color-scheme: dark)");
const [toast, setToast] = useState<string | null>(null);
const mode: ThemeMode = useMemo(() => {
const preference = settings?.appearance.theme ?? "system";
if (preference === "system") return prefersDark ? "dark" : "light";
return preference;
}, [settings?.appearance.theme, prefersDark]);
const theme = useMemo(
() => buildTheme(mode, settings?.appearance.compact ?? false),
[mode, settings?.appearance.compact],
);
// Keep the browser chrome in step with the app's surface.
useEffect(() => {
document.documentElement.dataset.theme = mode;
document.body.style.backgroundColor = theme.palette.background.default;
}, [mode, theme]);
return (
<ThemeProvider theme={theme}>
<CssBaseline />
{loading && <LinearProgress />}
{error && (
<Box sx={{ p: 2 }}>
<Alert severity="error">
The backend is not reachable ({error}). Start it with{" "}
<code>docker compose up</code> or check the API token in Settings.
</Alert>
</Box>
)}
<AppShell onToast={setToast} dark={mode === "dark"}>
<Routes>
<Route path="/" element={<TimerPage onToast={setToast} dark={mode === "dark"} />} />
<Route path="/tasks" element={<TasksPage onToast={setToast} />} />
<Route path="/blocker" element={<BlockerPage onToast={setToast} />} />
<Route path="/report" element={<ReportPage dark={mode === "dark"} onToast={setToast} />} />
<Route path="/settings" element={<SettingsPage onToast={setToast} />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</AppShell>
<Snackbar
open={toast !== null}
autoHideDuration={5000}
onClose={() => setToast(null)}
message={toast ?? ""}
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
/>
</ThemeProvider>
);
}
function Gate() {
const { me, loading } = useAuth();
const prefersDark = useMediaQuery("(prefers-color-scheme: dark)");
if (loading || !me) {
// The paper theme, so the blank moment before `/api/auth/me` answers
// doesn't flash a default MUI white.
const theme = buildTheme(prefersDark ? "dark" : "light", false);
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<LinearProgress />
</ThemeProvider>
);
}
if (!me.authenticated) {
const theme = buildTheme(prefersDark ? "dark" : "light", false);
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<LoginPage />
</ThemeProvider>
);
}
return (
<SettingsProvider>
<Shell />
</SettingsProvider>
);
}
export default function App() {
return (
<AuthProvider>
<Gate />
</AuthProvider>
);
}