diff options
author | Anton Tananaev <anton@traccar.org> | 2022-05-21 09:49:26 -0700 |
---|---|---|
committer | Anton Tananaev <anton@traccar.org> | 2022-05-21 09:49:26 -0700 |
commit | 7e96816f94314dcdf071eeee6e74a95bcace329f (patch) | |
tree | 21938fcdbe6e8b7a651308555af77c49dc59e1c2 | |
parent | 79dd42f0bdeef6d9f6331c0ec8301b2631a9cb90 (diff) | |
download | trackermap-web-7e96816f94314dcdf071eeee6e74a95bcace329f.tar.gz trackermap-web-7e96816f94314dcdf071eeee6e74a95bcace329f.tar.bz2 trackermap-web-7e96816f94314dcdf071eeee6e74a95bcace329f.zip |
Implement API error handling
38 files changed, 258 insertions, 76 deletions
diff --git a/modern/src/App.js b/modern/src/App.js index 0afefce4..fee94e5b 100644 --- a/modern/src/App.js +++ b/modern/src/App.js @@ -50,6 +50,7 @@ import PreferencesPage from './settings/PreferencesPage'; import BottomMenu from './common/components/BottomMenu'; import AccumulatorsPage from './settings/AccumulatorsPage'; import CommandSendPage from './settings/CommandSendPage'; +import ErrorHandler from './common/components/ErrorHandler'; const useStyles = makeStyles(() => ({ page: { @@ -86,6 +87,8 @@ const App = () => { if (items.length > 0) { dispatch(devicesActions.select(items[0].id)); } + } else { + throw Error(await response.text()); } history.push('/'); } else if (query.get('eventId')) { @@ -158,6 +161,7 @@ const App = () => { )} </Route> </Switch> + <ErrorHandler /> </ThemeProvider> )); }; diff --git a/modern/src/CachingController.js b/modern/src/CachingController.js index d6e20060..1c8fb43a 100644 --- a/modern/src/CachingController.js +++ b/modern/src/CachingController.js @@ -14,6 +14,8 @@ const CachingController = () => { const response = await fetch('/api/geofences'); if (response.ok) { dispatch(geofencesActions.update(await response.json())); + } else { + throw Error(await response.text()); } } }, [authenticated]); @@ -23,6 +25,8 @@ const CachingController = () => { const response = await fetch('/api/groups'); if (response.ok) { dispatch(groupsActions.update(await response.json())); + } else { + throw Error(await response.text()); } } }, [authenticated]); @@ -32,6 +36,8 @@ const CachingController = () => { const response = await fetch('/api/drivers'); if (response.ok) { dispatch(driversActions.update(await response.json())); + } else { + throw Error(await response.text()); } } }, [authenticated]); @@ -41,6 +47,8 @@ const CachingController = () => { const response = await fetch('/api/maintenance'); if (response.ok) { dispatch(maintenancesActions.update(await response.json())); + } else { + throw Error(await response.text()); } } }, [authenticated]); diff --git a/modern/src/SocketController.js b/modern/src/SocketController.js index cf01ff7f..9826d4b1 100644 --- a/modern/src/SocketController.js +++ b/modern/src/SocketController.js @@ -49,6 +49,8 @@ const SocketController = () => { const response = await fetch('/api/server'); if (response.ok) { dispatch(sessionActions.updateServer(await response.json())); + } else { + throw Error(await response.text()); } }, []); @@ -57,6 +59,8 @@ const SocketController = () => { const response = await fetch('/api/devices'); if (response.ok) { dispatch(devicesActions.refresh(await response.json())); + } else { + throw Error(await response.text()); } connectSocket(); return () => { @@ -88,10 +92,6 @@ const SocketController = () => { {notifications.map((notification) => ( <Snackbar key={notification.id} - anchorOrigin={{ - vertical: 'bottom', - horizontal: 'right', - }} open={notification.show} message={notification.message} autoHideDuration={snackBarDurationLongMs} diff --git a/modern/src/common/components/ErrorHandler.js b/modern/src/common/components/ErrorHandler.js new file mode 100644 index 00000000..534dd012 --- /dev/null +++ b/modern/src/common/components/ErrorHandler.js @@ -0,0 +1,26 @@ +import { Snackbar } from '@material-ui/core'; +import { Alert } from '@material-ui/lab'; +import React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { usePrevious } from '../../reactHelper'; +import { errorsActions } from '../../store'; + +const ErrorHandler = () => { + const dispatch = useDispatch(); + + const error = useSelector((state) => state.errors.errors.find(() => true)); + const previousError = usePrevious(error); + + return ( + <Snackbar open={!!error}> + <Alert + onClose={() => dispatch(errorsActions.pop())} + severity="error" + > + {error || previousError} + </Alert> + </Snackbar> + ); +}; + +export default ErrorHandler; diff --git a/modern/src/common/components/LinkField.js b/modern/src/common/components/LinkField.js index e11438df..0f6cc7ba 100644 --- a/modern/src/common/components/LinkField.js +++ b/modern/src/common/components/LinkField.js @@ -23,6 +23,8 @@ const LinkField = ({ const response = await fetch(endpointAll); if (response.ok) { setItems(await response.json()); + } else { + throw Error(await response.text()); } }, []); @@ -31,6 +33,8 @@ const LinkField = ({ if (response.ok) { const data = await response.json(); setLinked(data.map((it) => it.id)); + } else { + throw Error(await response.text()); } }, []); diff --git a/modern/src/common/components/PositionValue.js b/modern/src/common/components/PositionValue.js index 9f679605..1162a150 100644 --- a/modern/src/common/components/PositionValue.js +++ b/modern/src/common/components/PositionValue.js @@ -7,6 +7,7 @@ import { import { useAttributePreference, usePreference } from '../util/preferences'; import { useTranslation } from './LocalizationProvider'; import { useAdministrator } from '../util/permissions'; +import { useCatch } from '../../reactHelper'; const PositionValue = ({ position, property, attribute }) => { const t = useTranslation(); @@ -26,7 +27,7 @@ const PositionValue = ({ position, property, attribute }) => { setAddress(position.address); }, [position]); - const showAddress = async () => { + const showAddress = useCatch(async () => { const query = new URLSearchParams({ latitude: position.latitude, longitude: position.longitude, @@ -34,8 +35,10 @@ const PositionValue = ({ position, property, attribute }) => { const response = await fetch(`/api/server/geocode?${query.toString()}`); if (response.ok) { setAddress(await response.text()); + } else { + throw Error(await response.text()); } - }; + }); const formatValue = () => { switch (key) { diff --git a/modern/src/common/components/RemoveDialog.js b/modern/src/common/components/RemoveDialog.js index 6d191d6a..cbdbb05d 100644 --- a/modern/src/common/components/RemoveDialog.js +++ b/modern/src/common/components/RemoveDialog.js @@ -5,18 +5,21 @@ import DialogActions from '@material-ui/core/DialogActions'; import DialogContent from '@material-ui/core/DialogContent'; import DialogContentText from '@material-ui/core/DialogContentText'; import { useTranslation } from './LocalizationProvider'; +import { useCatch } from '../../reactHelper'; const RemoveDialog = ({ open, endpoint, itemId, onResult, }) => { const t = useTranslation(); - const handleRemove = async () => { + const handleRemove = useCatch(async () => { const response = await fetch(`/api/${endpoint}/${itemId}`, { method: 'DELETE' }); if (response.ok) { onResult(true); + } else { + throw Error(await response.text()); } - }; + }); return ( <Dialog diff --git a/modern/src/common/components/SelectField.js b/modern/src/common/components/SelectField.js index 98473b16..5a9a176b 100644 --- a/modern/src/common/components/SelectField.js +++ b/modern/src/common/components/SelectField.js @@ -25,6 +25,8 @@ const SelectField = ({ const response = await fetch(endpoint); if (response.ok) { setItems(await response.json()); + } else { + throw Error(await response.text()); } } }, []); diff --git a/modern/src/login/LoginPage.js b/modern/src/login/LoginPage.js index ae7b982d..5b690cdc 100644 --- a/modern/src/login/LoginPage.js +++ b/modern/src/login/LoginPage.js @@ -50,15 +50,19 @@ const LoginPage = () => { const handleSubmit = async (event) => { event.preventDefault(); - const response = await fetch('/api/session', { - method: 'POST', - body: new URLSearchParams(`email=${encodeURIComponent(email)}&password=${encodeURIComponent(password)}`), - }); - if (response.ok) { - const user = await response.json(); - dispatch(sessionActions.updateUser(user)); - history.push('/'); - } else { + try { + const response = await fetch('/api/session', { + method: 'POST', + body: new URLSearchParams(`email=${encodeURIComponent(email)}&password=${encodeURIComponent(password)}`), + }); + if (response.ok) { + const user = await response.json(); + dispatch(sessionActions.updateUser(user)); + history.push('/'); + } else { + throw Error(await response.text()); + } + } catch (error) { setFailed(true); setPassword(''); } @@ -153,7 +157,6 @@ const LoginPage = () => { </Grid> )} <Snackbar - anchorOrigin={{ vertical: 'top', horizontal: 'center' }} open={!!announcement && !announcementShown} message={announcement} action={( diff --git a/modern/src/login/RegisterPage.js b/modern/src/login/RegisterPage.js index cd6fc381..78728b58 100644 --- a/modern/src/login/RegisterPage.js +++ b/modern/src/login/RegisterPage.js @@ -7,6 +7,7 @@ import ArrowBackIcon from '@material-ui/icons/ArrowBack'; import LoginLayout from './LoginLayout'; import { useTranslation } from '../common/components/LocalizationProvider'; import { snackBarDurationShortMs } from '../common/util/duration'; +import { useCatch } from '../reactHelper'; const useStyles = makeStyles((theme) => ({ title: { @@ -33,7 +34,7 @@ const RegisterPage = () => { const [password, setPassword] = useState(''); const [snackbarOpen, setSnackbarOpen] = useState(false); - const handleSubmit = async () => { + const handleSubmit = useCatch(async () => { const response = await fetch('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -41,13 +42,14 @@ const RegisterPage = () => { }); if (response.ok) { setSnackbarOpen(true); + } else { + throw Error(await response.text()); } - }; + }); return ( <LoginLayout> <Snackbar - anchorOrigin={{ vertical: 'top', horizontal: 'center' }} open={snackbarOpen} onClose={() => history.push('/login')} autoHideDuration={snackBarDurationShortMs} diff --git a/modern/src/login/ResetPasswordPage.js b/modern/src/login/ResetPasswordPage.js index 6f7e784f..93e154e3 100644 --- a/modern/src/login/ResetPasswordPage.js +++ b/modern/src/login/ResetPasswordPage.js @@ -8,6 +8,7 @@ import LoginLayout from './LoginLayout'; import { useTranslation } from '../common/components/LocalizationProvider'; import useQuery from '../common/util/useQuery'; import { snackBarDurationShortMs } from '../common/util/duration'; +import { useCatch } from '../reactHelper'; const useStyles = makeStyles((theme) => ({ title: { @@ -36,7 +37,7 @@ const ResetPasswordPage = () => { const [password, setPassword] = useState(''); const [snackbarOpen, setSnackbarOpen] = useState(false); - const handleSubmit = async (event) => { + const handleSubmit = useCatch(async (event) => { event.preventDefault(); let response; if (!token) { @@ -52,13 +53,14 @@ const ResetPasswordPage = () => { } if (response.ok) { setSnackbarOpen(true); + } else { + throw Error(await response.text()); } - }; + }); return ( <LoginLayout> <Snackbar - anchorOrigin={{ vertical: 'top', horizontal: 'center' }} open={snackbarOpen} onClose={() => history.push('/login')} autoHideDuration={snackBarDurationShortMs} diff --git a/modern/src/main/DevicesList.js b/modern/src/main/DevicesList.js index 4da93547..6ff08c6a 100644 --- a/modern/src/main/DevicesList.js +++ b/modern/src/main/DevicesList.js @@ -164,6 +164,8 @@ const DeviceView = ({ updateTimestamp, onMenuClick, filter }) => { const response = await fetch('/api/devices'); if (response.ok) { dispatch(devicesActions.refresh(await response.json())); + } else { + throw Error(await response.text()); } }, [updateTimestamp]); diff --git a/modern/src/map/GeofenceEditMap.js b/modern/src/map/GeofenceEditMap.js index 2eb86321..0d7950a2 100644 --- a/modern/src/map/GeofenceEditMap.js +++ b/modern/src/map/GeofenceEditMap.js @@ -2,13 +2,14 @@ import 'mapbox-gl/dist/mapbox-gl.css'; import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css'; import MapboxDraw from '@mapbox/mapbox-gl-draw'; import theme from '@mapbox/mapbox-gl-draw/src/lib/theme'; -import { useCallback, useEffect } from 'react'; +import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; import { map } from './core/Map'; import { geofenceToFeature, geometryToArea } from './core/mapUtil'; -import { geofencesActions } from '../store'; +import { errorsActions, geofencesActions } from '../store'; +import { useCatchCallback } from '../reactHelper'; const draw = new MapboxDraw({ displayControlsDefault: false, @@ -39,10 +40,12 @@ const GeofenceEditMap = () => { const geofences = useSelector((state) => state.geofences.items); - const refreshGeofences = useCallback(async () => { + const refreshGeofences = useCatchCallback(async () => { const response = await fetch('/api/geofences'); if (response.ok) { dispatch(geofencesActions.refresh(await response.json())); + } else { + throw Error(await response.text()); } }, [dispatch]); @@ -58,33 +61,45 @@ const GeofenceEditMap = () => { const feature = event.features[0]; const newItem = { name: '', area: geometryToArea(feature.geometry) }; draw.delete(feature.id); - const response = await fetch('/api/geofences', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(newItem), - }); - if (response.ok) { - const item = await response.json(); - history.push(`/settings/geofence/${item.id}`); + try { + const response = await fetch('/api/geofences', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(newItem), + }); + if (response.ok) { + const item = await response.json(); + history.push(`/settings/geofence/${item.id}`); + } else { + throw Error(await response.text()); + } + } catch (error) { + dispatch(errorsActions.push(error.message)); } }; map.on('draw.create', listener); return () => map.off('draw.create', listener); - }, [history]); + }, [dispatch, history]); useEffect(() => { const listener = async (event) => { const feature = event.features[0]; - const response = await fetch(`/api/geofences/${feature.id}`, { method: 'DELETE' }); - if (response.ok) { - refreshGeofences(); + try { + const response = await fetch(`/api/geofences/${feature.id}`, { method: 'DELETE' }); + if (response.ok) { + refreshGeofences(); + } else { + throw Error(await response.text()); + } + } catch (error) { + dispatch(errorsActions.push(error.message)); } }; map.on('draw.delete', listener); return () => map.off('draw.delete', listener); - }, [refreshGeofences]); + }, [dispatch, refreshGeofences]); useEffect(() => { const listener = async (event) => { @@ -92,20 +107,26 @@ const GeofenceEditMap = () => { const item = Object.values(geofences).find((i) => i.id === feature.id); if (item) { const updatedItem = { ...item, area: geometryToArea(feature.geometry) }; - const response = await fetch(`/api/geofences/${feature.id}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(updatedItem), - }); - if (response.ok) { - refreshGeofences(); + try { + const response = await fetch(`/api/geofences/${feature.id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(updatedItem), + }); + if (response.ok) { + refreshGeofences(); + } else { + throw Error(await response.text()); + } + } catch (error) { + dispatch(errorsActions.push(error.message)); } } }; map.on('draw.update', listener); return () => map.off('draw.update', listener); - }, [geofences, refreshGeofences]); + }, [dispatch, geofences, refreshGeofences]); useEffect(() => { draw.deleteAll(); diff --git a/modern/src/other/EventPage.js b/modern/src/other/EventPage.js index 46f5e67c..7bcf90a8 100644 --- a/modern/src/other/EventPage.js +++ b/modern/src/other/EventPage.js @@ -37,6 +37,8 @@ const EventPage = () => { const response = await fetch(`/api/events/${id}`); if (response.ok) { setEvent(await response.json()); + } else { + throw Error(await response.text()); } } }, [id]); @@ -49,6 +51,8 @@ const EventPage = () => { if (positions.length > 0) { setPosition(positions[0]); } + } else { + throw Error(await response.text()); } } }, [event]); diff --git a/modern/src/other/PositionPage.js b/modern/src/other/PositionPage.js index 4c13955a..76bb560f 100644 --- a/modern/src/other/PositionPage.js +++ b/modern/src/other/PositionPage.js @@ -41,6 +41,8 @@ const PositionPage = () => { if (positions.length > 0) { setItem(positions[0]); } + } else { + throw Error(await response.text()); } } else { setItem({}); diff --git a/modern/src/other/ReplayPage.js b/modern/src/other/ReplayPage.js index 9bdf87be..c1c56ac1 100644 --- a/modern/src/other/ReplayPage.js +++ b/modern/src/other/ReplayPage.js @@ -18,6 +18,7 @@ import PositionsMap from '../map/PositionsMap'; import { formatTime } from '../common/util/formatter'; import ReportFilter from '../reports/components/ReportFilter'; import { useTranslation } from '../common/components/LocalizationProvider'; +import { useCatch } from '../reactHelper'; const useStyles = makeStyles((theme) => ({ root: { @@ -121,7 +122,7 @@ const ReplayPage = () => { } }, [index, positions]); - const handleSubmit = async (deviceId, from, to, _, headers) => { + const handleSubmit = useCatch(async (deviceId, from, to, _, headers) => { setSelectedDeviceId(deviceId); const query = new URLSearchParams({ deviceId, from, to }); const response = await fetch(`/api/positions?${query.toString()}`, { headers }); @@ -129,8 +130,10 @@ const ReplayPage = () => { setIndex(0); setPositions(await response.json()); setExpanded(false); + } else { + throw Error(await response.text()); } - }; + }); return ( <div className={classes.root}> diff --git a/modern/src/reactHelper.js b/modern/src/reactHelper.js index 124bb40a..c67f252b 100644 --- a/modern/src/reactHelper.js +++ b/modern/src/reactHelper.js @@ -1,4 +1,6 @@ -import { useRef, useEffect } from 'react'; +import { useRef, useEffect, useCallback } from 'react'; +import { useDispatch } from 'react-redux'; +import { errorsActions } from './store'; export const usePrevious = (value) => { const ref = useRef(); @@ -10,14 +12,29 @@ export const usePrevious = (value) => { /* eslint-disable */ export const useEffectAsync = (effect, deps) => { + const dispatch = useDispatch(); const ref = useRef(); useEffect(() => { - effect().then((result) => ref.current = result); + effect() + .then((result) => ref.current = result) + .catch((error) => dispatch(errorsActions.push(error.message))); + return () => { const result = ref.current; if (result) { result(); } }; - }, deps); + }, [...deps, dispatch]); +}; + +export const useCatch = (method) => { + const dispatch = useDispatch(); + return (...parameters) => { + method(...parameters).catch((error) => dispatch(errorsActions.push(error.message))); + }; +}; + +export const useCatchCallback = (method, deps) => { + return useCallback(useCatch(method), deps); }; diff --git a/modern/src/reports/ChartReportPage.js b/modern/src/reports/ChartReportPage.js index dbc205fc..bb71fa95 100644 --- a/modern/src/reports/ChartReportPage.js +++ b/modern/src/reports/ChartReportPage.js @@ -11,6 +11,7 @@ import { useTranslation } from '../common/components/LocalizationProvider'; import PageLayout from '../common/components/PageLayout'; import ReportsMenu from './components/ReportsMenu'; import usePositionAttributes from '../common/attributes/usePositionAttributes'; +import { useCatch } from '../reactHelper'; const useStyles = makeStyles(() => ({ chart: { @@ -35,7 +36,7 @@ const ChartReportPage = () => { return result; })(); - const handleSubmit = async (deviceId, from, to, mail, headers) => { + const handleSubmit = useCatch(async (deviceId, from, to, mail, headers) => { const query = new URLSearchParams({ deviceId, from, to, mail, }); @@ -48,8 +49,10 @@ const ChartReportPage = () => { fixTime: formatDate(position.fixTime, 'HH:mm:ss'), })); setItems(formattedPositions); + } else { + throw Error(await response.text()); } - }; + }); return ( <PageLayout menu={<ReportsMenu />} breadcrumbs={['reportTitle', 'reportChart']}> diff --git a/modern/src/reports/EventReportPage.js b/modern/src/reports/EventReportPage.js index 076979ba..f1b0c6bd 100644 --- a/modern/src/reports/EventReportPage.js +++ b/modern/src/reports/EventReportPage.js @@ -11,6 +11,7 @@ import PageLayout from '../common/components/PageLayout'; import ReportsMenu from './components/ReportsMenu'; import usePersistedState from '../common/util/usePersistedState'; import ColumnSelect from './components/ColumnSelect'; +import { useCatch } from '../reactHelper'; const typesArray = [ ['allEvents', 'eventAll'], @@ -53,7 +54,7 @@ const EventReportPage = () => { const [eventTypes, setEventTypes] = useState(['allEvents']); const [items, setItems] = useState([]); - const handleSubmit = async (deviceId, from, to, mail, headers) => { + const handleSubmit = useCatch(async (deviceId, from, to, mail, headers) => { const query = new URLSearchParams({ deviceId, from, to, mail, }); @@ -68,8 +69,10 @@ const EventReportPage = () => { window.location.assign(window.URL.createObjectURL(await response.blob())); } } + } else { + throw Error(await response.text()); } - }; + }); const formatValue = (item, key) => { switch (key) { diff --git a/modern/src/reports/RouteReportPage.js b/modern/src/reports/RouteReportPage.js index cd2c3694..118c7758 100644 --- a/modern/src/reports/RouteReportPage.js +++ b/modern/src/reports/RouteReportPage.js @@ -10,6 +10,7 @@ import usePersistedState from '../common/util/usePersistedState'; import PositionValue from '../common/components/PositionValue'; import ColumnSelect from './components/ColumnSelect'; import usePositionAttributes from '../common/attributes/usePositionAttributes'; +import { useCatch } from '../reactHelper'; const RouteReportPage = () => { const t = useTranslation(); @@ -19,7 +20,7 @@ const RouteReportPage = () => { const [columns, setColumns] = usePersistedState('routeColumns', ['fixTime', 'latitude', 'longitude', 'speed', 'address']); const [items, setItems] = useState([]); - const handleSubmit = async (deviceId, from, to, mail, headers) => { + const handleSubmit = useCatch(async (deviceId, from, to, mail, headers) => { const query = new URLSearchParams({ deviceId, from, to, mail, }); @@ -33,8 +34,10 @@ const RouteReportPage = () => { window.location.assign(window.URL.createObjectURL(await response.blob())); } } + } else { + throw Error(await response.text()); } - }; + }); return ( <PageLayout menu={<ReportsMenu />} breadcrumbs={['reportTitle', 'reportRoute']}> diff --git a/modern/src/reports/StatisticsPage.js b/modern/src/reports/StatisticsPage.js index 214773fe..b1652468 100644 --- a/modern/src/reports/StatisticsPage.js +++ b/modern/src/reports/StatisticsPage.js @@ -9,6 +9,7 @@ import ReportsMenu from './components/ReportsMenu'; import ReportFilter from './components/ReportFilter'; import usePersistedState from '../common/util/usePersistedState'; import ColumnSelect from './components/ColumnSelect'; +import { useCatch } from '../reactHelper'; const columnsArray = [ ['captureTime', 'statisticsCaptureTime'], @@ -30,13 +31,15 @@ const StatisticsPage = () => { const [columns, setColumns] = usePersistedState('statisticsColumns', ['captureTime', 'activeUsers', 'activeDevices', 'messagesStored']); const [items, setItems] = useState([]); - const handleSubmit = async (_, from, to) => { + const handleSubmit = useCatch(async (_, from, to) => { const query = new URLSearchParams({ from, to }); const response = await fetch(`/api/statistics?${query.toString()}`, { Accept: 'application/json' }); if (response.ok) { setItems(await response.json()); + } else { + throw Error(await response.text()); } - }; + }); return ( <PageLayout menu={<ReportsMenu />} breadcrumbs={['reportTitle', 'statisticsTitle']}> diff --git a/modern/src/reports/StopReportPage.js b/modern/src/reports/StopReportPage.js index dd55b774..dc2ac19a 100644 --- a/modern/src/reports/StopReportPage.js +++ b/modern/src/reports/StopReportPage.js @@ -12,6 +12,7 @@ import PageLayout from '../common/components/PageLayout'; import ReportsMenu from './components/ReportsMenu'; import ColumnSelect from './components/ColumnSelect'; import usePersistedState from '../common/util/usePersistedState'; +import { useCatch } from '../reactHelper'; const columnsArray = [ ['startTime', 'reportStartTime'], @@ -33,7 +34,7 @@ const StopReportPage = () => { const [columns, setColumns] = usePersistedState('stopColumns', ['startTime', 'endTime', 'startOdometer', 'address']); const [items, setItems] = useState([]); - const handleSubmit = async (deviceId, from, to, mail, headers) => { + const handleSubmit = useCatch(async (deviceId, from, to, mail, headers) => { const query = new URLSearchParams({ deviceId, from, to, mail, }); @@ -47,8 +48,10 @@ const StopReportPage = () => { window.location.assign(window.URL.createObjectURL(await response.blob())); } } + } else { + throw Error(await response.text()); } - }; + }); const formatValue = (item, key) => { switch (key) { diff --git a/modern/src/reports/SummaryReportPage.js b/modern/src/reports/SummaryReportPage.js index e838cd9e..1b8fd8e0 100644 --- a/modern/src/reports/SummaryReportPage.js +++ b/modern/src/reports/SummaryReportPage.js @@ -12,6 +12,7 @@ import PageLayout from '../common/components/PageLayout'; import ReportsMenu from './components/ReportsMenu'; import usePersistedState from '../common/util/usePersistedState'; import ColumnSelect from './components/ColumnSelect'; +import { useCatch } from '../reactHelper'; const columnsArray = [ ['startTime', 'reportStartDate'], @@ -37,7 +38,7 @@ const SummaryReportPage = () => { const [daily, setDaily] = useState(false); const [items, setItems] = useState([]); - const handleSubmit = async (deviceId, from, to, mail, headers) => { + const handleSubmit = useCatch(async (deviceId, from, to, mail, headers) => { const query = new URLSearchParams({ deviceId, from, to, daily, mail, }); @@ -51,8 +52,10 @@ const SummaryReportPage = () => { window.location.assign(window.URL.createObjectURL(await response.blob())); } } + } else { + throw Error(await response.text()); } - }; + }); const formatValue = (item, key) => { switch (key) { diff --git a/modern/src/reports/TripReportPage.js b/modern/src/reports/TripReportPage.js index c9d620e8..6e230388 100644 --- a/modern/src/reports/TripReportPage.js +++ b/modern/src/reports/TripReportPage.js @@ -12,6 +12,7 @@ import PageLayout from '../common/components/PageLayout'; import ReportsMenu from './components/ReportsMenu'; import ColumnSelect from './components/ColumnSelect'; import usePersistedState from '../common/util/usePersistedState'; +import { useCatch } from '../reactHelper'; const columnsArray = [ ['startTime', 'reportStartTime'], @@ -39,7 +40,7 @@ const TripReportPage = () => { const [columns, setColumns] = usePersistedState('tripColumns', ['startTime', 'endTime', 'distance', 'averageSpeed']); const [items, setItems] = useState([]); - const handleSubmit = async (deviceId, from, to, mail, headers) => { + const handleSubmit = useCatch(async (deviceId, from, to, mail, headers) => { const query = new URLSearchParams({ deviceId, from, to, mail, }); @@ -53,8 +54,10 @@ const TripReportPage = () => { window.location.assign(window.URL.createObjectURL(await response.blob())); } } + } else { + throw Error(await response.text()); } - }; + }); const formatValue = (item, key) => { switch (key) { diff --git a/modern/src/settings/AccumulatorsPage.js b/modern/src/settings/AccumulatorsPage.js index 0fcde8d1..b22fe02e 100644 --- a/modern/src/settings/AccumulatorsPage.js +++ b/modern/src/settings/AccumulatorsPage.js @@ -8,6 +8,7 @@ import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import { useTranslation } from '../common/components/LocalizationProvider'; import PageLayout from '../common/components/PageLayout'; import SettingsMenu from './components/SettingsMenu'; +import { useCatch } from '../reactHelper'; const useStyles = makeStyles((theme) => ({ container: { @@ -45,7 +46,7 @@ const AccumulatorsPage = () => { } }, [deviceId, position]); - const handleSave = async () => { + const handleSave = useCatch(async () => { const response = await fetch(`/api/devices/${deviceId}/accumulators`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, @@ -54,8 +55,10 @@ const AccumulatorsPage = () => { if (response.ok) { history.goBack(); + } else { + throw Error(await response.text()); } - }; + }); return ( <PageLayout menu={<SettingsMenu />} breadcrumbs={['sharedDeviceAccumulators']}> diff --git a/modern/src/settings/CalendarsPage.js b/modern/src/settings/CalendarsPage.js index 875530a8..06697647 100644 --- a/modern/src/settings/CalendarsPage.js +++ b/modern/src/settings/CalendarsPage.js @@ -26,6 +26,8 @@ const CalendarsView = ({ updateTimestamp, onMenuClick }) => { const response = await fetch('/api/calendars'); if (response.ok) { setItems(await response.json()); + } else { + throw Error(await response.text()); } }, [updateTimestamp]); diff --git a/modern/src/settings/CommandSendPage.js b/modern/src/settings/CommandSendPage.js index f7e62b24..a817e11d 100644 --- a/modern/src/settings/CommandSendPage.js +++ b/modern/src/settings/CommandSendPage.js @@ -9,6 +9,7 @@ import BaseCommandView from './components/BaseCommandView'; import SelectField from '../common/components/SelectField'; import PageLayout from '../common/components/PageLayout'; import SettingsMenu from './components/SettingsMenu'; +import { useCatch } from '../reactHelper'; const useStyles = makeStyles((theme) => ({ container: { @@ -36,12 +37,14 @@ const CommandSendPage = () => { const [savedId, setSavedId] = useState(0); const [item, setItem] = useState({}); - const handleSend = async () => { + const handleSend = useCatch(async () => { let command; if (savedId) { const response = await fetch(`/api/commands/${savedId}`); if (response.ok) { command = await response.json(); + } else { + throw Error(await response.text()); } } else { command = item; @@ -57,8 +60,10 @@ const CommandSendPage = () => { if (response.ok) { history.goBack(); + } else { + throw Error(await response.text()); } - }; + }); const validate = () => savedId || (item && item.type); diff --git a/modern/src/settings/CommandsPage.js b/modern/src/settings/CommandsPage.js index 20b792b8..1b09a8bd 100644 --- a/modern/src/settings/CommandsPage.js +++ b/modern/src/settings/CommandsPage.js @@ -28,6 +28,8 @@ const CommandsView = ({ updateTimestamp, onMenuClick }) => { const response = await fetch('/api/commands'); if (response.ok) { setItems(await response.json()); + } else { + throw Error(await response.text()); } }, [updateTimestamp]); diff --git a/modern/src/settings/ComputedAttributesPage.js b/modern/src/settings/ComputedAttributesPage.js index 451b47a7..86704c3b 100644 --- a/modern/src/settings/ComputedAttributesPage.js +++ b/modern/src/settings/ComputedAttributesPage.js @@ -28,6 +28,8 @@ const ComputedAttributeView = ({ updateTimestamp, onMenuClick }) => { const response = await fetch('/api/attributes/computed'); if (response.ok) { setItems(await response.json()); + } else { + throw Error(await response.text()); } }, [updateTimestamp]); diff --git a/modern/src/settings/DriversPage.js b/modern/src/settings/DriversPage.js index f5908381..26601777 100644 --- a/modern/src/settings/DriversPage.js +++ b/modern/src/settings/DriversPage.js @@ -26,6 +26,8 @@ const DriversView = ({ updateTimestamp, onMenuClick }) => { const response = await fetch('/api/drivers'); if (response.ok) { setItems(await response.json()); + } else { + throw Error(await response.text()); } }, [updateTimestamp]); diff --git a/modern/src/settings/GroupsPage.js b/modern/src/settings/GroupsPage.js index 5d23c2d9..257d0bca 100644 --- a/modern/src/settings/GroupsPage.js +++ b/modern/src/settings/GroupsPage.js @@ -26,6 +26,8 @@ const GroupsView = ({ updateTimestamp, onMenuClick }) => { const response = await fetch('/api/groups'); if (response.ok) { setItems(await response.json()); + } else { + throw Error(await response.text()); } }, [updateTimestamp]); diff --git a/modern/src/settings/MaintenancesPage.js b/modern/src/settings/MaintenancesPage.js index 3801f010..ea00e7e1 100644 --- a/modern/src/settings/MaintenancesPage.js +++ b/modern/src/settings/MaintenancesPage.js @@ -34,6 +34,8 @@ const MaintenancesView = ({ updateTimestamp, onMenuClick }) => { const response = await fetch('/api/maintenance'); if (response.ok) { setItems(await response.json()); + } else { + throw Error(await response.text()); } }, [updateTimestamp]); diff --git a/modern/src/settings/NotificationsPage.js b/modern/src/settings/NotificationsPage.js index 7ad5186b..de3e762f 100644 --- a/modern/src/settings/NotificationsPage.js +++ b/modern/src/settings/NotificationsPage.js @@ -28,6 +28,8 @@ const NotificationsView = ({ updateTimestamp, onMenuClick }) => { const response = await fetch('/api/notifications'); if (response.ok) { setItems(await response.json()); + } else { + throw Error(await response.text()); } }, [updateTimestamp]); diff --git a/modern/src/settings/ServerPage.js b/modern/src/settings/ServerPage.js index 5fdba47b..b745cb96 100644 --- a/modern/src/settings/ServerPage.js +++ b/modern/src/settings/ServerPage.js @@ -15,6 +15,7 @@ import PageLayout from '../common/components/PageLayout'; import SettingsMenu from './components/SettingsMenu'; import useCommonDeviceAttributes from '../common/attributes/useCommonDeviceAttributes'; import useCommonUserAttributes from '../common/attributes/useCommonUserAttributes'; +import { useCatch } from '../reactHelper'; const useStyles = makeStyles((theme) => ({ container: { @@ -44,7 +45,7 @@ const ServerPage = () => { const original = useSelector((state) => state.session.server); const [item, setItem] = useState({ ...original }); - const handleSave = async () => { + const handleSave = useCatch(async () => { const response = await fetch('/api/server', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, @@ -54,8 +55,10 @@ const ServerPage = () => { if (response.ok) { dispatch(sessionActions.updateServer(await response.json())); history.goBack(); + } else { + throw Error(await response.text()); } - }; + }); return ( <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'settingsServer']}> diff --git a/modern/src/settings/UsersPage.js b/modern/src/settings/UsersPage.js index 2d8d1199..235ae4c7 100644 --- a/modern/src/settings/UsersPage.js +++ b/modern/src/settings/UsersPage.js @@ -27,6 +27,8 @@ const UsersView = ({ updateTimestamp, onMenuClick }) => { const response = await fetch('/api/users'); if (response.ok) { setItems(await response.json()); + } else { + throw Error(await response.text()); } }, [updateTimestamp]); diff --git a/modern/src/settings/components/EditItemView.js b/modern/src/settings/components/EditItemView.js index 9b9918fe..28598e77 100644 --- a/modern/src/settings/components/EditItemView.js +++ b/modern/src/settings/components/EditItemView.js @@ -5,7 +5,7 @@ import Container from '@material-ui/core/Container'; import Button from '@material-ui/core/Button'; import FormControl from '@material-ui/core/FormControl'; -import { useEffectAsync } from '../../reactHelper'; +import { useCatch, useEffectAsync } from '../../reactHelper'; import { useTranslation } from '../../common/components/LocalizationProvider'; import PageLayout from '../../common/components/PageLayout'; @@ -36,13 +36,15 @@ const EditItemView = ({ const response = await fetch(`/api/${endpoint}/${id}`); if (response.ok) { setItem(await response.json()); + } else { + throw Error(await response.text()); } } else { setItem(defaultItem || {}); } }, [id]); - const handleSave = async () => { + const handleSave = useCatch(async () => { let url = `/api/${endpoint}`; if (id) { url += `/${id}`; @@ -59,8 +61,10 @@ const EditItemView = ({ onItemSaved(await response.json()); } history.goBack(); + } else { + throw Error(await response.text()); } - }; + }); return ( <PageLayout menu={menu} breadcrumbs={breadcrumbs}> diff --git a/modern/src/store/errors.js b/modern/src/store/errors.js new file mode 100644 index 00000000..d1b86e5a --- /dev/null +++ b/modern/src/store/errors.js @@ -0,0 +1,21 @@ +import { createSlice } from '@reduxjs/toolkit'; + +const { reducer, actions } = createSlice({ + name: 'errors', + initialState: { + errors: [], + }, + reducers: { + push(state, action) { + state.errors.push(action.payload); + }, + pop(state, _) { + if (state.errors.length) { + state.errors.shift(); + } + }, + }, +}); + +export { actions as errorsActions }; +export { reducer as errorsReducer }; diff --git a/modern/src/store/index.js b/modern/src/store/index.js index 6e2bb204..72867b76 100644 --- a/modern/src/store/index.js +++ b/modern/src/store/index.js @@ -1,5 +1,6 @@ import { combineReducers, configureStore } from '@reduxjs/toolkit'; +import { errorsReducer as errors } from './errors'; import { sessionReducer as session } from './session'; import { devicesReducer as devices } from './devices'; import { positionsReducer as positions } from './positions'; @@ -9,6 +10,7 @@ import { driversReducer as drivers } from './drivers'; import { maintenancesReducer as maintenances } from './maintenances'; const reducer = combineReducers({ + errors, session, devices, positions, @@ -18,6 +20,7 @@ const reducer = combineReducers({ maintenances, }); +export { errorsActions } from './errors'; export { sessionActions } from './session'; export { devicesActions } from './devices'; export { positionsActions } from './positions'; |