Skip to content

Commit

Permalink
Merge pull request #653 from NBISweden/dev/confirmation-dialogue
Browse files Browse the repository at this point in the history
Confirmation Dialog
  • Loading branch information
jonandernovella authored Sep 15, 2022
2 parents bb55dfa + 791875f commit b1ea27b
Show file tree
Hide file tree
Showing 5 changed files with 255 additions and 50 deletions.
75 changes: 39 additions & 36 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { AbsencePlanner } from "./pages/AbsencePlanner";
import { AuthProvider } from "./components/AuthProvider";
import { ProtectedRoute } from "./components/ProtectedRoute";
import { getISOWeek } from "date-fns";
import { ConfirmDialogProvider } from "./components/ConfirmDialogProvider";

// Route calls
// Order of routes is critical.
Expand All @@ -22,42 +23,44 @@ export const App = () => {
<React.StrictMode>
<BrowserRouter>
<AuthProvider>
<Routes>
<Route path="/login" element={<Login />} />
<Route
path="/report/:year/:week"
element={
<ProtectedRoute>
<Report />
</ProtectedRoute>
}
/>
<Route
path="/*"
element={
<Navigate
replace
to={`/report/${currentYear}/${currentWeek}`}
/>
}
/>
<Route
path="/help"
element={
<ProtectedRoute>
<Help />
</ProtectedRoute>
}
/>
<Route
path="/absence"
element={
<ProtectedRoute>
<AbsencePlanner />
</ProtectedRoute>
}
/>
</Routes>
<ConfirmDialogProvider>
<Routes>
<Route path="/login" element={<Login />} />
<Route
path="/report/:year/:week"
element={
<ProtectedRoute>
<Report />
</ProtectedRoute>
}
/>
<Route
path="/*"
element={
<Navigate
replace
to={`/report/${currentYear}/${currentWeek}`}
/>
}
/>
<Route
path="/help"
element={
<ProtectedRoute>
<Help />
</ProtectedRoute>
}
/>
<Route
path="/absence"
element={
<ProtectedRoute>
<AbsencePlanner />
</ProtectedRoute>
}
/>
</Routes>
</ConfirmDialogProvider>
</AuthProvider>
</BrowserRouter>
</React.StrictMode>
Expand Down
43 changes: 43 additions & 0 deletions frontend/src/components/ConfirmDialogProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React, {
createContext,
useState,
useRef,
useCallback,
useContext,
} from "react";
import { ModalDialog } from "./ModalDialog";

const ConfirmDialog = createContext(null);

export const ConfirmDialogProvider = ({ children }) => {
const [state, setState] = useState<{}>({ isOpen: false });
const handlerFunction = useRef<(choice: boolean) => void>();

const confirm = useCallback(
(forwarded) => {
return new Promise((resolve) => {
setState({ ...forwarded, isOpen: true });
handlerFunction.current = (choice: boolean) => {
resolve(choice);
setState({ isOpen: false });
};
});
},
[setState]
);

return (
<ConfirmDialog.Provider value={confirm}>
{children}
<ModalDialog
{...state}
onCancel={() => handlerFunction.current(false)}
onConfirm={() => handlerFunction.current(true)}
/>
</ConfirmDialog.Provider>
);
};

export const useConfirm = () => {
return useContext(ConfirmDialog);
};
76 changes: 76 additions & 0 deletions frontend/src/components/ModalDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React, { useEffect, useRef } from "react";
import { useEscaper } from "../utils";

/*
The modal component can be filled with a simple string using the content prop.
In that case, the component should be rendered without children.
To fill it with more complex JSX, it can be rendered with children.
If children are present, the content prop will be ignored.
*/

export const ModalDialog = ({
isOpen,
title,
content,
confirmButtonLabel,
onCancel,
onConfirm,
children,
}: {
isOpen: boolean;
title: string;
content?: string;
confirmButtonLabel: string;
onCancel: () => void;
onConfirm: () => void;
children: JSX.Element;
}) => {
// Autofocus on cancel button when opening dialog
const cancelButton = useRef(null);
useEffect(() => {
cancelButton.current.focus();
});

// Close the dialog when pressing escape
const modal = useRef(null);
useEscaper(modal, () => onCancel());

return (
<section
className="modal-overlay"
style={{ display: `${isOpen ? "block" : "none"}` }}
ref={modal}
>
<div
className="modal-wrapper"
role="alertdialog"
aria-modal={true}
aria-labelledby="modalTitle"
aria-describedby="modalContent"
>
<section className="modal-title-wrapper">
<h2 id="modalTitle">{title}</h2>
</section>
<section className="modal-content-wrapper">
{!children && <p id="modalContent">{content}</p>}
{children}
</section>
<section className="modal-buttons-wrapper">
<button
ref={cancelButton}
onClick={onCancel}
className="basic-button modal-cancel-button"
>
Cancel
</button>
<button
onClick={onConfirm}
className="basic-button modal-confirm-button"
>
{confirmButtonLabel}
</button>
</section>
</div>
</section>
);
};
70 changes: 70 additions & 0 deletions frontend/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -936,3 +936,73 @@ tr:last-of-type td {
margin-top: 2rem;
margin-bottom: 3rem;
}

/* ModalDialog styles */

.modal-overlay {
width: 100%;
height: 100vh;
position: absolute;
left: 0;
top: -0.2rem;
background-color: hsla(0, 0%, 0%, 0.5);
z-index: 1;
}

.modal-wrapper {
background-color: white;
color: hsl(185deg 92% 11%);
height: fit-content;
width: fit-content;
max-width: 90%;
margin: 0 auto;
position: absolute;
top: 25%;
left: 0;
right: 0;
border-radius: 4px;
}

.modal-title-wrapper {
background-color: hsl(70deg 55% 98%);
border-bottom: 1px solid hsl(76deg 55% 77%);
padding: 1.5rem 1.5rem 0.2rem;
border-radius: 4px 4px 0 0;
}

.modal-content-wrapper {
padding: 1rem 1.5rem;
}

.modal-content-wrapper p {
margin: 0;
}

.modal-buttons-wrapper {
display: flex;
justify-content: flex-end;
padding: 1rem;
border-radius: 0 0 4px 4px;
}

.modal-confirm-button {
background-color: hsl(185deg 92% 16%);
padding: 0.2rem 1rem;
margin: 0.25rem;
}

.modal-confirm-button:hover {
background-color: hsl(185deg 92% 11%);
}

.modal-cancel-button {
border: 2px solid hsl(187deg 29% 94%);
background-color: white;
color: hsl(185deg 92% 11%);
padding: 0.2rem 0.5rem;
margin: 0.25rem;
}

.modal-cancel-button:hover {
background-color: hsl(187deg 29% 94%);
}
41 changes: 27 additions & 14 deletions frontend/src/pages/AbsencePlanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { HeaderUser } from "../components/HeaderUser";
import { Chart } from "react-google-charts";
import trash from "../icons/trash.svg";
import pencil from "../icons/pencil.svg";
import { FetchedTimeEntry } from "../model";
import { useConfirm } from "../components/ConfirmDialogProvider";

export const AbsencePlanner = () => {
const [startDate, setStartDate] = useState<Date>(undefined);
Expand All @@ -47,6 +47,7 @@ export const AbsencePlanner = () => {
>([]);
const [reloadPage, setReloadPage] = useState<boolean>(false);
const [reportedDates, setReportedDates] = useState<string[]>([]);
const confirm: ({}) => any = useConfirm();

let today = new Date();
const absenceFrom: Date = new Date(new Date().setMonth(today.getMonth() - 1));
Expand Down Expand Up @@ -213,20 +214,32 @@ export const AbsencePlanner = () => {
};

const onRemoveEntriesButton = async (entryIds: number[]) => {
toggleLoadingPage(true);
const removed: boolean = await removeTimeEntries(entryIds);
if (removed) {
setToastList([
...toastList,
{
type: "info",
timeout: 8000,
message: "Absence period was successfully removed",
},
]);
// Open the dialog first, using the confirm function
const isConfirmed = await confirm({
title: "Deleting absence period",
content: "Do you really want to delete the whole absence period?",
confirmButtonLabel: "Yes",
});
// The confirm function will return a boolean indicating if the user aborts (false) or confirms (true)
if (!isConfirmed) {
return;
} else {
toggleLoadingPage(true);
const removed: boolean = await removeTimeEntries(entryIds);
if (removed) {
setToastList([
...toastList,
{
type: "info",
timeout: 8000,
message: "Absence period was successfully removed",
},
]);
}
toggleLoadingPage(false);
setReloadPage(!reloadPage);
return;
}
toggleLoadingPage(false);
setReloadPage(!reloadPage);
};

const getAbsenceRanges = (entries: FetchedTimeEntry[]) => {
Expand Down

0 comments on commit b1ea27b

Please sign in to comment.