Can copy invitation code to clipboard

This commit is contained in:
Pierre HUBERT 2023-07-08 16:02:18 +02:00
parent 3a0f9c6e48
commit e3dea1512c
6 changed files with 1381 additions and 15728 deletions

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@emotion/react": "^11.11.0", "@emotion/react": "^11.11.0",
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.11.0",
"@fontsource/roboto": "^5.0.2", "@fontsource/roboto": "^5.0.2",
@ -21,7 +22,7 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router-dom": "^6.11.2", "react-router-dom": "^6.11.2",
"react-scripts": "5.0.1", "react-scripts": "^5.0.1",
"typescript": "^4.9.5", "typescript": "^4.9.5",
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4"
}, },

View File

@ -14,6 +14,7 @@ import { BrowserRouter } from "react-router-dom";
import { ConfirmDialogProvider } from "./widgets/ConfirmDialogProvider"; import { ConfirmDialogProvider } from "./widgets/ConfirmDialogProvider";
import { AlertDialogProvider } from "./widgets/AlertDialogProvider"; import { AlertDialogProvider } from "./widgets/AlertDialogProvider";
import { AsyncWidget } from "./widgets/AsyncWidget"; import { AsyncWidget } from "./widgets/AsyncWidget";
import { SnackbarProvider } from "./widgets/SnackbarProvider";
async function init() { async function init() {
try { try {
@ -26,14 +27,16 @@ async function init() {
<BrowserRouter> <BrowserRouter>
<AlertDialogProvider> <AlertDialogProvider>
<ConfirmDialogProvider> <ConfirmDialogProvider>
<div style={{ height: "100vh" }}> <SnackbarProvider>
<AsyncWidget <div style={{ height: "100vh" }}>
loadKey={1} <AsyncWidget
load={async () => await ServerApi.LoadConfig()} loadKey={1}
errMsg="Echec de la connexion au serveur pour la récupération de la configuration statique !" load={async () => await ServerApi.LoadConfig()}
build={() => <App />} errMsg="Echec de la connexion au serveur pour la récupération de la configuration statique !"
/> build={() => <App />}
</div> />
</div>
</SnackbarProvider>
</ConfirmDialogProvider> </ConfirmDialogProvider>
</AlertDialogProvider> </AlertDialogProvider>
</BrowserRouter> </BrowserRouter>

View File

@ -1,7 +1,7 @@
import { mdiFamilyTree } from "@mdi/js"; import { mdiFamilyTree } from "@mdi/js";
import Icon from "@mdi/react"; import Icon from "@mdi/react";
import SettingsIcon from "@mui/icons-material/Settings"; import SettingsIcon from "@mui/icons-material/Settings";
import { Button } from "@mui/material"; import { Box, Button } from "@mui/material";
import AppBar from "@mui/material/AppBar"; import AppBar from "@mui/material/AppBar";
import Menu from "@mui/material/Menu"; import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem"; import MenuItem from "@mui/material/MenuItem";
@ -61,11 +61,20 @@ export function BaseAuthenticatedPage(): React.ReactElement {
reloadUserInfo: load, reloadUserInfo: load,
}} }}
> >
<div <Box
style={{ component="div"
sx={{
minHeight: "100vh", minHeight: "100vh",
display: "flex", display: "flex",
flexDirection: "column", 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],
}} }}
> >
<AppBar position="sticky"> <AppBar position="sticky">
@ -122,7 +131,7 @@ export function BaseAuthenticatedPage(): React.ReactElement {
</Toolbar> </Toolbar>
</AppBar> </AppBar>
<Outlet /> <Outlet />
</div> </Box>
</UserContextK.Provider> </UserContextK.Provider>
)} )}
/> />

View File

