From 65e3db1a38e7cd6053978272821e4494b64ec30d Mon Sep 17 00:00:00 2001 From: Ayush Tomar <41674634+ayushtom@users.noreply.github.com> Date: Tue, 8 Aug 2023 00:52:14 +0530 Subject: [PATCH] feat: add error boundary and snackbar store (#15) * feat: add error boundary and snackbar store * chore: add placeholder for logging service * chore: make toast styles constant * chore: shift toast options to global level --- src/renderer/components/ErrorBoundary.tsx | 39 +++++++++++++++++++++++ src/renderer/index.tsx | 20 ++++++++---- src/renderer/pages/Apps.tsx | 7 ++-- src/renderer/pages/Navigation.tsx | 12 +++++-- src/renderer/store/snackbar.ts | 39 +++++++++++++++++++++++ src/renderer/store/store.ts | 2 ++ src/shared/constants.ts | 12 +++++++ 7 files changed, 116 insertions(+), 15 deletions(-) create mode 100644 src/renderer/components/ErrorBoundary.tsx create mode 100644 src/renderer/store/snackbar.ts create mode 100644 src/shared/constants.ts diff --git a/src/renderer/components/ErrorBoundary.tsx b/src/renderer/components/ErrorBoundary.tsx new file mode 100644 index 0000000..705afae --- /dev/null +++ b/src/renderer/components/ErrorBoundary.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { toast } from 'react-toastify'; +import defaultToastStyleOptions from 'shared/constants'; + +interface Props { + children: React.ReactNode; +} + +interface State { + error: boolean | Error; +} + +class ErrorBoundary extends React.Component { + constructor(props: Props) { + super(props); + this.state = { error: false }; + } + + static getDerivedStateFromError(error: Error): object { + return { error }; + } + + // componentDidCatch(error: Error, errorInfo: any): void { + // // add our error logging service here + // // errorLoggingService(error,errorInfo) + // } + + render(): React.ReactNode { + const { children } = this.props; + const { error } = this.state; + if (error) { + toast('Something went wrong', defaultToastStyleOptions); + } + + return children; + } +} + +export default ErrorBoundary; diff --git a/src/renderer/index.tsx b/src/renderer/index.tsx index 2e4030f..a5ee714 100644 --- a/src/renderer/index.tsx +++ b/src/renderer/index.tsx @@ -2,8 +2,11 @@ import { createRoot } from 'react-dom/client'; import { Provider } from 'react-redux'; import { MemoryRouter as Router } from 'react-router-dom'; import { PersistGate } from 'redux-persist/integration/react'; +import { ToastContainer } from 'react-toastify'; import App from './App'; import store, { persistor } from './store/store'; +import ErrorBoundary from './components/ErrorBoundary'; +import 'react-toastify/dist/ReactToastify.css'; declare global { interface Window { @@ -14,11 +17,14 @@ declare global { const container = document.getElementById('root') as HTMLElement; const root = createRoot(container); root.render( - - - - - - - + + + + + + + + + + ); diff --git a/src/renderer/pages/Apps.tsx b/src/renderer/pages/Apps.tsx index 3c39410..728ae2f 100644 --- a/src/renderer/pages/Apps.tsx +++ b/src/renderer/pages/Apps.tsx @@ -5,6 +5,7 @@ import { useEffect, useState } from 'react'; import { Id, ToastContainer, ToastOptions, toast } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; import Button from 'renderer/components/Button'; +import defaultToastStyleOptions from 'shared/constants'; import { selectInstalledApps, selectRunningApps, @@ -121,12 +122,8 @@ export default function Apps() { data: { appId: string; progress: Progress; filename: string } ) => { const TOAST_STYLE: ToastOptions = { - position: 'bottom-center', + ...defaultToastStyleOptions, hideProgressBar: false, - closeOnClick: false, - pauseOnHover: false, - draggable: false, - theme: 'dark', progressClassName: 'toast-progress-bar', autoClose: false, }; diff --git a/src/renderer/pages/Navigation.tsx b/src/renderer/pages/Navigation.tsx index 60f941a..2532fab 100644 --- a/src/renderer/pages/Navigation.tsx +++ b/src/renderer/pages/Navigation.tsx @@ -10,6 +10,7 @@ import { startNode, stopNode, } from 'renderer/features/nodeSlice'; +import { showSnackbar } from 'renderer/store/snackbar'; import { useAppDispatch, useAppSelector } from 'renderer/utils/hooks'; import { styled } from 'styled-components'; import Spinner from 'renderer/components/Spinner'; @@ -133,9 +134,14 @@ export default function Navigtion() { const runningApps = useAppSelector(selectRunningApps); const handleScreenshot = async () => { - setShowSpinner(true); - await window.electron.ipcRenderer.madara.sendTweet(); - setShowSpinner(false); + try { + setShowSpinner(true); + await window.electron.ipcRenderer.madara.sendTweet(); + setShowSpinner(false); + } catch (err) { + dispatch(showSnackbar()); + setShowSpinner(false); + } }; return ( diff --git a/src/renderer/store/snackbar.ts b/src/renderer/store/snackbar.ts new file mode 100644 index 0000000..c461e6b --- /dev/null +++ b/src/renderer/store/snackbar.ts @@ -0,0 +1,39 @@ +import { createSlice } from '@reduxjs/toolkit'; +import { toast, ToastOptions } from 'react-toastify'; +import defaultToastStyleOptions from 'shared/constants'; + +const initialState = { + isVisible: false, +}; + +const snackbarSlice = createSlice({ + name: 'snackbar', + initialState, + // The `reducers` field lets us define reducers and generate associated actions + reducers: { + showToast: (state) => { + state.isVisible = true; + }, + hideToast: (state) => { + state.isVisible = false; + }, + }, +}); + +const { showToast, hideToast } = snackbarSlice.actions; + +export const showSnackbar = (message?: string) => (dispatch: any) => { + dispatch(showToast()); + const TOAST_STYLE: ToastOptions = { + ...defaultToastStyleOptions, + autoClose: 2000, + }; + toast(message ?? 'Something went wrong', { ...TOAST_STYLE }); +}; + +export const hideSnackbar = () => (dispatch: any) => { + dispatch(hideToast()); + toast.dismiss(); +}; + +export default snackbarSlice.reducer; diff --git a/src/renderer/store/store.ts b/src/renderer/store/store.ts index 6d76f92..4454ae2 100644 --- a/src/renderer/store/store.ts +++ b/src/renderer/store/store.ts @@ -4,6 +4,7 @@ import storage from 'redux-persist/lib/storage'; import appsReducer from '../features/appsSlice'; import nodeReducer from '../features/nodeSlice'; import walletReducer from '../features/walletSlice'; +import snackbarReducer from './snackbar'; const persistConfig = { key: 'root', @@ -16,6 +17,7 @@ const rootReducer = combineReducers({ node: nodeReducer, wallet: walletReducer, apps: appsReducer, + snackbar: snackbarReducer, }); const persistedReducer = persistReducer(persistConfig, rootReducer); diff --git a/src/shared/constants.ts b/src/shared/constants.ts new file mode 100644 index 0000000..09ff8aa --- /dev/null +++ b/src/shared/constants.ts @@ -0,0 +1,12 @@ +import { ToastOptions } from 'react-toastify'; + +const defaultToastStyleOptions: ToastOptions = { + position: 'bottom-center', + hideProgressBar: true, + closeOnClick: false, + pauseOnHover: false, + draggable: false, + theme: 'dark', +}; + +export default defaultToastStyleOptions;