Skip to content

Commit

Permalink
refactor: add useToast hook for toast notifications (#3593)
Browse files Browse the repository at this point in the history
  • Loading branch information
jamdelion authored Sep 2, 2024
1 parent f242545 commit bc75023
Show file tree
Hide file tree
Showing 16 changed files with 243 additions and 248 deletions.
28 changes: 28 additions & 0 deletions editor.planx.uk/src/components/Toast/Toast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Alert from "@mui/material/Alert";
import Snackbar from "@mui/material/Snackbar";
import { useToast } from "hooks/useToast";
import React from "react";

import { Toast as ToastProps } from "./types";

const Toast = ({ message, type = "success", id }: ToastProps) => {
const toast = useToast();

const handleCloseToast = () => {
if (toast) {
toast.remove(id);
} else {
console.warn("ToastContext is not provided.");
}
};

return (
<Snackbar onClose={handleCloseToast} autoHideDuration={6000} open={true}>
<Alert onClose={handleCloseToast} severity={type} sx={{ width: "100%" }}>
{message}
</Alert>
</Snackbar>
);
};

export default Toast;
16 changes: 16 additions & 0 deletions editor.planx.uk/src/components/Toast/ToastContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from "react";

import Toast from "./Toast";
import { Toast as ToastComponent } from "./types";

const ToastContainer = ({ toasts }: { toasts: ToastComponent[] }) => {
return (
<div className="toasts-container">
{toasts.map((toast) => (
<Toast key={toast.id} {...toast} />
))}
</div>
);
};

export default ToastContainer;
31 changes: 31 additions & 0 deletions editor.planx.uk/src/components/Toast/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export interface Toast {
message: string;
type: ToastType;
id: number;
}
export type ToastType = "success" | "warning" | "info" | "error";

export type ToastState = {
toasts: Toast[];
};

export type ToastAction = AddToast | DeleteToast;

type AddToast = {
type: "ADD_TOAST";
payload: Toast;
};

type DeleteToast = {
type: "DELETE_TOAST";
payload: { id: number };
};

export type ToastContextType = {
addToast: (type: ToastType, message: string) => void;
remove: (id: number) => void;
success: (message: string) => void;
warning: (message: string) => void;
info: (message: string) => void;
error: (message: string) => void;
};
70 changes: 70 additions & 0 deletions editor.planx.uk/src/contexts/ToastContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/* eslint-disable @typescript-eslint/no-empty-function */
import ToastContainer from "components/Toast/ToastContainer";
import {
ToastContextType,
ToastState,
ToastType,
} from "components/Toast/types";
import React, { createContext, ReactNode, useReducer } from "react";
import { toastReducer } from "reducers/toastReducer";

const defaultCreateContextValue = {
remove: (_id: number) => {},
addToast: (_type: ToastType, _message: string) => {},
success: (_message: string) => {},
warning: (_message: string) => {},
info: (_message: string) => {},
error: (_message: string) => {},
};

export const ToastContext = createContext<ToastContextType>(
defaultCreateContextValue,
);

const initialState: ToastState = {
toasts: [],
};

export const ToastContextProvider = ({
children,
}: Readonly<{ children: ReactNode }>) => {
const [state, dispatch] = useReducer(toastReducer, initialState);
const addToast = (type: ToastType, message: string) => {
const id = Math.floor(Math.random() * 10_000_000);
dispatch({ type: "ADD_TOAST", payload: { id, message, type } });
};
const remove = (id: number) => {
dispatch({ type: "DELETE_TOAST", payload: { id } });
};
const success = (message: string) => {
addToast("success", message);
};

const warning = (message: string) => {
addToast("warning", message);
};

const info = (message: string) => {
addToast("info", message);
};

const error = (message: string) => {
addToast("error", message);
};

const value: ToastContextType = {
success,
warning,
info,
error,
remove,
addToast,
};

return (
<ToastContext.Provider value={value}>
<ToastContainer toasts={state.toasts} />
{children}
</ToastContext.Provider>
);
};
5 changes: 5 additions & 0 deletions editor.planx.uk/src/hooks/useToast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { useContext } from "react";

import { ToastContext } from "../contexts/ToastContext";

export const useToast = () => useContext(ToastContext);
5 changes: 3 additions & 2 deletions editor.planx.uk/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ApolloProvider } from "@apollo/client";
import CssBaseline from "@mui/material/CssBaseline";
import { StyledEngineProvider, ThemeProvider } from "@mui/material/styles";
import { MyMap } from "@opensystemslab/map";
import { ToastContextProvider } from "contexts/ToastContext";
import { getCookie, setCookie } from "lib/cookie";
import ErrorPage from "pages/ErrorPage";
import { AnalyticsProvider } from "pages/FlowEditor/lib/analytics/provider";
Expand Down Expand Up @@ -93,7 +94,7 @@ const Layout: React.FC<{
};

