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
This commit is contained in:
Generated
+28
@@ -21,6 +21,7 @@
|
|||||||
"formik": "^2.2.8",
|
"formik": "^2.2.8",
|
||||||
"formik-material-ui": "^3.0.1",
|
"formik-material-ui": "^3.0.1",
|
||||||
"material-ui-dropzone": "^3.5.0",
|
"material-ui-dropzone": "^3.5.0",
|
||||||
|
"notistack": "^1.0.9",
|
||||||
"opener": "^1.5.2",
|
"opener": "^1.5.2",
|
||||||
"qrcode.react": "^1.0.1",
|
"qrcode.react": "^1.0.1",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
@@ -12991,6 +12992,24 @@
|
|||||||
"node": ">=4"
|
"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": {
|
"node_modules/npm-run-path": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
|
||||||
@@ -32672,6 +32691,15 @@
|
|||||||
"sort-keys": "^1.0.0"
|
"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": {
|
"npm-run-path": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
"formik": "^2.2.8",
|
"formik": "^2.2.8",
|
||||||
"formik-material-ui": "^3.0.1",
|
"formik-material-ui": "^3.0.1",
|
||||||
"material-ui-dropzone": "^3.5.0",
|
"material-ui-dropzone": "^3.5.0",
|
||||||
|
"notistack": "^1.0.9",
|
||||||
"opener": "^1.5.2",
|
"opener": "^1.5.2",
|
||||||
"qrcode.react": "^1.0.1",
|
"qrcode.react": "^1.0.1",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
|
|||||||
+9
-6
@@ -4,6 +4,7 @@ import './App.css'
|
|||||||
|
|
||||||
import { ThemeProvider } from '@material-ui/styles'
|
import { ThemeProvider } from '@material-ui/styles'
|
||||||
import CssBaseline from '@material-ui/core/CssBaseline'
|
import CssBaseline from '@material-ui/core/CssBaseline'
|
||||||
|
import { SnackbarProvider } from 'notistack'
|
||||||
|
|
||||||
import BaseRouter from './routes/routes'
|
import BaseRouter from './routes/routes'
|
||||||
import { lightTheme, darkTheme } from './theme'
|
import { lightTheme, darkTheme } from './theme'
|
||||||
@@ -35,12 +36,14 @@ const App = (): ReactElement => {
|
|||||||
<div className="App">
|
<div className="App">
|
||||||
<ThemeProvider theme={themeMode === 'light' ? lightTheme : darkTheme}>
|
<ThemeProvider theme={themeMode === 'light' ? lightTheme : darkTheme}>
|
||||||
<StampsProvider>
|
<StampsProvider>
|
||||||
<>
|
<SnackbarProvider>
|
||||||
<CssBaseline />
|
<>
|
||||||
<Router>
|
<CssBaseline />
|
||||||
<BaseRouter />
|
<Router>
|
||||||
</Router>
|
<BaseRouter />
|
||||||
</>
|
</Router>
|
||||||
|
</>
|
||||||
|
</SnackbarProvider>
|
||||||
</StampsProvider>
|
</StampsProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import DialogActions from '@material-ui/core/DialogActions'
|
|||||||
import DialogContent from '@material-ui/core/DialogContent'
|
import DialogContent from '@material-ui/core/DialogContent'
|
||||||
import DialogContentText from '@material-ui/core/DialogContentText'
|
import DialogContentText from '@material-ui/core/DialogContentText'
|
||||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
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'
|
import { beeDebugApi } from '../services/bee'
|
||||||
|
|
||||||
@@ -19,8 +20,7 @@ interface Props {
|
|||||||
export default function DepositModal({ peerId, uncashedAmount }: Props): ReactElement {
|
export default function DepositModal({ peerId, uncashedAmount }: Props): ReactElement {
|
||||||
const [open, setOpen] = useState<boolean>(false)
|
const [open, setOpen] = useState<boolean>(false)
|
||||||
const [loadingCashout, setLoadingCashout] = useState<boolean>(false)
|
const [loadingCashout, setLoadingCashout] = useState<boolean>(false)
|
||||||
const [showToast, setToastVisibility] = useState<boolean>(false)
|
const { enqueueSnackbar } = useSnackbar()
|
||||||
const [toastContent, setToastContent] = useState<JSX.Element | null>(null)
|
|
||||||
|
|
||||||
const handleClickOpen = () => {
|
const handleClickOpen = () => {
|
||||||
setOpen(true)
|
setOpen(true)
|
||||||
@@ -37,37 +37,30 @@ export default function DepositModal({ peerId, uncashedAmount }: Props): ReactEl
|
|||||||
.peerCashout(peerId)
|
.peerCashout(peerId)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
handleToast(
|
enqueueSnackbar(
|
||||||
<span>
|
<span>
|
||||||
Successfully cashed out cheque. Transaction
|
Successfully cashed out cheque. Transaction
|
||||||
<EthereumAddress hideBlockie transaction address={res.transactionHash} network={'goerli'} />
|
<EthereumAddress hideBlockie transaction address={res.transactionHash} network={'goerli'} />
|
||||||
</span>,
|
</span>,
|
||||||
|
{ variant: 'success' },
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((e: Error) => {
|
||||||
// FIXME: handle errors more gracefully
|
enqueueSnackbar(<span>Error: {e.message}</span>, { variant: 'error' })
|
||||||
handleToast(<span>Error with cashout</span>)
|
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoadingCashout(false)
|
setLoadingCashout(false)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
handleToast(<span>Peer Id invalid</span>)
|
enqueueSnackbar(<span>Peer Id invalid</span>, { variant: 'error' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleToast = (text: JSX.Element) => {
|
|
||||||
setToastContent(text)
|
|
||||||
setToastVisibility(true)
|
|
||||||
setTimeout(() => setToastVisibility(false), 7000)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button variant="contained" color="primary" onClick={handleClickOpen} style={{ marginLeft: '7px' }}>
|
<Button variant="contained" color="primary" onClick={handleClickOpen} style={{ marginLeft: '7px' }}>
|
||||||
Cashout
|
Cashout
|
||||||
</Button>
|
</Button>
|
||||||
<Snackbar anchorOrigin={{ vertical: 'top', horizontal: 'center' }} open={showToast} message={toastContent} />
|
|
||||||
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||||
<DialogTitle id="form-dialog-title">Cashout Cheque</DialogTitle>
|
<DialogTitle id="form-dialog-title">Cashout Cheque</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
|||||||
@@ -1,25 +1,21 @@
|
|||||||
import { ReactElement, useState } from 'react'
|
import type { ReactElement } from 'react'
|
||||||
import { IconButton, Snackbar } from '@material-ui/core'
|
import IconButton from '@material-ui/core/IconButton'
|
||||||
import { CopyToClipboard } from 'react-copy-to-clipboard'
|
import { CopyToClipboard } from 'react-copy-to-clipboard'
|
||||||
import { Clipboard } from 'react-feather'
|
import { Clipboard } from 'react-feather'
|
||||||
|
import { useSnackbar } from 'notistack'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
value: string
|
value: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ClipboardCopy(props: Props): ReactElement {
|
export default function ClipboardCopy({ value }: Props): ReactElement {
|
||||||
const [copied, setCopied] = useState(false)
|
const { enqueueSnackbar } = useSnackbar()
|
||||||
|
const handleCopy = () => enqueueSnackbar(`Copied: ${value}`, { variant: 'success' })
|
||||||
const handleCopy = () => {
|
|
||||||
setCopied(true)
|
|
||||||
setTimeout(() => setCopied(false), 3000)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ marginRight: '3px', marginLeft: '3px' }}>
|
<div style={{ marginRight: '3px', marginLeft: '3px' }}>
|
||||||
<Snackbar anchorOrigin={{ vertical: 'top', horizontal: 'center' }} open={copied} message="Copied" />
|
|
||||||
<IconButton color="primary" size="small" onClick={handleCopy}>
|
<IconButton color="primary" size="small" onClick={handleCopy}>
|
||||||
<CopyToClipboard text={props.value}>
|
<CopyToClipboard text={value}>
|
||||||
<Clipboard style={{ height: '20px' }} />
|
<Clipboard style={{ height: '20px' }} />
|
||||||
</CopyToClipboard>
|
</CopyToClipboard>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ import DialogActions from '@material-ui/core/DialogActions'
|
|||||||
import DialogContent from '@material-ui/core/DialogContent'
|
import DialogContent from '@material-ui/core/DialogContent'
|
||||||
import DialogContentText from '@material-ui/core/DialogContentText'
|
import DialogContentText from '@material-ui/core/DialogContentText'
|
||||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
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 { Token } from '../models/Token'
|
||||||
import type { BigNumber } from 'bignumber.js'
|
import type { BigNumber } from 'bignumber.js'
|
||||||
|
import { useSnackbar } from 'notistack'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
successMessage: string
|
successMessage: string
|
||||||
@@ -33,8 +34,7 @@ export default function WithdrawModal({
|
|||||||
const [amount, setAmount] = useState('')
|
const [amount, setAmount] = useState('')
|
||||||
const [amountToken, setAmountToken] = useState<Token | null>(null)
|
const [amountToken, setAmountToken] = useState<Token | null>(null)
|
||||||
const [amountError, setAmountError] = useState<Error | null>(null)
|
const [amountError, setAmountError] = useState<Error | null>(null)
|
||||||
const [showToast, setToastVisibility] = useState(false)
|
const { enqueueSnackbar } = useSnackbar()
|
||||||
const [toastContent, setToastContent] = useState('')
|
|
||||||
|
|
||||||
const handleClickOpen = () => {
|
const handleClickOpen = () => {
|
||||||
setOpen(true)
|
setOpen(true)
|
||||||
@@ -50,18 +50,12 @@ export default function WithdrawModal({
|
|||||||
try {
|
try {
|
||||||
const { transactionHash } = await action(amountToken.toBigInt as bigint)
|
const { transactionHash } = await action(amountToken.toBigInt as bigint)
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
handleToast(`${successMessage} Transaction ${transactionHash}`)
|
enqueueSnackbar(`${successMessage} Transaction ${transactionHash}`, { variant: 'success' })
|
||||||
} catch (e) {
|
} 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<HTMLTextAreaElement | HTMLInputElement>) => {
|
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
|
||||||
const value = e.target.value
|
const value = e.target.value
|
||||||
setAmount(value)
|
setAmount(value)
|
||||||
@@ -83,7 +77,6 @@ export default function WithdrawModal({
|
|||||||
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
|
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
|
||||||
{label}
|
{label}
|
||||||
</Button>
|
</Button>
|
||||||
<Snackbar anchorOrigin={{ vertical: 'top', horizontal: 'center' }} open={showToast} message={toastContent} />
|
|
||||||
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||||
<DialogTitle id="form-dialog-title">{label}</DialogTitle>
|
<DialogTitle id="form-dialog-title">{label}</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ReactElement, useContext, useEffect, useState } from 'react'
|
import { ReactElement, useContext, useEffect, useState } from 'react'
|
||||||
import { beeApi } from '../../services/bee'
|
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 { DropzoneArea } from 'material-ui-dropzone'
|
||||||
import ClipboardCopy from '../../components/ClipboardCopy'
|
import ClipboardCopy from '../../components/ClipboardCopy'
|
||||||
import { PostageBatch } from '@ethersphere/bee-js'
|
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 Avatar from '@material-ui/core/Avatar'
|
||||||
import SelectStamp from './SelectStamp'
|
import SelectStamp from './SelectStamp'
|
||||||
import CreatePostageStamp from '../stamps/CreatePostageStampModal'
|
import CreatePostageStamp from '../stamps/CreatePostageStampModal'
|
||||||
|
import { useSnackbar } from 'notistack'
|
||||||
|
|
||||||
export default function Files(): ReactElement {
|
export default function Files(): ReactElement {
|
||||||
const [file, setFile] = useState<File | null>(null)
|
const [file, setFile] = useState<File | null>(null)
|
||||||
const [uploadReference, setUploadReference] = useState('')
|
const [uploadReference, setUploadReference] = useState('')
|
||||||
const [uploadError, setUploadError] = useState<Error | null>(null)
|
|
||||||
const [isUploadingFile, setIsUploadingFile] = useState(false)
|
const [isUploadingFile, setIsUploadingFile] = useState(false)
|
||||||
|
|
||||||
const [selectedStamp, setSelectedStamp] = useState<PostageBatch | null>(null)
|
const [selectedStamp, setSelectedStamp] = useState<PostageBatch | null>(null)
|
||||||
|
|
||||||
const { isLoading, error, stamps } = useContext(Context)
|
const { isLoading, error, stamps } = useContext(Context)
|
||||||
|
const { enqueueSnackbar } = useSnackbar()
|
||||||
|
|
||||||
// Choose a postage stamp that has the lowest utilization
|
// Choose a postage stamp that has the lowest utilization
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -38,14 +39,13 @@ export default function Files(): ReactElement {
|
|||||||
const uploadFile = () => {
|
const uploadFile = () => {
|
||||||
if (file === null || selectedStamp === null) return
|
if (file === null || selectedStamp === null) return
|
||||||
setIsUploadingFile(true)
|
setIsUploadingFile(true)
|
||||||
setUploadError(null)
|
|
||||||
beeApi.files
|
beeApi.files
|
||||||
.uploadFile(selectedStamp.batchID, file)
|
.uploadFile(selectedStamp.batchID, file)
|
||||||
.then(hash => {
|
.then(hash => {
|
||||||
setUploadReference(hash)
|
setUploadReference(hash)
|
||||||
setFile(null)
|
setFile(null)
|
||||||
})
|
})
|
||||||
.catch(setUploadError) // FIXME: should instead trigger notification
|
.catch(e => enqueueSnackbar(`Error uploading: ${e.message}`, { variant: 'error' }))
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setIsUploadingFile(false)
|
setIsUploadingFile(false)
|
||||||
})
|
})
|
||||||
@@ -93,7 +93,6 @@ export default function Files(): ReactElement {
|
|||||||
<ClipboardCopy value={uploadReference} />
|
<ClipboardCopy value={uploadReference} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{uploadError && <FormHelperText error>{uploadError.message}</FormHelperText>}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { TextField } from 'formik-material-ui'
|
|||||||
import { beeApi } from '../../services/bee'
|
import { beeApi } from '../../services/bee'
|
||||||
import { Context } from '../../providers/Stamps'
|
import { Context } from '../../providers/Stamps'
|
||||||
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
|
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
|
||||||
|
import { useSnackbar } from 'notistack'
|
||||||
|
|
||||||
interface FormValues {
|
interface FormValues {
|
||||||
depth?: string
|
depth?: string
|
||||||
@@ -55,6 +56,7 @@ export default function FormDialog({ label }: Props): ReactElement {
|
|||||||
const { refresh } = useContext(Context)
|
const { refresh } = useContext(Context)
|
||||||
const handleClickOpen = () => setOpen(true)
|
const handleClickOpen = () => setOpen(true)
|
||||||
const handleClose = () => setOpen(false)
|
const handleClose = () => setOpen(false)
|
||||||
|
const { enqueueSnackbar } = useSnackbar()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Formik
|
<Formik
|
||||||
@@ -71,8 +73,7 @@ export default function FormDialog({ label }: Props): ReactElement {
|
|||||||
await refresh()
|
await refresh()
|
||||||
handleClose()
|
handleClose()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO: trigger notification with notistack
|
enqueueSnackbar(`Error: ${e.message}`, { variant: 'error' })
|
||||||
console.error(`${e.message}`) // eslint-disable-line
|
|
||||||
actions.setSubmitting(false)
|
actions.setSubmitting(false)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
Reference in New Issue
Block a user