init
This commit is contained in:
121 files changed
+17641
No files matched your search
@@ -0,0 +1,75 @@
|
||||
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 { SettingsProvider, useSettings } from "./lib/settings";
|
||||
import { buildTheme, type ThemeMode } from "./theme";
|
||||
import BlockerPage from "./pages/BlockerPage";
|
||||
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.accent || "#6750A4", settings?.appearance.compact ?? false),
|
||||
[mode, settings?.appearance.accent, 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>
|
||||
);
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<SettingsProvider>
|
||||
<Shell />
|
||||
</SettingsProvider>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user