@ -4,6 +4,7 @@ import {
mdiCrowd, mdiCrowd,
mdiFamilyTree, mdiFamilyTree,
mdiHumanMaleFemale, mdiHumanMaleFemale,
mdiLockCheck,
} from "@mdi/js"; } from "@mdi/js";
import Icon from "@mdi/react"; import Icon from "@mdi/react";
import HomeIcon from "@mui/icons-material/Home"; import HomeIcon from "@mui/icons-material/Home";
@ -15,12 +16,14 @@ import {
ListItemIcon, ListItemIcon,
ListItemText, ListItemText,
ListSubheader, ListSubheader,
Tooltip,
} from "@mui/material"; } from "@mui/material";
import React from "react"; import React from "react";
import { Outlet, useLocation, useParams } from "react-router-dom"; import { Outlet, useLocation, useParams } from "react-router-dom";
import { Family, FamilyApi } from "../api/FamilyApi"; import { Family, FamilyApi } from "../api/FamilyApi";
import { AsyncWidget } from "./AsyncWidget"; import { AsyncWidget } from "./AsyncWidget";
import { RouterLink } from "./RouterLink"; import { RouterLink } from "./RouterLink";
import { useSnackbar } from "./SnackbarProvider";
interface FamilyContext { interface FamilyContext {
family: Family; family: Family;
@ -31,6 +34,7 @@ const FamilyContextK = React.createContext<FamilyContext | null>(null);
export function BaseFamilyRoute(): React.ReactElement { export function BaseFamilyRoute(): React.ReactElement {
const { familyId } = useParams(); const { familyId } = useParams();
const snackbar = useSnackbar();
const [family, setFamily] = React.useState<null | Family>(null); const [family, setFamily] = React.useState<null | Family>(null);
@ -45,6 +49,11 @@ export function BaseFamilyRoute(): React.ReactElement {
setFamily(null); setFamily(null);
}; };
const copyInvitationCode = async () => {
navigator.clipboard.writeText(family!.invitation_code);
snackbar("Le code d'invitation a été copié dans le presse papier !");
};
return ( return (
<AsyncWidget <AsyncWidget
ready={family != null} ready={family != null}
@ -58,8 +67,21 @@ export function BaseFamilyRoute(): React.ReactElement {
reloadFamilyInfo: onReload, reloadFamilyInfo: onReload,
}} }}
> >
<Box sx={{ display: "flex", flex: "2" }}> <Box
<List component="nav"> sx={{
display: "flex",
flex: "2",
}}
>
<List
component="nav"
sx={{
backgroundColor: (theme) =>
theme.palette.mode === "light"
? theme.palette.grey[100]
: "background.paper",
}}
>
<FamilyLink icon={<HomeIcon />} label="Accueil" uri="" /> <FamilyLink icon={<HomeIcon />} label="Accueil" uri="" />
<FamilyLink <FamilyLink
@ -81,9 +103,7 @@ export function BaseFamilyRoute(): React.ReactElement {
/> />
<Divider sx={{ my: 1 }} /> <Divider sx={{ my: 1 }} />
<ListSubheader component="div" inset> <ListSubheader component="div">Administration</ListSubheader>
Administration
</ListSubheader>
<FamilyLink <FamilyLink
icon={<Icon path={mdiAccountMultiple} size={1} />} icon={<Icon path={mdiAccountMultiple} size={1} />}
@ -96,15 +116,24 @@ export function BaseFamilyRoute(): React.ReactElement {
label="Paramètres" label="Paramètres"
uri="settings" uri="settings"
/> />
{/* Invitation code */}
<Tooltip title="Copier le code d'invitation dans le presse papier">
<ListItemButton onClick={copyInvitationCode}>
<ListItemIcon>
<Icon path={mdiLockCheck} size={1} />
</ListItemIcon>
<ListItemText
primary="Code d'invitation"
secondary={family?.invitation_code}
/>
</ListItemButton>
</Tooltip>
</List> </List>
<Box <Box
component="main" component="main"
sx={{ sx={{
backgroundColor: (theme) =>
theme.palette.mode === "light"
? theme.palette.grey[100]
: theme.palette.grey[900],
flexGrow: 1, flexGrow: 1,
overflow: "auto", overflow: "auto",
padding: "20px", padding: "20px",

View File

@ -0,0 +1,43 @@
import { Snackbar } from "@mui/material";
import React, { PropsWithChildren } from "react";
type SnackbarContext = (message: string, duration?: number) => void;
const SnackbarContextK = React.createContext<SnackbarContext | null>(null);
export function SnackbarProvider(p: PropsWithChildren): React.ReactElement {
const [open, setOpen] = React.useState(false);
const [message, setMessage] = React.useState("");
const [duration, setDuration] = React.useState(0);
const handleClose = () => {
setOpen(false);
};
const hook: SnackbarContext = (message, duration) => {
setMessage(message);
setDuration(duration ?? 6000);
setOpen(true);
};
return (
<>
<SnackbarContextK.Provider value={hook}>
{p.children}
</SnackbarContextK.Provider>
<Snackbar
open={open}
autoHideDuration={duration}
onClose={handleClose}
message={message}
/>
</>
);
}
export function useSnackbar(): SnackbarContext {
return React.useContext(SnackbarContextK)!;
}