router
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Dashfront</title>
|
||||
</head>
|
||||
|
||||
60
package-lock.json
generated
60
package-lock.json
generated
@@ -14,7 +14,8 @@
|
||||
"@mui/material": "^9.1.2",
|
||||
"notistack": "^3.0.2",
|
||||
"react": "^19.2.7",
|
||||
"react-dom": "^19.2.7"
|
||||
"react-dom": "^19.2.7",
|
||||
"react-router-dom": "^7.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-react": "^6.0.2",
|
||||
@@ -1036,6 +1037,19 @@
|
||||
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
|
||||
"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/cosmiconfig": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
|
||||
@@ -1772,6 +1786,44 @@
|
||||
"integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "7.18.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.0.tgz",
|
||||
"integrity": "sha512-pTTGt8J+ji1NOmYnjzT+bAJy/1zD+Jp4ziO6cL7T3ZLvXKtusO7BpFqlRXitqpcPVqllsIXFHRMt+2/k3Xn6HQ==",
|
||||
"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.18.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.0.tgz",
|
||||
"integrity": "sha512-Fi0yY6kgtKae/Th2xibdWK0KSdYZ4B53Gyf6wRtomOKWgpNm7H7+DyfDhncdz9FKbpS+1jmDhg3F4WoGJ+yFOA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-router": "7.18.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-transition-group": {
|
||||
"version": "4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||
@@ -1858,6 +1910,12 @@
|
||||
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"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": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
"@mui/material": "^9.1.2",
|
||||
"notistack": "^3.0.2",
|
||||
"react": "^19.2.7",
|
||||
"react-dom": "^19.2.7"
|
||||
"react-dom": "^19.2.7",
|
||||
"react-router-dom": "^7.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-react": "^6.0.2",
|
||||
|
||||
12
public/favicon.svg
Normal file
12
public/favicon.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<rect width="32" height="32" rx="6" fill="#3b5bdb"/>
|
||||
<text
|
||||
x="16"
|
||||
y="22"
|
||||
text-anchor="middle"
|
||||
font-family="system-ui, sans-serif"
|
||||
font-size="18"
|
||||
font-weight="700"
|
||||
fill="#fff"
|
||||
>D</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 287 B |
29
src/App.jsx
29
src/App.jsx
@@ -1,10 +1,12 @@
|
||||
import Box from '@mui/material/Box';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
|
||||
import { useAuth } from './auth/useAuth';
|
||||
import LoginPage from './components/LoginPage';
|
||||
import AppShell from './components/AppShell';
|
||||
import SectionPage from './pages/SectionPage';
|
||||
import { NAV_ITEMS } from './nav';
|
||||
|
||||
// Top-level router by auth status. Kept tiny so it rarely re-renders.
|
||||
export default function App() {
|
||||
const { status } = useAuth();
|
||||
|
||||
@@ -23,5 +25,28 @@ export default function App() {
|
||||
);
|
||||
}
|
||||
|
||||
return status === 'authed' ? <AppShell /> : <LoginPage />;
|
||||
if (status !== 'authed') {
|
||||
return <LoginPage />;
|
||||
}
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<AppShell />}>
|
||||
{NAV_ITEMS.map(({ path, label }) =>
|
||||
path === '/' ? (
|
||||
<Route key={path} index element={<SectionPage title={label} />} />
|
||||
) : (
|
||||
<Route
|
||||
key={path}
|
||||
path={path.slice(1)}
|
||||
element={<SectionPage title={label} />}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
import { useState } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import List from '@mui/material/List';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import { NavLink, Outlet } from 'react-router-dom';
|
||||
import UserMenu from './UserMenu';
|
||||
import { NAV_ITEMS } from '../nav';
|
||||
|
||||
const SIDEBAR_WIDTH = 240;
|
||||
|
||||
// App layout. State lives in the children (UserMenu, page content) so the
|
||||
// shell itself does not re-render when their state changes.
|
||||
export default function AppShell() {
|
||||
const [activeId, setActiveId] = useState(NAV_ITEMS[0].id);
|
||||
const activeItem = NAV_ITEMS.find((item) => item.id === activeId) ?? NAV_ITEMS[0];
|
||||
const ActivePage = activeItem.Page;
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', minHeight: '100vh', bgcolor: 'grey.50' }}>
|
||||
<Box
|
||||
@@ -29,13 +23,17 @@ export default function AppShell() {
|
||||
}}
|
||||
>
|
||||
<List disablePadding sx={{ flexGrow: 1 }}>
|
||||
{NAV_ITEMS.map(({ id, label, icon: Icon }) => (
|
||||
{NAV_ITEMS.map(({ path, label, icon: Icon }) => (
|
||||
<ListItemButton
|
||||
key={id}
|
||||
disableGutters
|
||||
selected={id === activeId}
|
||||
onClick={() => setActiveId(id)}
|
||||
sx={{ borderRadius: 0, px: 2 }}
|
||||
key={path}
|
||||
component={NavLink}
|
||||
to={path}
|
||||
end={path === '/'}
|
||||
sx={{
|
||||
borderRadius: 0,
|
||||
px: 2,
|
||||
'&.active': { bgcolor: 'action.selected' },
|
||||
}}
|
||||
>
|
||||
<ListItemIcon sx={{ minWidth: 40 }}>
|
||||
<Icon />
|
||||
@@ -49,7 +47,7 @@ export default function AppShell() {
|
||||
</Box>
|
||||
|
||||
<Box component="main" sx={{ flexGrow: 1, py: 4, px: 3 }}>
|
||||
<ActivePage />
|
||||
<Outlet />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
13
src/components/PageLayout.jsx
Normal file
13
src/components/PageLayout.jsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import Box from '@mui/material/Box';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
export default function PageLayout({ title, children }) {
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h5" sx={{ fontWeight: 700, mb: 2 }}>
|
||||
{title}
|
||||
</Typography>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
20
src/nav.js
20
src/nav.js
@@ -1,11 +1,15 @@
|
||||
import DashboardIcon from '@mui/icons-material/Dashboard';
|
||||
import DashboardPage from './pages/DashboardPage';
|
||||
import HomeIcon from '@mui/icons-material/Home';
|
||||
import Inventory2Icon from '@mui/icons-material/Inventory2';
|
||||
import LocalShippingIcon from '@mui/icons-material/LocalShipping';
|
||||
import AssignmentIcon from '@mui/icons-material/Assignment';
|
||||
import PeopleIcon from '@mui/icons-material/People';
|
||||
import InfoIcon from '@mui/icons-material/Info';
|
||||
|
||||
export const NAV_ITEMS = [
|
||||
{
|
||||
id: 'dashboard',
|
||||
label: 'Dashboard',
|
||||
icon: DashboardIcon,
|
||||
Page: DashboardPage,
|
||||
},
|
||||
{ path: '/', label: 'Home', icon: HomeIcon },
|
||||
{ path: '/artikel', label: 'Artikel', icon: Inventory2Icon },
|
||||
{ path: '/lieferung', label: 'Lieferung', icon: LocalShippingIcon },
|
||||
{ path: '/auftrag', label: 'Auftrag', icon: AssignmentIcon },
|
||||
{ path: '/kunde', label: 'Kunde', icon: PeopleIcon },
|
||||
{ path: '/infos', label: 'Infos', icon: InfoIcon },
|
||||
];
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
export default function DashboardPage() {
|
||||
return (
|
||||
<Typography variant="h5" sx={{ fontWeight: 700 }}>
|
||||
Dashboard
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
10
src/pages/SectionPage.jsx
Normal file
10
src/pages/SectionPage.jsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import Typography from '@mui/material/Typography';
|
||||
import PageLayout from '../components/PageLayout';
|
||||
|
||||
export default function SectionPage({ title }) {
|
||||
return (
|
||||
<PageLayout title={title}>
|
||||
<Typography color="text.secondary">Inhalt folgt.</Typography>
|
||||
</PageLayout>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user