Create home page
This commit is contained in:
parent
e1739d9818
commit
1d32ca1559
56
central_frontend/package-lock.json
generated
56
central_frontend/package-lock.json
generated
@ -11,10 +11,13 @@
|
|||||||
"@emotion/react": "^11.11.4",
|
"@emotion/react": "^11.11.4",
|
||||||
"@emotion/styled": "^11.11.5",
|
"@emotion/styled": "^11.11.5",
|
||||||
"@fontsource/roboto": "^5.0.13",
|
"@fontsource/roboto": "^5.0.13",
|
||||||
|
"@mdi/js": "^7.4.47",
|
||||||
|
"@mdi/react": "^1.6.1",
|
||||||
"@mui/icons-material": "^5.15.21",
|
"@mui/icons-material": "^5.15.21",
|
||||||
"@mui/material": "^5.15.21",
|
"@mui/material": "^5.15.21",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1"
|
"react-dom": "^18.3.1",
|
||||||
|
"react-router-dom": "^6.24.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.3.3",
|
"@types/react": "^18.3.3",
|
||||||
@ -1138,6 +1141,19 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@mdi/js": {
|
||||||
|
"version": "7.4.47",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mdi/js/-/js-7.4.47.tgz",
|
||||||
|
"integrity": "sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ=="
|
||||||
|
},
|
||||||
|
"node_modules/@mdi/react": {
|
||||||
|
"version": "1.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mdi/react/-/react-1.6.1.tgz",
|
||||||
|
"integrity": "sha512-4qZeDcluDFGFTWkHs86VOlHkm6gnKaMql13/gpIcUQ8kzxHgpj31NuCkD8abECVfbULJ3shc7Yt4HJ6Wu6SN4w==",
|
||||||
|
"dependencies": {
|
||||||
|
"prop-types": "^15.7.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@mui/base": {
|
"node_modules/@mui/base": {
|
||||||
"version": "5.0.0-beta.40",
|
"version": "5.0.0-beta.40",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz",
|
||||||
@ -1427,6 +1443,14 @@
|
|||||||
"url": "https://opencollective.com/popperjs"
|
"url": "https://opencollective.com/popperjs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@remix-run/router": {
|
||||||
|
"version": "1.17.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.17.0.tgz",
|
||||||
|
"integrity": "sha512-2D6XaHEVvkCn682XBnipbJjgZUU7xjLtA4dGJRBVUKpEaDYOZMENZoZjAOSb7qirxt5RupjzZxz4fK2FO+EFPw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.18.0",
|
"version": "4.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz",
|
||||||
@ -3449,6 +3473,36 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-router": {
|
||||||
|
"version": "6.24.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.24.0.tgz",
|
||||||
|
"integrity": "sha512-sQrgJ5bXk7vbcC4BxQxeNa5UmboFm35we1AFK0VvQaz9g0LzxEIuLOhHIoZ8rnu9BO21ishGeL9no1WB76W/eg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@remix-run/router": "1.17.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-router-dom": {
|
||||||
|
"version": "6.24.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.24.0.tgz",
|
||||||
|
"integrity": "sha512-960sKuau6/yEwS8e+NVEidYQb1hNjAYM327gjEyXlc6r3Skf2vtwuJ2l7lssdegD2YjoKG5l8MsVyeTDlVeY8g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@remix-run/router": "1.17.0",
|
||||||
|
"react-router": "6.24.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8",
|
||||||
|
"react-dom": ">=16.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-transition-group": {
|
"node_modules/react-transition-group": {
|
||||||
"version": "4.4.5",
|
"version": "4.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||||
|
@ -13,10 +13,13 @@
|
|||||||
"@emotion/react": "^11.11.4",
|
"@emotion/react": "^11.11.4",
|
||||||
"@emotion/styled": "^11.11.5",
|
"@emotion/styled": "^11.11.5",
|
||||||
"@fontsource/roboto": "^5.0.13",
|
"@fontsource/roboto": "^5.0.13",
|
||||||
|
"@mdi/js": "^7.4.47",
|
||||||
|
"@mdi/react": "^1.6.1",
|
||||||
"@mui/icons-material": "^5.15.21",
|
"@mui/icons-material": "^5.15.21",
|
||||||
"@mui/material": "^5.15.21",
|
"@mui/material": "^5.15.21",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1"
|
"react-dom": "^18.3.1",
|
||||||
|
"react-router-dom": "^6.24.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.3.3",
|
"@types/react": "^18.3.3",
|
||||||
|
@ -1,10 +1,29 @@
|
|||||||
|
import {
|
||||||
|
Route,
|
||||||
|
RouterProvider,
|
||||||
|
createBrowserRouter,
|
||||||
|
createRoutesFromElements,
|
||||||
|
} from "react-router-dom";
|
||||||
import { AuthApi } from "./api/AuthApi";
|
import { AuthApi } from "./api/AuthApi";
|
||||||
import { ServerApi } from "./api/ServerApi";
|
import { ServerApi } from "./api/ServerApi";
|
||||||
import { LoginRoute } from "./routes/LoginRoute";
|
import { LoginRoute } from "./routes/LoginRoute";
|
||||||
|
import { NotFoundRoute } from "./routes/NotFoundRoute";
|
||||||
|
import { HomeRoute } from "./routes/HomeRoute";
|
||||||
|
import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage";
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
if (!AuthApi.SignedIn && !ServerApi.Config.auth_disabled)
|
if (!AuthApi.SignedIn && !ServerApi.Config.auth_disabled)
|
||||||
return <LoginRoute />;
|
return <LoginRoute />;
|
||||||
|
|
||||||
return <>logged in todo</>;
|
const router = createBrowserRouter(
|
||||||
|
createRoutesFromElements(
|
||||||
|
<Route path="*" element={<BaseAuthenticatedPage />}>
|
||||||
|
<Route path="" element={<HomeRoute />} />
|
||||||
|
|
||||||
|
<Route path="*" element={<NotFoundRoute />} />
|
||||||
|
</Route>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return <RouterProvider router={router} />;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { APIClient } from "./ApiClient";
|
import { APIClient } from "./ApiClient";
|
||||||
|
|
||||||
export interface AuthInfo {
|
export interface AuthInfo {
|
||||||
name: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TokenStateKey = "auth-state";
|
const TokenStateKey = "auth-state";
|
||||||
@ -60,11 +60,15 @@ export class AuthApi {
|
|||||||
* Sign out
|
* Sign out
|
||||||
*/
|
*/
|
||||||
static async SignOut(): Promise<void> {
|
static async SignOut(): Promise<void> {
|
||||||
|
this.UnsetAuthenticated();
|
||||||
|
|
||||||
|
try {
|
||||||
await APIClient.exec({
|
await APIClient.exec({
|
||||||
uri: "/auth/sign_out",
|
uri: "/auth/sign_out",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
});
|
});
|
||||||
|
} finally {
|
||||||
this.UnsetAuthenticated();
|
window.location.href = "/";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
3
central_frontend/src/routes/HomeRoute.tsx
Normal file
3
central_frontend/src/routes/HomeRoute.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export function HomeRoute(): React.ReactElement {
|
||||||
|
return <>home authenticated todo</>;
|
||||||
|
}
|
23
central_frontend/src/routes/NotFoundRoute.tsx
Normal file
23
central_frontend/src/routes/NotFoundRoute.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Button } from "@mui/material";
|
||||||
|
import { RouterLink } from "../widgets/RouterLink";
|
||||||
|
|
||||||
|
export function NotFoundRoute(): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
textAlign: "center",
|
||||||
|
flex: "1",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h1>404 Not found</h1>
|
||||||
|
<p>The page you requested was not found!</p>
|
||||||
|
<RouterLink to="/">
|
||||||
|
<Button>Go back home</Button>
|
||||||
|
</RouterLink>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
82
central_frontend/src/widgets/BaseAuthenticatedPage.tsx
Normal file
82
central_frontend/src/widgets/BaseAuthenticatedPage.tsx
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import { Box, Button } from "@mui/material";
|
||||||
|
import * as React from "react";
|
||||||
|
import { Outlet } from "react-router-dom";
|
||||||
|
import { AuthApi, AuthInfo } from "../api/AuthApi";
|
||||||
|
import { AsyncWidget } from "./AsyncWidget";
|
||||||
|
import { SolarEnergyAppBar } from "./SolarEnergyAppBar";
|
||||||
|
import { SolarEnergyNavList } from "./SolarEnergyNavList";
|
||||||
|
|
||||||
|
interface AuthInfoContext {
|
||||||
|
info: AuthInfo;
|
||||||
|
reloadAuthInfo: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthInfoContextK = React.createContext<AuthInfoContext | null>(null);
|
||||||
|
|
||||||
|
export function BaseAuthenticatedPage(): React.ReactElement {
|
||||||
|
const [authInfo, setAuthInfo] = React.useState<null | AuthInfo>(null);
|
||||||
|
|
||||||
|
const signOut = () => {
|
||||||
|
AuthApi.SignOut();
|
||||||
|
};
|
||||||
|
|
||||||
|
const load = async () => {
|
||||||
|
setAuthInfo(await AuthApi.GetAuthInfo());
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AsyncWidget
|
||||||
|
loadKey="1"
|
||||||
|
load={load}
|
||||||
|
errMsg="Failed to load user information!"
|
||||||
|
errAdditionalElement={() => (
|
||||||
|
<>
|
||||||
|
<Button onClick={signOut}>Sign out</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
build={() => (
|
||||||
|
<AuthInfoContextK.Provider
|
||||||
|
value={{
|
||||||
|
info: authInfo!,
|
||||||
|
reloadAuthInfo: load,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
component="div"
|
||||||
|
sx={{
|
||||||
|
minHeight: "100vh",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
backgroundColor: (theme) =>
|
||||||
|
theme.palette.mode === "light"
|
||||||
|
? theme.palette.grey[100]
|
||||||
|
: theme.palette.grey[900],
|
||||||
|
color: (theme) =>
|
||||||
|
theme.palette.mode === "light"
|
||||||
|
? theme.palette.grey[900]
|
||||||
|
: theme.palette.grey[100],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SolarEnergyAppBar onSignOut={signOut} />
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flex: "2",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SolarEnergyNavList />
|
||||||
|
<div style={{ flex: 1, display: "flex" }}>
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</AuthInfoContextK.Provider>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAuthInfo(): AuthInfoContext {
|
||||||
|
return React.useContext(AuthInfoContextK)!;
|
||||||
|
}
|
19
central_frontend/src/widgets/DarkThemeButton.tsx
Normal file
19
central_frontend/src/widgets/DarkThemeButton.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import Brightness7Icon from "@mui/icons-material/Brightness7";
|
||||||
|
import DarkModeIcon from "@mui/icons-material/DarkMode";
|
||||||
|
import { IconButton, Tooltip } from "@mui/material";
|
||||||
|
import { useDarkTheme } from "../hooks/context_providers/DarkThemeProvider";
|
||||||
|
|
||||||
|
export function DarkThemeButton(): React.ReactElement {
|
||||||
|
const darkTheme = useDarkTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip title="Activer / désactiver le mode sombre">
|
||||||
|
<IconButton
|
||||||
|
onClick={() => darkTheme.setEnabled(!darkTheme.enabled)}
|
||||||
|
style={{ color: "inherit" }}
|
||||||
|
>
|
||||||
|
{!darkTheme.enabled ? <DarkModeIcon /> : <Brightness7Icon />}
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
16
central_frontend/src/widgets/RouterLink.tsx
Normal file
16
central_frontend/src/widgets/RouterLink.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { PropsWithChildren } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
export function RouterLink(
|
||||||
|
p: PropsWithChildren<{ to: string; target?: React.HTMLAttributeAnchorTarget }>
|
||||||
|
): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
to={p.to}
|
||||||
|
target={p.target}
|
||||||
|
style={{ color: "inherit", textDecoration: "inherit" }}
|
||||||
|
>
|
||||||
|
{p.children}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
85
central_frontend/src/widgets/SolarEnergyAppBar.tsx
Normal file
85
central_frontend/src/widgets/SolarEnergyAppBar.tsx
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { mdiWhiteBalanceSunny } from "@mdi/js";
|
||||||
|
import Icon from "@mdi/react";
|
||||||
|
import SettingsIcon from "@mui/icons-material/Settings";
|
||||||
|
import { Button } from "@mui/material";
|
||||||
|
import AppBar from "@mui/material/AppBar";
|
||||||
|
import Menu from "@mui/material/Menu";
|
||||||
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
|
import Toolbar from "@mui/material/Toolbar";
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
import * as React from "react";
|
||||||
|
import { useAuthInfo } from "./BaseAuthenticatedPage";
|
||||||
|
import { DarkThemeButton } from "./DarkThemeButton";
|
||||||
|
import { RouterLink } from "./RouterLink";
|
||||||
|
|
||||||
|
export function SolarEnergyAppBar(p: {
|
||||||
|
onSignOut: () => void;
|
||||||
|
}): React.ReactElement {
|
||||||
|
const authInfo = useAuthInfo();
|
||||||
|
|
||||||
|
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||||
|
const handleMenu = (event: React.MouseEvent<HTMLElement>) => {
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseMenu = () => {
|
||||||
|
setAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const signOut = () => {
|
||||||
|
handleCloseMenu();
|
||||||
|
p.onSignOut();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppBar position="sticky">
|
||||||
|
<Toolbar>
|
||||||
|
<Icon
|
||||||
|
path={mdiWhiteBalanceSunny}
|
||||||
|
size={1}
|
||||||
|
style={{ marginRight: "1rem" }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
|
||||||
|
<RouterLink to="/">Solar Energy</RouterLink>
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<DarkThemeButton />
|
||||||
|
|
||||||
|
<Button size="large" color="inherit">
|
||||||
|
{authInfo!.info.id}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
size="large"
|
||||||
|
aria-label="account of current user"
|
||||||
|
aria-controls="menu-appbar"
|
||||||
|
aria-haspopup="true"
|
||||||
|
onClick={handleMenu}
|
||||||
|
color="inherit"
|
||||||
|
>
|
||||||
|
<SettingsIcon />
|
||||||
|
</Button>
|
||||||
|
<Menu
|
||||||
|
id="menu-appbar"
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: "top",
|
||||||
|
horizontal: "right",
|
||||||
|
}}
|
||||||
|
keepMounted
|
||||||
|
transformOrigin={{
|
||||||
|
vertical: "top",
|
||||||
|
horizontal: "right",
|
||||||
|
}}
|
||||||
|
open={Boolean(anchorEl)}
|
||||||
|
onClose={handleCloseMenu}
|
||||||
|
>
|
||||||
|
<MenuItem onClick={signOut}>Déconnexion</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
</Toolbar>
|
||||||
|
</AppBar>
|
||||||
|
);
|
||||||
|
}
|
59
central_frontend/src/widgets/SolarEnergyNavList.tsx
Normal file
59
central_frontend/src/widgets/SolarEnergyNavList.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import {
|
||||||
|
mdiAccountMultiple,
|
||||||
|
mdiAccountMusic,
|
||||||
|
mdiAlbum,
|
||||||
|
mdiApi,
|
||||||
|
mdiChartLine,
|
||||||
|
mdiCog,
|
||||||
|
mdiHome,
|
||||||
|
mdiInbox,
|
||||||
|
mdiMusic,
|
||||||
|
} from "@mdi/js";
|
||||||
|
import Icon from "@mdi/react";
|
||||||
|
import {
|
||||||
|
List,
|
||||||
|
ListItemButton,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemSecondaryAction,
|
||||||
|
ListItemText,
|
||||||
|
ListSubheader,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
import { useAuthInfo } from "./BaseAuthenticatedPage";
|
||||||
|
import { RouterLink } from "./RouterLink";
|
||||||
|
|
||||||
|
export function SolarEnergyNavList(): React.ReactElement {
|
||||||
|
const user = useAuthInfo().info;
|
||||||
|
return (
|
||||||
|
<List
|
||||||
|
dense
|
||||||
|
component="nav"
|
||||||
|
sx={{
|
||||||
|
minWidth: "200px",
|
||||||
|
backgroundColor: "background.paper",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<NavLink label="Home" uri="/" icon={<Icon path={mdiHome} size={1} />} />
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function NavLink(p: {
|
||||||
|
icon: React.ReactElement;
|
||||||
|
uri: string;
|
||||||
|
label: string;
|
||||||
|
secondaryAction?: React.ReactElement;
|
||||||
|
}): React.ReactElement {
|
||||||
|
const location = useLocation();
|
||||||
|
return (
|
||||||
|
<RouterLink to={p.uri}>
|
||||||
|
<ListItemButton selected={p.uri === location.pathname}>
|
||||||
|
<ListItemIcon>{p.icon}</ListItemIcon>
|
||||||
|
<ListItemText primary={p.label} />
|
||||||
|
{p.secondaryAction && (
|
||||||
|
<ListItemSecondaryAction>{p.secondaryAction}</ListItemSecondaryAction>
|
||||||
|
)}
|
||||||
|
</ListItemButton>
|
||||||
|
</RouterLink>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user