root.render(
<>
<ToastContextProvider>
<ApolloProvider client={client}>
<AnalyticsProvider>
<Router context={{ currentUser: hasJWT() }} navigation={navigation}>
Expand All @@ -109,5 +110,5 @@ root.render(
</AnalyticsProvider>
</ApolloProvider>
<ToastContainer icon={false} theme="colored" />
</>,
</ToastContextProvider>,
);
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import Alert from "@mui/material/Alert";
import Box from "@mui/material/Box";
import Container from "@mui/material/Container";
import Snackbar from "@mui/material/Snackbar";
import { styled } from "@mui/material/styles";
import Typography from "@mui/material/Typography";
import { TeamTheme } from "@opensystemslab/planx-core/types";
import { FormikConfig } from "formik";
import { useToast } from "hooks/useToast";
import { useStore } from "pages/FlowEditor/lib/store";
import React, { useEffect, useState } from "react";
import SettingsSection from "ui/editor/SettingsSection";
Expand Down Expand Up @@ -34,7 +33,7 @@ const DesignSettings: React.FC = () => {
const [formikConfig, setFormikConfig] = useState<
FormikConfig<TeamTheme> | undefined
>(undefined);

const toast = useToast();
/**
* Fetch current team and setup shared form config
*/
Expand All @@ -60,20 +59,7 @@ const DesignSettings: React.FC = () => {
fetchTeam();
}, []);

const [open, setOpen] = useState(false);

const handleClose = (
_event?: React.SyntheticEvent | Event,
reason?: string,
) => {
if (reason === "clickaway") {
return;
}

setOpen(false);
};

const onSuccess = () => setOpen(true);
const onSuccess = () => toast.success("Theme updated successfully");

return (
<Container maxWidth="formWrap">
Expand All @@ -93,11 +79,6 @@ const DesignSettings: React.FC = () => {
<FaviconForm formikConfig={formikConfig} onSuccess={onSuccess} />
</>
)}
<Snackbar open={open} autoHideDuration={6000} onClose={handleClose}>
<Alert onClose={handleClose} severity="success" sx={{ width: "100%" }}>
Theme updated successfully
</Alert>
</Snackbar>
</Container>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import Alert from "@mui/material/Alert";
import Container from "@mui/material/Container";
import Snackbar from "@mui/material/Snackbar";
import Typography from "@mui/material/Typography";
import { TeamSettings } from "@opensystemslab/planx-core/types";
import { FormikConfig } from "formik";
import { useToast } from "hooks/useToast";
import { useStore } from "pages/FlowEditor/lib/store";
import React, { useEffect, useState } from "react";
import SettingsSection from "ui/editor/SettingsSection";
Expand All @@ -22,6 +21,7 @@ const GeneralSettings: React.FC = () => {
const [formikConfig, setFormikConfig] = useState<
FormikConfig<TeamSettings> | undefined
>(undefined);
const toast = useToast();

useEffect(() => {
const fetchTeam = async () => {
Expand All @@ -44,21 +44,7 @@ const GeneralSettings: React.FC = () => {
fetchTeam();
}, []);

const [open, setOpen] = useState(false);
const [updateMessage, _setUpdateMessage] = useState("Setting Updated");

const handleClose = (
_event?: React.SyntheticEvent | Event,
reason?: string,
) => {
if (reason === "clickaway") {
return;
}

setOpen(false);
};

const onSuccess = () => setOpen(true);
const onSuccess = () => toast.success("Setting Updated");

return (
<Container maxWidth="formWrap">
Expand All @@ -81,11 +67,6 @@ const GeneralSettings: React.FC = () => {
<SubmissionsForm formikConfig={formikConfig} onSuccess={onSuccess} />
</>
)}
<Snackbar open={open} autoHideDuration={6000} onClose={handleClose}>
<Alert onClose={handleClose} severity="success" sx={{ width: "100%" }}>
{updateMessage}
</Alert>
</Snackbar>
</Container>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
import Alert from "@mui/material/Alert";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Container from "@mui/material/Container";
import FormControlLabel, {
formControlLabelClasses,
} from "@mui/material/FormControlLabel";
import Link from "@mui/material/Link";
import Snackbar from "@mui/material/Snackbar";
import Switch, { SwitchProps } from "@mui/material/Switch";
import Tooltip from "@mui/material/Tooltip";
import Typography from "@mui/material/Typography";
import { FlowStatus } from "@opensystemslab/planx-core/types";
import axios from "axios";
import { useFormik } from "formik";
import { useToast } from "hooks/useToast";
import React, { useState } from "react";
import { rootFlowPath } from "routes/utils";
import { FONT_WEIGHT_BOLD } from "theme";
Expand Down Expand Up @@ -228,19 +227,7 @@ const ServiceSettings: React.FC = () => {
state.teamDomain,
state.isFlowPublished,
]);

const [isAlertOpen, setIsAlertOpen] = useState(false);

const handleClose = (
_event?: React.SyntheticEvent | Event,
reason?: string,
) => {
if (reason === "clickaway") {
return;
}

setIsAlertOpen(false);
};
const toast = useToast();

const sendFlowStatusSlackNotification = async (status: FlowStatus) => {
const skipTeamSlugs = [
Expand Down Expand Up @@ -294,7 +281,7 @@ const ServiceSettings: React.FC = () => {
},
onSubmit: async (values) => {
await updateFlowSettings(values);
setIsAlertOpen(true);
toast.success("Service settings updated successfully");
},
validate: () => {},
});
Expand All @@ -306,7 +293,7 @@ const ServiceSettings: React.FC = () => {
onSubmit: async (values, { resetForm }) => {
const isSuccess = await updateFlowStatus(values.status);
if (isSuccess) {
setIsAlertOpen(true);
toast.success("Service settings updated successfully");
// Send a Slack notification to #planx-notifications
sendFlowStatusSlackNotification(values.status);
// Reset "dirty" status to disable Save & Reset buttons
Expand Down Expand Up @@ -487,15 +474,6 @@ const ServiceSettings: React.FC = () => {
</Box>
</SettingsSection>
</Box>
<Snackbar
open={isAlertOpen}
autoHideDuration={6000}
onClose={handleClose}
>
<Alert onClose={handleClose} severity="success" sx={{ width: "100%" }}>
Service settings updated successfully
</Alert>
</Snackbar>
</Container>
);
};
Expand Down
Loading

0 comments on commit bc75023

Please sign in to comment.