Can copy invitation code to clipboard
This commit is contained in:
		
							
								
								
									
										16980
									
								
								geneit_app/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										16980
									
								
								geneit_app/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -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"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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>
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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",
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										43
									
								
								geneit_app/src/widgets/SnackbarProvider.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								geneit_app/src/widgets/SnackbarProvider.tsx
									
									
									
									
									
										Normal 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)!;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user