Ask user confirmation before leaving an unsaved form
This commit is contained in:
		@@ -1,5 +1,11 @@
 | 
				
			|||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import { Route, Routes } from "react-router-dom";
 | 
					import {
 | 
				
			||||||
 | 
					  Route,
 | 
				
			||||||
 | 
					  RouterProvider,
 | 
				
			||||||
 | 
					  Routes,
 | 
				
			||||||
 | 
					  createBrowserRouter,
 | 
				
			||||||
 | 
					  createRoutesFromElements,
 | 
				
			||||||
 | 
					} from "react-router-dom";
 | 
				
			||||||
import "./App.css";
 | 
					import "./App.css";
 | 
				
			||||||
import { AuthApi } from "./api/AuthApi";
 | 
					import { AuthApi } from "./api/AuthApi";
 | 
				
			||||||
import { DeleteAccountRoute } from "./routes/DeleteAccountRoute";
 | 
					import { DeleteAccountRoute } from "./routes/DeleteAccountRoute";
 | 
				
			||||||
@@ -41,9 +47,9 @@ export function App(): React.ReactElement {
 | 
				
			|||||||
    setSignedIn: (s) => setSignedIn(s),
 | 
					    setSignedIn: (s) => setSignedIn(s),
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  const router = createBrowserRouter(
 | 
				
			||||||
    <AuthContextK.Provider value={context}>
 | 
					    createRoutesFromElements(
 | 
				
			||||||
      <Routes>
 | 
					      <>
 | 
				
			||||||
        <Route path="delete_account" element={<DeleteAccountRoute />} />
 | 
					        <Route path="delete_account" element={<DeleteAccountRoute />} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        {signedIn ? (
 | 
					        {signedIn ? (
 | 
				
			||||||
@@ -80,7 +86,13 @@ export function App(): React.ReactElement {
 | 
				
			|||||||
            <Route path="*" element={<NotFoundRoute />} />
 | 
					            <Route path="*" element={<NotFoundRoute />} />
 | 
				
			||||||
          </Route>
 | 
					          </Route>
 | 
				
			||||||
        )}
 | 
					        )}
 | 
				
			||||||
      </Routes>
 | 
					      </>
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <AuthContextK.Provider value={context}>
 | 
				
			||||||
 | 
					      <RouterProvider router={router} />
 | 
				
			||||||
    </AuthContextK.Provider>
 | 
					    </AuthContextK.Provider>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@ import React from "react";
 | 
				
			|||||||
import { TextInputDialog } from "./TextInputDialog";
 | 
					import { TextInputDialog } from "./TextInputDialog";
 | 
				
			||||||
import { ServerApi } from "../api/ServerApi";
 | 
					import { ServerApi } from "../api/ServerApi";
 | 
				
			||||||
import { FamilyApi } from "../api/FamilyApi";
 | 
					import { FamilyApi } from "../api/FamilyApi";
 | 
				
			||||||
import { useAlert } from "../context_providers/AlertDialogProvider";
 | 
					import { useAlert } from "../hooks/context_providers/AlertDialogProvider";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function CreateFamilyDialog(p: {
 | 
					export function CreateFamilyDialog(p: {
 | 
				
			||||||
  open: boolean;
 | 
					  open: boolean;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@ import React from "react";
 | 
				
			|||||||
import { TextInputDialog } from "./TextInputDialog";
 | 
					import { TextInputDialog } from "./TextInputDialog";
 | 
				
			||||||
import { ServerApi } from "../api/ServerApi";
 | 
					import { ServerApi } from "../api/ServerApi";
 | 
				
			||||||
import { FamilyApi, JoinFamilyResult } from "../api/FamilyApi";
 | 
					import { FamilyApi, JoinFamilyResult } from "../api/FamilyApi";
 | 
				
			||||||
import { useAlert } from "../context_providers/AlertDialogProvider";
 | 
					import { useAlert } from "../hooks/context_providers/AlertDialogProvider";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function JoinFamilyDialog(p: {
 | 
					export function JoinFamilyDialog(p: {
 | 
				
			||||||
  open: boolean;
 | 
					  open: boolean;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,11 +11,11 @@ import "@fontsource/roboto/400.css";
 | 
				
			|||||||
import "@fontsource/roboto/500.css";
 | 
					import "@fontsource/roboto/500.css";
 | 
				
			||||||
import "@fontsource/roboto/700.css";
 | 
					import "@fontsource/roboto/700.css";
 | 
				
			||||||
import { BrowserRouter } from "react-router-dom";
 | 
					import { BrowserRouter } from "react-router-dom";
 | 
				
			||||||
import { ConfirmDialogProvider } from "./context_providers/ConfirmDialogProvider";
 | 
					import { ConfirmDialogProvider } from "./hooks/context_providers/ConfirmDialogProvider";
 | 
				
			||||||
import { AlertDialogProvider } from "./context_providers/AlertDialogProvider";
 | 
					import { AlertDialogProvider } from "./hooks/context_providers/AlertDialogProvider";
 | 
				
			||||||
import { AsyncWidget } from "./widgets/AsyncWidget";
 | 
					import { AsyncWidget } from "./widgets/AsyncWidget";
 | 
				
			||||||
import { SnackbarProvider } from "./context_providers/SnackbarProvider";
 | 
					import { SnackbarProvider } from "./hooks/context_providers/SnackbarProvider";
 | 
				
			||||||
import { DarkThemeProvider } from "./context_providers/DarkThemeProvider";
 | 
					import { DarkThemeProvider } from "./hooks/context_providers/DarkThemeProvider";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function init() {
 | 
					async function init() {
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
@@ -25,24 +25,22 @@ async function init() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    root.render(
 | 
					    root.render(
 | 
				
			||||||
      <React.StrictMode>
 | 
					      <React.StrictMode>
 | 
				
			||||||
        <BrowserRouter>
 | 
					        <DarkThemeProvider>
 | 
				
			||||||
          <DarkThemeProvider>
 | 
					          <AlertDialogProvider>
 | 
				
			||||||
            <AlertDialogProvider>
 | 
					            <ConfirmDialogProvider>
 | 
				
			||||||
              <ConfirmDialogProvider>
 | 
					              <SnackbarProvider>
 | 
				
			||||||
                <SnackbarProvider>
 | 
					                <div style={{ height: "100vh" }}>
 | 
				
			||||||
                  <div style={{ height: "100vh" }}>
 | 
					                  <AsyncWidget
 | 
				
			||||||
                    <AsyncWidget
 | 
					                    loadKey={1}
 | 
				
			||||||
                      loadKey={1}
 | 
					                    load={async () => await ServerApi.LoadConfig()}
 | 
				
			||||||
                      load={async () => await ServerApi.LoadConfig()}
 | 
					                    errMsg="Echec de la connexion au serveur pour la récupération de la configuration statique !"
 | 
				
			||||||
                      errMsg="Echec de la connexion au serveur pour la récupération de la configuration statique !"
 | 
					                    build={() => <App />}
 | 
				
			||||||
                      build={() => <App />}
 | 
					                  />
 | 
				
			||||||
                    />
 | 
					                </div>
 | 
				
			||||||
                  </div>
 | 
					              </SnackbarProvider>
 | 
				
			||||||
                </SnackbarProvider>
 | 
					            </ConfirmDialogProvider>
 | 
				
			||||||
              </ConfirmDialogProvider>
 | 
					          </AlertDialogProvider>
 | 
				
			||||||
            </AlertDialogProvider>
 | 
					        </DarkThemeProvider>
 | 
				
			||||||
          </DarkThemeProvider>
 | 
					 | 
				
			||||||
        </BrowserRouter>
 | 
					 | 
				
			||||||
      </React.StrictMode>
 | 
					      </React.StrictMode>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  } catch (e) {
 | 
					  } catch (e) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,9 +3,9 @@ import React from "react";
 | 
				
			|||||||
import { Link, useLocation, useNavigate } from "react-router-dom";
 | 
					import { Link, useLocation, useNavigate } from "react-router-dom";
 | 
				
			||||||
import { AuthApi } from "../api/AuthApi";
 | 
					import { AuthApi } from "../api/AuthApi";
 | 
				
			||||||
import { DeleteAccountTokenInfo, UserApi } from "../api/UserApi";
 | 
					import { DeleteAccountTokenInfo, UserApi } from "../api/UserApi";
 | 
				
			||||||
import { useAlert } from "../context_providers/AlertDialogProvider";
 | 
					import { useAlert } from "../hooks/context_providers/AlertDialogProvider";
 | 
				
			||||||
import { AsyncWidget } from "../widgets/AsyncWidget";
 | 
					import { AsyncWidget } from "../widgets/AsyncWidget";
 | 
				
			||||||
import { useConfirm } from "../context_providers/ConfirmDialogProvider";
 | 
					import { useConfirm } from "../hooks/context_providers/ConfirmDialogProvider";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function DeleteAccountRoute(): React.ReactElement {
 | 
					export function DeleteAccountRoute(): React.ReactElement {
 | 
				
			||||||
  const alert = useAlert();
 | 
					  const alert = useAlert();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,9 +14,9 @@ import React from "react";
 | 
				
			|||||||
import { Family, FamilyApi } from "../api/FamilyApi";
 | 
					import { Family, FamilyApi } from "../api/FamilyApi";
 | 
				
			||||||
import { CreateFamilyDialog } from "../dialogs/CreateFamilyDialog";
 | 
					import { CreateFamilyDialog } from "../dialogs/CreateFamilyDialog";
 | 
				
			||||||
import { JoinFamilyDialog } from "../dialogs/JoinFamilyDialog";
 | 
					import { JoinFamilyDialog } from "../dialogs/JoinFamilyDialog";
 | 
				
			||||||
import { useAlert } from "../context_providers/AlertDialogProvider";
 | 
					import { useAlert } from "../hooks/context_providers/AlertDialogProvider";
 | 
				
			||||||
import { AsyncWidget } from "../widgets/AsyncWidget";
 | 
					import { AsyncWidget } from "../widgets/AsyncWidget";
 | 
				
			||||||
import { useConfirm } from "../context_providers/ConfirmDialogProvider";
 | 
					import { useConfirm } from "../hooks/context_providers/ConfirmDialogProvider";
 | 
				
			||||||
import { RouterLink } from "../widgets/RouterLink";
 | 
					import { RouterLink } from "../widgets/RouterLink";
 | 
				
			||||||
import { TimeWidget } from "../widgets/TimeWidget";
 | 
					import { TimeWidget } from "../widgets/TimeWidget";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,9 +13,9 @@ import {
 | 
				
			|||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import { ServerApi } from "../api/ServerApi";
 | 
					import { ServerApi } from "../api/ServerApi";
 | 
				
			||||||
import { ReplacePasswordResponse, User, UserApi } from "../api/UserApi";
 | 
					import { ReplacePasswordResponse, User, UserApi } from "../api/UserApi";
 | 
				
			||||||
import { useAlert } from "../context_providers/AlertDialogProvider";
 | 
					import { useAlert } from "../hooks/context_providers/AlertDialogProvider";
 | 
				
			||||||
import { useUser } from "../widgets/BaseAuthenticatedPage";
 | 
					import { useUser } from "../widgets/BaseAuthenticatedPage";
 | 
				
			||||||
import { useConfirm } from "../context_providers/ConfirmDialogProvider";
 | 
					import { useConfirm } from "../hooks/context_providers/ConfirmDialogProvider";
 | 
				
			||||||
import { PasswordInput } from "../widgets/PasswordInput";
 | 
					import { PasswordInput } from "../widgets/PasswordInput";
 | 
				
			||||||
import { formatDate } from "../widgets/TimeWidget";
 | 
					import { formatDate } from "../widgets/TimeWidget";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,21 +1,22 @@
 | 
				
			|||||||
import ClearIcon from "@mui/icons-material/Clear";
 | 
					import ClearIcon from "@mui/icons-material/Clear";
 | 
				
			||||||
 | 
					import DeleteIcon from "@mui/icons-material/Delete";
 | 
				
			||||||
 | 
					import EditIcon from "@mui/icons-material/Edit";
 | 
				
			||||||
import SaveIcon from "@mui/icons-material/Save";
 | 
					import SaveIcon from "@mui/icons-material/Save";
 | 
				
			||||||
import { Button, Grid, Stack } from "@mui/material";
 | 
					import { Button, Grid, Stack } from "@mui/material";
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import EditIcon from "@mui/icons-material/Edit";
 | 
					 | 
				
			||||||
import DeleteIcon from "@mui/icons-material/Delete";
 | 
					 | 
				
			||||||
import { useNavigate, useParams } from "react-router-dom";
 | 
					import { useNavigate, useParams } from "react-router-dom";
 | 
				
			||||||
import { Member, MemberApi } from "../../api/MemberApi";
 | 
					import { Member, MemberApi } from "../../api/MemberApi";
 | 
				
			||||||
import { ServerApi } from "../../api/ServerApi";
 | 
					import { ServerApi } from "../../api/ServerApi";
 | 
				
			||||||
import { useAlert } from "../../context_providers/AlertDialogProvider";
 | 
					import { useAlert } from "../../hooks/context_providers/AlertDialogProvider";
 | 
				
			||||||
import { useConfirm } from "../../context_providers/ConfirmDialogProvider";
 | 
					import { useConfirm } from "../../hooks/context_providers/ConfirmDialogProvider";
 | 
				
			||||||
 | 
					import { useSnackbar } from "../../hooks/context_providers/SnackbarProvider";
 | 
				
			||||||
 | 
					import { AsyncWidget } from "../../widgets/AsyncWidget";
 | 
				
			||||||
import { useFamily } from "../../widgets/BaseFamilyRoute";
 | 
					import { useFamily } from "../../widgets/BaseFamilyRoute";
 | 
				
			||||||
 | 
					import { ConfirmLeaveWithoutSaveDialog } from "../../widgets/ConfirmLeaveWithoutSaveDialog";
 | 
				
			||||||
import { FamilyPageTitle } from "../../widgets/FamilyPageTitle";
 | 
					import { FamilyPageTitle } from "../../widgets/FamilyPageTitle";
 | 
				
			||||||
import { PropEdit } from "../../widgets/PropEdit";
 | 
					import { PropEdit } from "../../widgets/PropEdit";
 | 
				
			||||||
import { PropertiesBox } from "../../widgets/PropertiesBox";
 | 
					import { PropertiesBox } from "../../widgets/PropertiesBox";
 | 
				
			||||||
import { SexSelection } from "../../widgets/SexSelection";
 | 
					import { SexSelection } from "../../widgets/SexSelection";
 | 
				
			||||||
import { useSnackbar } from "../../context_providers/SnackbarProvider";
 | 
					 | 
				
			||||||
import { AsyncWidget } from "../../widgets/AsyncWidget";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Create a new member route
 | 
					 * Create a new member route
 | 
				
			||||||
@@ -24,6 +25,7 @@ export function FamilyCreateMemberRoute(): React.ReactElement {
 | 
				
			|||||||
  const alert = useAlert();
 | 
					  const alert = useAlert();
 | 
				
			||||||
  const snackbar = useSnackbar();
 | 
					  const snackbar = useSnackbar();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [shouldQuit, setShouldQuit] = React.useState(false);
 | 
				
			||||||
  const n = useNavigate();
 | 
					  const n = useNavigate();
 | 
				
			||||||
  const family = useFamily();
 | 
					  const family = useFamily();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -33,6 +35,7 @@ export function FamilyCreateMemberRoute(): React.ReactElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      // TODO : trigger update
 | 
					      // TODO : trigger update
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      setShouldQuit(true);
 | 
				
			||||||
      n(family.family.URL(`member/${r.id}`));
 | 
					      n(family.family.URL(`member/${r.id}`));
 | 
				
			||||||
      snackbar(`La fiche pour ${r.fullName} a été créée avec succès !`);
 | 
					      snackbar(`La fiche pour ${r.fullName} a été créée avec succès !`);
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
@@ -41,13 +44,19 @@ export function FamilyCreateMemberRoute(): React.ReactElement {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const cancel = () => {
 | 
				
			||||||
 | 
					    setShouldQuit(true);
 | 
				
			||||||
 | 
					    n(family.family.URL("members"));
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <MemberPage
 | 
					    <MemberPage
 | 
				
			||||||
      member={Member.New(family.family.family_id)}
 | 
					      member={Member.New(family.family.family_id)}
 | 
				
			||||||
      creating={true}
 | 
					      creating={true}
 | 
				
			||||||
      editing={true}
 | 
					      editing={true}
 | 
				
			||||||
      onCancel={() => n(family.family.URL("members"))}
 | 
					      onCancel={cancel}
 | 
				
			||||||
      onSave={create}
 | 
					      onSave={create}
 | 
				
			||||||
 | 
					      shouldAllowLeaving={shouldQuit}
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -118,6 +127,8 @@ export function FamilyEditMemberRoute(): React.ReactElement {
 | 
				
			|||||||
  const alert = useAlert();
 | 
					  const alert = useAlert();
 | 
				
			||||||
  const snackbar = useSnackbar();
 | 
					  const snackbar = useSnackbar();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [shouldQuit, setShouldQuit] = React.useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const family = useFamily();
 | 
					  const family = useFamily();
 | 
				
			||||||
  const { memberId } = useParams();
 | 
					  const { memberId } = useParams();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -126,6 +137,11 @@ export function FamilyEditMemberRoute(): React.ReactElement {
 | 
				
			|||||||
    setMember(await MemberApi.GetSingle(family.familyId, Number(memberId)));
 | 
					    setMember(await MemberApi.GetSingle(family.familyId, Number(memberId)));
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const cancel = () => {
 | 
				
			||||||
 | 
					    setShouldQuit(true);
 | 
				
			||||||
 | 
					    n(family.family.URL(`member/${member!.id}`));
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const save = async (m: Member) => {
 | 
					  const save = async (m: Member) => {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      await MemberApi.Update(m);
 | 
					      await MemberApi.Update(m);
 | 
				
			||||||
@@ -134,6 +150,7 @@ export function FamilyEditMemberRoute(): React.ReactElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      // TODO : update family hook info
 | 
					      // TODO : update family hook info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      setShouldQuit(true);
 | 
				
			||||||
      n(family.family.URL(`member/${member!.id}`));
 | 
					      n(family.family.URL(`member/${member!.id}`));
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
      console.error(e);
 | 
					      console.error(e);
 | 
				
			||||||
@@ -151,8 +168,9 @@ export function FamilyEditMemberRoute(): React.ReactElement {
 | 
				
			|||||||
          member={member!}
 | 
					          member={member!}
 | 
				
			||||||
          creating={false}
 | 
					          creating={false}
 | 
				
			||||||
          editing={true}
 | 
					          editing={true}
 | 
				
			||||||
          onCancel={() => n(family.family.URL(`member/${member!.id}`))}
 | 
					          onCancel={cancel}
 | 
				
			||||||
          onSave={save}
 | 
					          onSave={save}
 | 
				
			||||||
 | 
					          shouldAllowLeaving={shouldQuit}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
@@ -163,6 +181,7 @@ export function MemberPage(p: {
 | 
				
			|||||||
  member: Member;
 | 
					  member: Member;
 | 
				
			||||||
  editing: boolean;
 | 
					  editing: boolean;
 | 
				
			||||||
  creating: boolean;
 | 
					  creating: boolean;
 | 
				
			||||||
 | 
					  shouldAllowLeaving?: boolean;
 | 
				
			||||||
  onCancel?: () => void;
 | 
					  onCancel?: () => void;
 | 
				
			||||||
  onSave?: (m: Member) => void;
 | 
					  onSave?: (m: Member) => void;
 | 
				
			||||||
  onRequestEdit?: () => void;
 | 
					  onRequestEdit?: () => void;
 | 
				
			||||||
@@ -170,7 +189,6 @@ export function MemberPage(p: {
 | 
				
			|||||||
}): React.ReactElement {
 | 
					}): React.ReactElement {
 | 
				
			||||||
  const confirm = useConfirm();
 | 
					  const confirm = useConfirm();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // TODO : add confirmation when leaving page https://dev.to/bangash1996/detecting-user-leaving-page-with-react-router-dom-v602-33ni
 | 
					 | 
				
			||||||
  const [changed, setChanged] = React.useState(false);
 | 
					  const [changed, setChanged] = React.useState(false);
 | 
				
			||||||
  const [member, setMember] = React.useState(structuredClone(p.member));
 | 
					  const [member, setMember] = React.useState(structuredClone(p.member));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -198,6 +216,9 @@ export function MemberPage(p: {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div style={{ maxWidth: "2000px", margin: "auto" }}>
 | 
					    <div style={{ maxWidth: "2000px", margin: "auto" }}>
 | 
				
			||||||
 | 
					      <ConfirmLeaveWithoutSaveDialog
 | 
				
			||||||
 | 
					        shouldBlock={changed && p.shouldAllowLeaving !== true}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
      <div
 | 
					      <div
 | 
				
			||||||
        style={{
 | 
					        style={{
 | 
				
			||||||
          display: "flex",
 | 
					          display: "flex",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,8 +12,8 @@ import React from "react";
 | 
				
			|||||||
import { useNavigate } from "react-router-dom";
 | 
					import { useNavigate } from "react-router-dom";
 | 
				
			||||||
import { FamilyApi } from "../../api/FamilyApi";
 | 
					import { FamilyApi } from "../../api/FamilyApi";
 | 
				
			||||||
import { ServerApi } from "../../api/ServerApi";
 | 
					import { ServerApi } from "../../api/ServerApi";
 | 
				
			||||||
import { useAlert } from "../../context_providers/AlertDialogProvider";
 | 
					import { useAlert } from "../../hooks/context_providers/AlertDialogProvider";
 | 
				
			||||||
import { useConfirm } from "../../context_providers/ConfirmDialogProvider";
 | 
					import { useConfirm } from "../../hooks/context_providers/ConfirmDialogProvider";
 | 
				
			||||||
import { useFamily } from "../../widgets/BaseFamilyRoute";
 | 
					import { useFamily } from "../../widgets/BaseFamilyRoute";
 | 
				
			||||||
import { formatDate } from "../../widgets/TimeWidget";
 | 
					import { formatDate } from "../../widgets/TimeWidget";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,8 +12,8 @@ import {
 | 
				
			|||||||
} from "@mui/x-data-grid";
 | 
					} from "@mui/x-data-grid";
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import { FamilyApi, FamilyUser } from "../../api/FamilyApi";
 | 
					import { FamilyApi, FamilyUser } from "../../api/FamilyApi";
 | 
				
			||||||
import { useAlert } from "../../context_providers/AlertDialogProvider";
 | 
					import { useAlert } from "../../hooks/context_providers/AlertDialogProvider";
 | 
				
			||||||
import { useConfirm } from "../../context_providers/ConfirmDialogProvider";
 | 
					import { useConfirm } from "../../hooks/context_providers/ConfirmDialogProvider";
 | 
				
			||||||
import { AsyncWidget } from "../../widgets/AsyncWidget";
 | 
					import { AsyncWidget } from "../../widgets/AsyncWidget";
 | 
				
			||||||
import { useUser } from "../../widgets/BaseAuthenticatedPage";
 | 
					import { useUser } from "../../widgets/BaseAuthenticatedPage";
 | 
				
			||||||
import { useFamily } from "../../widgets/BaseFamilyRoute";
 | 
					import { useFamily } from "../../widgets/BaseFamilyRoute";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,9 +27,9 @@ 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 "../context_providers/SnackbarProvider";
 | 
					import { useSnackbar } from "../hooks/context_providers/SnackbarProvider";
 | 
				
			||||||
import { useConfirm } from "../context_providers/ConfirmDialogProvider";
 | 
					import { useConfirm } from "../hooks/context_providers/ConfirmDialogProvider";
 | 
				
			||||||
import { useAlert } from "../context_providers/AlertDialogProvider";
 | 
					import { useAlert } from "../hooks/context_providers/AlertDialogProvider";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface FamilyContext {
 | 
					interface FamilyContext {
 | 
				
			||||||
  family: Family;
 | 
					  family: Family;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										59
									
								
								geneit_app/src/widgets/ConfirmLeaveWithoutSaveDialog.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								geneit_app/src/widgets/ConfirmLeaveWithoutSaveDialog.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
				
			|||||||
 | 
					// https://github.com/remix-run/react-router/blob/main/examples/navigation-blocking/src/app.tsx
 | 
				
			||||||
 | 
					// https://stackoverflow.com/questions/75135147/react-router-dom-v6-useblocker
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Button,
 | 
				
			||||||
 | 
					  Dialog,
 | 
				
			||||||
 | 
					  DialogActions,
 | 
				
			||||||
 | 
					  DialogContent,
 | 
				
			||||||
 | 
					  DialogContentText,
 | 
				
			||||||
 | 
					  DialogTitle,
 | 
				
			||||||
 | 
					} from "@mui/material";
 | 
				
			||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { unstable_useBlocker as useBlocker } from "react-router-dom";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function ConfirmLeaveWithoutSaveDialog(p: {
 | 
				
			||||||
 | 
					  shouldBlock: boolean;
 | 
				
			||||||
 | 
					}): React.ReactElement {
 | 
				
			||||||
 | 
					  let blocker = useBlocker(p.shouldBlock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  React.useEffect(() => {
 | 
				
			||||||
 | 
					    if (blocker.state === "blocked" && !p.shouldBlock) {
 | 
				
			||||||
 | 
					      blocker.proceed();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, [blocker, p.shouldBlock]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const cancelNavigation = () => {
 | 
				
			||||||
 | 
					    blocker.reset?.();
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const confirmNavigation = () => {
 | 
				
			||||||
 | 
					    blocker.proceed?.();
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Dialog
 | 
				
			||||||
 | 
					      open={blocker.state === "blocked"}
 | 
				
			||||||
 | 
					      onClose={cancelNavigation}
 | 
				
			||||||
 | 
					      aria-labelledby="alert-dialog-title"
 | 
				
			||||||
 | 
					      aria-describedby="alert-dialog-description"
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      <DialogTitle id="alert-dialog-title">
 | 
				
			||||||
 | 
					        Quitter sans enregistrer ?
 | 
				
			||||||
 | 
					      </DialogTitle>
 | 
				
			||||||
 | 
					      <DialogContent>
 | 
				
			||||||
 | 
					        <DialogContentText id="alert-dialog-description">
 | 
				
			||||||
 | 
					          Voulez-vous vraiment quitter cette page sans enregistrer vos
 | 
				
			||||||
 | 
					          modifications ?
 | 
				
			||||||
 | 
					        </DialogContentText>
 | 
				
			||||||
 | 
					      </DialogContent>
 | 
				
			||||||
 | 
					      <DialogActions>
 | 
				
			||||||
 | 
					        <Button onClick={confirmNavigation as any}>Quitter la page</Button>
 | 
				
			||||||
 | 
					        <Button onClick={cancelNavigation as any} autoFocus>
 | 
				
			||||||
 | 
					          Rester sur la page
 | 
				
			||||||
 | 
					        </Button>
 | 
				
			||||||
 | 
					      </DialogActions>
 | 
				
			||||||
 | 
					    </Dialog>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import Brightness7Icon from "@mui/icons-material/Brightness7";
 | 
					import Brightness7Icon from "@mui/icons-material/Brightness7";
 | 
				
			||||||
import DarkModeIcon from "@mui/icons-material/DarkMode";
 | 
					import DarkModeIcon from "@mui/icons-material/DarkMode";
 | 
				
			||||||
import { IconButton, Tooltip } from "@mui/material";
 | 
					import { IconButton, Tooltip } from "@mui/material";
 | 
				
			||||||
import { useDarkTheme } from "../context_providers/DarkThemeProvider";
 | 
					import { useDarkTheme } from "../hooks/context_providers/DarkThemeProvider";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function DarkThemeButton(): React.ReactElement {
 | 
					export function DarkThemeButton(): React.ReactElement {
 | 
				
			||||||
  const darkTheme = useDarkTheme();
 | 
					  const darkTheme = useDarkTheme();
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user