Skip to content

Commit

Permalink
Closes #11. Add new Dialog context and useDialog hook.
Browse files Browse the repository at this point in the history
  • Loading branch information
bngarren committed Mar 16, 2022
1 parent c35f0de commit 2246417
Show file tree
Hide file tree
Showing 7 changed files with 262 additions and 243 deletions.
22 changes: 12 additions & 10 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import theme from "./context/Theme";
import { SettingsProvider } from "./context/Settings";

// Components
import { Header, Footer, PageRouter } from "./components";
import { Header, Footer, PageRouter, DialogProvider } from "./components";

// Routing
import { BrowserRouter } from "react-router-dom";
Expand All @@ -30,15 +30,17 @@ function App() {
<CssBaseline />
<AuthStateProvider Firebase={fb}>
<GridStateProvider Firebase={fb}>
<BrowserRouter>
<Container maxWidth="xl">
<Header />
<main>
<PageRouter />
</main>
</Container>
<Footer />
</BrowserRouter>
<DialogProvider>
<BrowserRouter>
<Container maxWidth="xl">
<Header />
<main>
<PageRouter />
</main>
</Container>
<Footer />
</BrowserRouter>
</DialogProvider>
</GridStateProvider>
</AuthStateProvider>
</ThemeProvider>
Expand Down
150 changes: 150 additions & 0 deletions src/components/Dialog/Dialog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import * as React from "react";

// MUI
import {
Dialog,
DialogTitle,
DialogActions,
DialogContent,
DialogContentText,
Button,
Typography,
} from "@mui/material";

// Util
import { v4 as uuidv4 } from "uuid";

const TRANSITION_DURATION = 50; //ms

const DialogContext = React.createContext();

const DialogProvider = ({ children }) => {
const [, setOpen] = React.useState(false);
const [dialog, setDialog] = React.useState(null);

const close = () => {
setOpen(false);
setDialog(null);
};

/* Shows the dialog component and returns a promise.
This is a curried function. The first partial function accepts the
default template for this type of dialog, and the second partial function
accepts the custom template, i.e. the specific language and variables to use */
const openDialog = React.useCallback(
(defaultTemplate, allowClickAway = false) =>
(customTemplate) => {
const template = { ...defaultTemplate, ...customTemplate };

/* Customize how this dialog responds to a close request,
i.e. "escapeKeyDown" or "backdropClick".
By default, if we allow this kind of close, we will resolve
the dialog to false, akin to the user canceling */
const handleOnClose = (reason, resolve) => {
if (reason === "backdropClick" && !allowClickAway) {
return;
} else {
close();
resolve(false);
}
};

/* We return a promise that resolves to a value equal to
the user's choice */
return new Promise((resolve) => {
const onChoose = (answer) => (e) => {
e.preventDefault();
close();
resolve(answer);
};
setDialog(
<Dialog
sx={{
"&. MuiPaper-root": {
whiteSpace: "pre-line",
},
}}
open={true}
onClose={(_, reason) => handleOnClose(reason, resolve)}
transitionDuration={TRANSITION_DURATION}
>
<DialogTitle>{template.title}</DialogTitle>
<DialogContent>
<DialogContentText
sx={{
display: "flex",
flexDirection: "column",
}}
>
{Array.isArray(template.content) ? (
template.content.map((el) => (
<Typography
variant="body1"
component="span"
key={uuidv4()}
>
{el}
</Typography>
))
) : (
<Typography variant="body1" component="span">
{template.content}
</Typography>
)}
</DialogContentText>
<DialogContentText
variant="body1"
align="center"
sx={{ marginTop: "15px", fontWeight: "bold" }}
>
{template.prompt}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={onChoose(true)}>
{template.labels?.ok ?? "Okay"}
</Button>
<Button onClick={onChoose(false)} autoFocus>
{template.labels?.cancel ?? "Cancel"}
</Button>
</DialogActions>
</Dialog>
);
});
},
[]
);

return (
<DialogContext.Provider
value={{
confirm: openDialog(DEFAULT_CONFIRM, true),
}}
>
{children}
{dialog}
</DialogContext.Provider>
);
};

export default DialogProvider;

export const useDialog = () => {
const context = React.useContext(DialogContext);
if (context === undefined) {
throw new Error(
"useDialog must be consumed within a DialogProvider component."
);
}
return context;
};

const DEFAULT_CONFIRM = {
title: "Please confirm:",
content: [],
prompt: "Do you want to continue?",
labels: {
ok: "Yes",
cancel: "Cancel",
},
};
File renamed without changes.
2 changes: 1 addition & 1 deletion src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ export { default as Header } from "./layout/Header";
export { default as Footer } from "./layout/Footer";
export { default as PageRouter } from "./layout/PageRouter";

export { useDialog } from "./Dialog/useDialog";
export { default as DialogProvider, useDialog } from "./Dialog/Dialog";

export { default as ButtonStandard } from "./Button/ButtonStandard";
36 changes: 19 additions & 17 deletions src/domains/Settings/SecuritySection.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,27 @@
import { Box } from "@mui/material";

// Custom components
import { ButtonStandard } from "../../components";
import { ButtonStandard, useDialog } from "../../components";

const SecuritySection = ({ showYesNoDialog = (f) => f }) => {
const handleClearStorage = () => {
const content = "Delete local data?";
const SecuritySection = () => {
const { confirm } = useDialog();

showYesNoDialog(
content,
() => {
// confirmed
localStorage.clear();
window.location.reload(false);
},
() => {
// chose to cancel
return;
},
{ yes: "Continue", no: "Cancel" }
);
const handleClearStorage = async () => {
let proceed = false;

// Show a confirm dialog before clearing storage
const dialogTemplate = {
title: "Remove browser (local) storage?",
content: `This will clear all data within the app. Make sure you have exported your data.`,
};
const res = await confirm(dialogTemplate);
proceed = res;

if (proceed) {
// confirmed
localStorage.clear();
window.location.reload(false);
}
};

return (
Expand Down
54 changes: 21 additions & 33 deletions src/domains/Settings/SettingsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ const SettingsPage = () => {
/* Get GridData and locationLayout from context */
const { locationLayout, gridData, updateGridData } = useGridStateContext();

/* Hook for our Dialog modal */
const { dialogIsOpen, dialog, showYesNoDialog } = useDialog();
const { confirm } = useDialog();

/**
* Handles the saving of the Settings data by passing
Expand Down Expand Up @@ -80,7 +79,7 @@ const SettingsPage = () => {
* @param {string} newLocationLayout The "new" locationLayout string from user input
* @return {bool} Returns true if save was successful, false if not
*/
const handleSaveLocationLayout = (newLocationLayout) => {
const handleSaveLocationLayout = async (newLocationLayout) => {
/* Convert the input locationLayout string (CSV format) to an array */
const formattedLocationLayout =
getLocationLayoutArrayFromCsv(newLocationLayout);
Expand All @@ -101,36 +100,26 @@ const SettingsPage = () => {
}
});
}
let proceed = true;
// If there are location(s) with data that are missing from new locationLayout
if (risklocations.length > 0) {
// Construct the message for the Dialog
const content = (
<div>
<p>
This new layout will <b>NOT</b> include the following locations
which contain data:
</p>
<p>
{risklocations.map((i) => {
return `| ${i} | `;
})}
</p>
</div>
);
showYesNoDialog(
content,
() => {
// chose to continue
updateGridData(gridData, formattedLocationLayout);
return true;
},
() => {
// chose to cancel
return false;
},
{ yes: "Continue", no: "Cancel" }
);
} else {
proceed = false;
const riskLocationsString = risklocations.map((i) => {
return `| ${i} | `;
});
// Show a confirm dialog before dropping non-empty locations from layout
const dialogTemplate = {
title: "Warning!",
content: [
"You are about to remove the following non-empty locations:",
riskLocationsString,
],
};
const res = await confirm(dialogTemplate);
proceed = res;
}

if (proceed) {
updateGridData(gridData, formattedLocationLayout);
return true;
}
Expand Down Expand Up @@ -183,10 +172,9 @@ const SettingsPage = () => {
<SettingsPageSection
title="Security & Privacy"
subtitle="Manage your browser data."
section={<SecuritySection showYesNoDialog={showYesNoDialog} />}
section={<SecuritySection />}
/>
</StyledBodyStack>
{dialogIsOpen && dialog}
</Container>
);
};
Expand Down
Loading

0 comments on commit 2246417

Please sign in to comment.