From bec84051a9582bf62a23f2080a6587a9f458b969 Mon Sep 17 00:00:00 2001 From: Vojtech Simetka Date: Wed, 2 Jun 2021 13:36:39 +0200 Subject: [PATCH] feat: unified notification with notistack (#127) * feat: unified existing notification with notistack * chore: replaced with useSnackbar, added missing notifications * chore: removed FIXME as per PR review --- package-lock.json | 28 ++++++++++++++++++++ package.json | 1 + src/App.tsx | 15 ++++++----- src/components/CashoutModal.tsx | 23 ++++++---------- src/components/ClipboardCopy.tsx | 18 +++++-------- src/components/WDModal.tsx | 17 ++++-------- src/pages/files/Upload.tsx | 9 +++---- src/pages/stamps/CreatePostageStampModal.tsx | 5 ++-- 8 files changed, 65 insertions(+), 51 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8b6e44b..86ecc0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "formik": "^2.2.8", "formik-material-ui": "^3.0.1", "material-ui-dropzone": "^3.5.0", + "notistack": "^1.0.9", "opener": "^1.5.2", "qrcode.react": "^1.0.1", "react": "^17.0.2", @@ -12991,6 +12992,24 @@ "node": ">=4" } }, + "node_modules/notistack": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/notistack/-/notistack-1.0.9.tgz", + "integrity": "sha512-Dal2HtTpWrdYCZ3t0HhJt47NJZwVSPee36WzORRbqUkFR0k9pxFszxBuPSWshBLwF6Av8s86XPP+ED5zRz0CGw==", + "dependencies": { + "clsx": "^1.1.0", + "hoist-non-react-statics": "^3.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/notistack" + }, + "peerDependencies": { + "@material-ui/core": "^4.0.0", + "react": "^16.8.0 || ^17.0.0", + "react-dom": "^16.8.0 || ^17.0.0" + } + }, "node_modules/npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -32672,6 +32691,15 @@ "sort-keys": "^1.0.0" } }, + "notistack": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/notistack/-/notistack-1.0.9.tgz", + "integrity": "sha512-Dal2HtTpWrdYCZ3t0HhJt47NJZwVSPee36WzORRbqUkFR0k9pxFszxBuPSWshBLwF6Av8s86XPP+ED5zRz0CGw==", + "requires": { + "clsx": "^1.1.0", + "hoist-non-react-statics": "^3.3.0" + } + }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", diff --git a/package.json b/package.json index 6d72d3c..a8aa331 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "formik": "^2.2.8", "formik-material-ui": "^3.0.1", "material-ui-dropzone": "^3.5.0", + "notistack": "^1.0.9", "opener": "^1.5.2", "qrcode.react": "^1.0.1", "react": "^17.0.2", diff --git a/src/App.tsx b/src/App.tsx index a047f2c..1f7fe87 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,6 +4,7 @@ import './App.css' import { ThemeProvider } from '@material-ui/styles' import CssBaseline from '@material-ui/core/CssBaseline' +import { SnackbarProvider } from 'notistack' import BaseRouter from './routes/routes' import { lightTheme, darkTheme } from './theme' @@ -35,12 +36,14 @@ const App = (): ReactElement => {
- <> - - - - - + + <> + + + + + +
diff --git a/src/components/CashoutModal.tsx b/src/components/CashoutModal.tsx index f355a6c..1e6d160 100644 --- a/src/components/CashoutModal.tsx +++ b/src/components/CashoutModal.tsx @@ -5,7 +5,8 @@ import DialogActions from '@material-ui/core/DialogActions' import DialogContent from '@material-ui/core/DialogContent' import DialogContentText from '@material-ui/core/DialogContentText' import DialogTitle from '@material-ui/core/DialogTitle' -import { Snackbar, Container, CircularProgress } from '@material-ui/core' +import { Container, CircularProgress } from '@material-ui/core' +import { useSnackbar } from 'notistack' import { beeDebugApi } from '../services/bee' @@ -19,8 +20,7 @@ interface Props { export default function DepositModal({ peerId, uncashedAmount }: Props): ReactElement { const [open, setOpen] = useState(false) const [loadingCashout, setLoadingCashout] = useState(false) - const [showToast, setToastVisibility] = useState(false) - const [toastContent, setToastContent] = useState(null) + const { enqueueSnackbar } = useSnackbar() const handleClickOpen = () => { setOpen(true) @@ -37,37 +37,30 @@ export default function DepositModal({ peerId, uncashedAmount }: Props): ReactEl .peerCashout(peerId) .then(res => { setOpen(false) - handleToast( + enqueueSnackbar( Successfully cashed out cheque. Transaction , + { variant: 'success' }, ) }) - .catch(() => { - // FIXME: handle errors more gracefully - handleToast(Error with cashout) + .catch((e: Error) => { + enqueueSnackbar(Error: {e.message}, { variant: 'error' }) }) .finally(() => { setLoadingCashout(false) }) } else { - handleToast(Peer Id invalid) + enqueueSnackbar(Peer Id invalid, { variant: 'error' }) } } - const handleToast = (text: JSX.Element) => { - setToastContent(text) - setToastVisibility(true) - setTimeout(() => setToastVisibility(false), 7000) - } - return (
- Cashout Cheque diff --git a/src/components/ClipboardCopy.tsx b/src/components/ClipboardCopy.tsx index 6309e4c..7ad40a2 100644 --- a/src/components/ClipboardCopy.tsx +++ b/src/components/ClipboardCopy.tsx @@ -1,25 +1,21 @@ -import { ReactElement, useState } from 'react' -import { IconButton, Snackbar } from '@material-ui/core' +import type { ReactElement } from 'react' +import IconButton from '@material-ui/core/IconButton' import { CopyToClipboard } from 'react-copy-to-clipboard' import { Clipboard } from 'react-feather' +import { useSnackbar } from 'notistack' interface Props { value: string } -export default function ClipboardCopy(props: Props): ReactElement { - const [copied, setCopied] = useState(false) - - const handleCopy = () => { - setCopied(true) - setTimeout(() => setCopied(false), 3000) - } +export default function ClipboardCopy({ value }: Props): ReactElement { + const { enqueueSnackbar } = useSnackbar() + const handleCopy = () => enqueueSnackbar(`Copied: ${value}`, { variant: 'success' }) return (
- - + diff --git a/src/components/WDModal.tsx b/src/components/WDModal.tsx index 50d7d64..e680534 100644 --- a/src/components/WDModal.tsx +++ b/src/components/WDModal.tsx @@ -6,9 +6,10 @@ import DialogActions from '@material-ui/core/DialogActions' import DialogContent from '@material-ui/core/DialogContent' import DialogContentText from '@material-ui/core/DialogContentText' import DialogTitle from '@material-ui/core/DialogTitle' -import { FormHelperText, Snackbar } from '@material-ui/core' +import FormHelperText from '@material-ui/core/FormHelperText' import { Token } from '../models/Token' import type { BigNumber } from 'bignumber.js' +import { useSnackbar } from 'notistack' interface Props { successMessage: string @@ -33,8 +34,7 @@ export default function WithdrawModal({ const [amount, setAmount] = useState('') const [amountToken, setAmountToken] = useState(null) const [amountError, setAmountError] = useState(null) - const [showToast, setToastVisibility] = useState(false) - const [toastContent, setToastContent] = useState('') + const { enqueueSnackbar } = useSnackbar() const handleClickOpen = () => { setOpen(true) @@ -50,18 +50,12 @@ export default function WithdrawModal({ try { const { transactionHash } = await action(amountToken.toBigInt as bigint) setOpen(false) - handleToast(`${successMessage} Transaction ${transactionHash}`) + enqueueSnackbar(`${successMessage} Transaction ${transactionHash}`, { variant: 'success' }) } catch (e) { - handleToast(`${errorMessage} Error: ${e.message}`) + enqueueSnackbar(`${errorMessage} Error: ${e.message}`, { variant: 'error' }) } } - const handleToast = (text: string) => { - setToastContent(text) - setToastVisibility(true) - setTimeout(() => setToastVisibility(false), 7000) - } - const handleChange = (e: React.ChangeEvent) => { const value = e.target.value setAmount(value) @@ -83,7 +77,6 @@ export default function WithdrawModal({ - {label} diff --git a/src/pages/files/Upload.tsx b/src/pages/files/Upload.tsx index 4349197..2355ac6 100644 --- a/src/pages/files/Upload.tsx +++ b/src/pages/files/Upload.tsx @@ -1,7 +1,7 @@ import { ReactElement, useContext, useEffect, useState } from 'react' import { beeApi } from '../../services/bee' -import { Button, Container, CircularProgress, FormHelperText } from '@material-ui/core' +import { Button, Container, CircularProgress } from '@material-ui/core' import { DropzoneArea } from 'material-ui-dropzone' import ClipboardCopy from '../../components/ClipboardCopy' import { PostageBatch } from '@ethersphere/bee-js' @@ -11,16 +11,17 @@ import Chip from '@material-ui/core/Chip' import Avatar from '@material-ui/core/Avatar' import SelectStamp from './SelectStamp' import CreatePostageStamp from '../stamps/CreatePostageStampModal' +import { useSnackbar } from 'notistack' export default function Files(): ReactElement { const [file, setFile] = useState(null) const [uploadReference, setUploadReference] = useState('') - const [uploadError, setUploadError] = useState(null) const [isUploadingFile, setIsUploadingFile] = useState(false) const [selectedStamp, setSelectedStamp] = useState(null) const { isLoading, error, stamps } = useContext(Context) + const { enqueueSnackbar } = useSnackbar() // Choose a postage stamp that has the lowest utilization useEffect(() => { @@ -38,14 +39,13 @@ export default function Files(): ReactElement { const uploadFile = () => { if (file === null || selectedStamp === null) return setIsUploadingFile(true) - setUploadError(null) beeApi.files .uploadFile(selectedStamp.batchID, file) .then(hash => { setUploadReference(hash) setFile(null) }) - .catch(setUploadError) // FIXME: should instead trigger notification + .catch(e => enqueueSnackbar(`Error uploading: ${e.message}`, { variant: 'error' })) .finally(() => { setIsUploadingFile(false) }) @@ -93,7 +93,6 @@ export default function Files(): ReactElement {
)} - {uploadError && {uploadError.message}}
diff --git a/src/pages/stamps/CreatePostageStampModal.tsx b/src/pages/stamps/CreatePostageStampModal.tsx index 77ddc52..6a9084f 100644 --- a/src/pages/stamps/CreatePostageStampModal.tsx +++ b/src/pages/stamps/CreatePostageStampModal.tsx @@ -12,6 +12,7 @@ import { TextField } from 'formik-material-ui' import { beeApi } from '../../services/bee' import { Context } from '../../providers/Stamps' import { makeStyles, Theme, createStyles } from '@material-ui/core/styles' +import { useSnackbar } from 'notistack' interface FormValues { depth?: string @@ -55,6 +56,7 @@ export default function FormDialog({ label }: Props): ReactElement { const { refresh } = useContext(Context) const handleClickOpen = () => setOpen(true) const handleClose = () => setOpen(false) + const { enqueueSnackbar } = useSnackbar() return (