Add error exception

This commit is contained in:
Paulo Gustavo Veiga 2021-02-04 23:05:46 -08:00
parent 6115eeec26
commit 680a679a92
15 changed files with 115 additions and 52 deletions

View File

@ -8,7 +8,7 @@ import ForgotPasswordSuccessPage from './components/forgot-password-success-page
import RegistationPage from './components/registration-page'; import RegistationPage from './components/registration-page';
import LoginPage from './components/login-page'; import LoginPage from './components/login-page';
import MapsPage from './components/maps-page'; import MapsPage from './components/maps-page';
import store from "./store"; import store from "./redux/store";
import { ForgotPasswordPage } from './components/forgot-password-page'; import { ForgotPasswordPage } from './components/forgot-password-page';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { QueryClient, QueryClientProvider } from 'react-query'; import { QueryClient, QueryClientProvider } from 'react-query';

View File

@ -13,7 +13,6 @@ export type Label = {
iconName: string; iconName: string;
} }
export type MapInfo = { export type MapInfo = {
id: number; id: number;
starred: boolean; starred: boolean;

View File

@ -1,18 +1,23 @@
import axios from 'axios'; import axios from 'axios';
import { ErrorInfo, MapInfo, BasicMapInfo, NewUser, Label } from '..'; import { useIntl } from 'react-intl';
import MockClient from '../mock-client/'; import Client, { ErrorInfo, MapInfo, BasicMapInfo, NewUser, Label } from '..';
//@Remove inheritance once is it completed. export default class RestClient implements Client {
export default class RestClient extends MockClient {
private baseUrl: string; private baseUrl: string;
private authFailed: () => void private sessionExpired: () => void
constructor(baseUrl: string, authFailed: () => void) {
super(); constructor(baseUrl: string, sessionExpired: () => void) {
this.baseUrl = baseUrl; this.baseUrl = baseUrl;
this.sessionExpired = sessionExpired;
} }
fetchMapInfo(id: number): Promise<BasicMapInfo> {
throw new Error('Method not implemented.');
}
private parseResponseOnError = (response: any): ErrorInfo => { private parseResponseOnError = (response: any): ErrorInfo => {
const intl = useIntl();
let result: ErrorInfo | undefined; let result: ErrorInfo | undefined;
if (response) { if (response) {
@ -22,7 +27,9 @@ export default class RestClient extends MockClient {
switch (status) { switch (status) {
case 401: case 401:
// this.authFailed(); case 302:
this.sessionExpired();
result = { msg: intl.formatMessage({ id: "expired.title", defaultMessage: "Your current session has expired. Please, sign in and try again." })}
break; break;
default: default:
if (data) { if (data) {

View File

@ -8,7 +8,7 @@ import Footer from '../layout/footer'
import FormContainer from '../layout/form-container'; import FormContainer from '../layout/form-container';
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { useMutation } from 'react-query' import { useMutation } from 'react-query'
import { activeInstance } from '../../reducers/serviceSlice' import { activeInstance } from '../../redux/clientSlice'
import Input from '../form/input' import Input from '../form/input'
import GlobalError from '../form/global-error' import GlobalError from '../form/global-error'
import SubmitButton from '../form/submit-button' import SubmitButton from '../form/submit-button'

View File

@ -6,7 +6,7 @@ import { FormControl } from '@material-ui/core';
import Client, { BasicMapInfo, ErrorInfo } from '../../../../client'; import Client, { BasicMapInfo, ErrorInfo } from '../../../../client';
import { activeInstance } from '../../../../reducers/serviceSlice'; import { activeInstance } from '../../../../redux/clientSlice';
import Input from '../../../form/input'; import Input from '../../../form/input';
import BaseDialog from '../base-dialog'; import BaseDialog from '../base-dialog';

View File

@ -4,7 +4,7 @@ import { FormattedMessage, useIntl } from "react-intl";
import { useMutation, useQueryClient } from "react-query"; import { useMutation, useQueryClient } from "react-query";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import Client from "../../../../client"; import Client from "../../../../client";
import { activeInstance } from '../../../../reducers/serviceSlice'; import { activeInstance } from '../../../../redux/clientSlice';
import { DialogProps, fetchMapById, handleOnMutationSuccess } from ".."; import { DialogProps, fetchMapById, handleOnMutationSuccess } from "..";
import BaseDialog from "../base-dialog"; import BaseDialog from "../base-dialog";

View File

@ -5,7 +5,7 @@ import { FormControl } from "@material-ui/core";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import Client, { BasicMapInfo, ErrorInfo } from "../../../../client"; import Client, { BasicMapInfo, ErrorInfo } from "../../../../client";
import { activeInstance } from '../../../../reducers/serviceSlice'; import { activeInstance } from '../../../../redux/clientSlice';
import Input from "../../../form/input"; import Input from "../../../form/input";
import { DialogProps, fetchMapById } from ".."; import { DialogProps, fetchMapById } from "..";
import BaseDialog from "../base-dialog"; import BaseDialog from "../base-dialog";

View File

@ -6,7 +6,7 @@ import { ErrorInfo, MapInfo } from '../../../client';
import Client from '../../../client'; import Client from '../../../client';
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { QueryClient, useQuery } from 'react-query'; import { QueryClient, useQuery } from 'react-query';
import { activeInstance } from '../../../reducers/serviceSlice'; import { activeInstance } from '../../../redux/clientSlice';
import DuplicateDialog from './duplicate-dialog'; import DuplicateDialog from './duplicate-dialog';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import CreateDialog from './create-dialog'; import CreateDialog from './create-dialog';
@ -49,8 +49,6 @@ const ActionDispatcher = (props: ActionDialogProps) => {
</span > </span >
); );
} }
type MapLoadResult = { type MapLoadResult = {
isLoading: boolean, isLoading: boolean,
error: ErrorInfo | null, error: ErrorInfo | null,

View File

@ -3,7 +3,7 @@ import { useIntl } from "react-intl";
import { useMutation, useQueryClient } from "react-query"; import { useMutation, useQueryClient } from "react-query";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import Client, { BasicMapInfo, ErrorInfo } from "../../../../client"; import Client, { BasicMapInfo, ErrorInfo } from "../../../../client";
import { activeInstance } from '../../../../reducers/serviceSlice'; import { activeInstance } from '../../../../redux/clientSlice';
import { DialogProps, fetchMapById, handleOnMutationSuccess } from ".."; import { DialogProps, fetchMapById, handleOnMutationSuccess } from "..";
import Input from "../../../form/input"; import Input from "../../../form/input";
import { FormControl } from "@material-ui/core"; import { FormControl } from "@material-ui/core";

View File

@ -9,15 +9,16 @@ import ListItem from '@material-ui/core/ListItem';
import ListItemIcon from '@material-ui/core/ListItemIcon'; import ListItemIcon from '@material-ui/core/ListItemIcon';
import { useStyles } from './style'; import { useStyles } from './style';
import { AccountCircle, AcUnitTwoTone, AddCircleTwoTone, CloudUploadTwoTone, DeleteOutlineTwoTone, EmailOutlined, EmojiPeopleOutlined, ExitToAppOutlined, FeedbackOutlined, Help, LabelTwoTone, PeopleAltTwoTone, PersonAddTwoTone, PersonOutlineTwoTone, PersonTwoTone, PolicyOutlined, PublicTwoTone, SettingsApplicationsOutlined, ShareTwoTone, StarTwoTone } from '@material-ui/icons'; import { AccountCircle, AcUnitTwoTone, AddCircleTwoTone, CloudUploadTwoTone, DeleteOutlineTwoTone, EmailOutlined, EmojiPeopleOutlined, ExitToAppOutlined, FeedbackOutlined, Help, LabelTwoTone, PeopleAltTwoTone, PersonAddTwoTone, PersonOutlineTwoTone, PersonTwoTone, PolicyOutlined, PublicTwoTone, SettingsApplicationsOutlined, ShareTwoTone, StarTwoTone } from '@material-ui/icons';
import { Button, Link, ListItemSecondaryAction, ListItemText, Menu, MenuItem, Tooltip } from '@material-ui/core'; import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Link, ListItemSecondaryAction, ListItemText, Menu, MenuItem, Tooltip } from '@material-ui/core';
import { MapsList } from './maps-list'; import { MapsList } from './maps-list';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { useQuery, useMutation, useQueryClient } from 'react-query'; import { useQuery, useMutation, useQueryClient } from 'react-query';
import { activeInstance } from '../../reducers/serviceSlice'; import { activeInstance, activeInstanceStatus, ClientStatus } from '../../redux/clientSlice';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import Client, { Label } from '../../client'; import Client, { Label } from '../../client';
import ActionDispatcher from './action-dispatcher'; import ActionDispatcher from './action-dispatcher';
import { ActionType } from './action-chooser'; import { ActionType } from './action-chooser';
import { Alert, AlertTitle } from '@material-ui/lab';
const logoIcon = require('../../images/logo-small.svg'); const logoIcon = require('../../images/logo-small.svg');
const poweredByIcon = require('../../images/pwrdby-white.svg'); const poweredByIcon = require('../../images/pwrdby-white.svg');
@ -46,12 +47,10 @@ const MapsPage = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [activeDialog, setActiveDialog] = React.useState<ActionType | undefined>(undefined); const [activeDialog, setActiveDialog] = React.useState<ActionType | undefined>(undefined);
useEffect(() => { useEffect(() => {
document.title = 'Maps | WiseMapping'; document.title = 'Maps | WiseMapping';
}, []); }, []);
const mutation = useMutation( const mutation = useMutation(
(id: number) => client.deleteLabel(id), (id: number) => client.deleteLabel(id),
{ {
@ -106,6 +105,7 @@ const MapsPage = () => {
return ( return (
<div className={classes.root}> <div className={classes.root}>
<HandleClientStatus/>
<AppBar <AppBar
position="fixed" position="fixed"
className={clsx(classes.appBar, { className={clsx(classes.appBar, {
@ -238,6 +238,46 @@ const StyleListItem = (props: ListItemProps) => {
); );
} }
const HandleClientStatus = () => {
const status: ClientStatus = useSelector(activeInstanceStatus);
const handleOnClose = () => {
window.location.href = '/c/login';
}
return (
<div>
<Dialog
open={status.state != 'healthy'}
onClose={handleOnClose}
maxWidth="sm"
fullWidth={true}>
<DialogTitle>
<FormattedMessage id="expired.title" defaultMessage="Your session has expired" />
</DialogTitle>
<DialogContent>
<Alert severity="error">
<AlertTitle><FormattedMessage id="expired.title" defaultMessage="Your current session has expired. Please, sign in and try again." /></AlertTitle>
</Alert>
</DialogContent>
<DialogActions>
<Button
type="button"
color="primary"
size="medium"
onClick={handleOnClose} >
<FormattedMessage id="action.close-button" defaultMessage="Close" />
</Button>
</DialogActions>
</Dialog>
</div>
)
}
const ProfileToobarButton = () => { const ProfileToobarButton = () => {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null); const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl); const open = Boolean(anchorEl);

View File

@ -18,7 +18,7 @@ import StarRateRoundedIcon from '@material-ui/icons/StarRateRounded';
import MoreHorizIcon from '@material-ui/icons/MoreHoriz'; import MoreHorizIcon from '@material-ui/icons/MoreHoriz';
import { CSSProperties } from 'react'; import { CSSProperties } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { activeInstance } from '../../../reducers/serviceSlice'; import { activeInstance } from '../../../redux/clientSlice';
import { useMutation, useQuery, useQueryClient } from 'react-query'; import { useMutation, useQuery, useQueryClient } from 'react-query';
import { ErrorInfo, MapInfo } from '../../../client'; import { ErrorInfo, MapInfo } from '../../../client';
import Client from '../../../client'; import Client from '../../../client';
@ -30,6 +30,7 @@ import moment from 'moment'
import { Filter, LabelFilter } from '..'; import { Filter, LabelFilter } from '..';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { DeleteOutlined, LabelTwoTone } from '@material-ui/icons'; import { DeleteOutlined, LabelTwoTone } from '@material-ui/icons';
import Alert from '@material-ui/lab/Alert';
function descendingComparator<T>(a: T, b: T, orderBy: keyof T) { function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
@ -162,7 +163,7 @@ const mapsFilter = (filter: Filter, search: string): ((mapInfo: MapInfo) => bool
result = mapInfo.starred; result = mapInfo.starred;
break; break;
case 'owned': case 'owned':
//@todo: complete ... //@todo: complete ...
result = mapInfo.starred; result = mapInfo.starred;
break; break;
@ -398,7 +399,7 @@ export const MapsList = (props: MapsListProps) => {
{isLoading ? ( {isLoading ? (
<TableRow><TableCell colSpan={6}>Loading ...</TableCell></TableRow>) : <TableRow><TableCell colSpan={6}>Loading ...</TableCell></TableRow>) :
(mapsInfo.length == 0 ? (mapsInfo.length == 0 ?
(<TableRow><TableCell colSpan={6} style={{ textAlign: 'center' }}><FormattedMessage id="maps.emptyresult" defaultMessage="No matching record found with the current filter criteria." /></TableCell></TableRow>) : (<TableRow><TableCell colSpan={6} style={{ textAlign: 'center' }}><FormattedMessage id="maps.empty-result" defaultMessage="No matching record found with the current filter criteria." /></TableCell></TableRow>) :
stableSort(mapsInfo, getComparator(order, orderBy)) stableSort(mapsInfo, getComparator(order, orderBy))
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map((row: MapInfo) => { .map((row: MapInfo) => {
@ -478,4 +479,10 @@ export const MapsList = (props: MapsListProps) => {
<ActionDispatcher action={activeDialog?.actionType} onClose={() => setActiveDialog(undefined)} mapId={activeDialog ? activeDialog.mapId : -1} /> <ActionDispatcher action={activeDialog?.actionType} onClose={() => setActiveDialog(undefined)} mapId={activeDialog ? activeDialog.mapId : -1} />
</div > </div >
); );
}
const ErrorDialog = (props) => {
return (<Alert severity="error">This is an error alert check it out!</Alert>);
} }

View File

@ -11,7 +11,7 @@ import Footer from '../layout/footer';
import { FormControl, Typography } from '@material-ui/core'; import { FormControl, Typography } from '@material-ui/core';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { useMutation } from 'react-query'; import { useMutation } from 'react-query';
import { activeInstance } from '../../reducers/serviceSlice'; import { activeInstance } from '../../redux/clientSlice';
import Input from '../form/input'; import Input from '../form/input';
import GlobalError from '../form/global-error'; import GlobalError from '../form/global-error';
import SubmitButton from '../form/submit-button'; import SubmitButton from '../form/submit-button';

View File

@ -1,5 +1,4 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit' import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import axios from 'axios';
import Client from '../client'; import Client from '../client';
import MockClient from '../client/mock-client'; import MockClient from '../client/mock-client';
import RestClient from '../client/rest-client'; import RestClient from '../client/rest-client';
@ -25,7 +24,7 @@ class RutimeConfig {
buildClient(): Client { buildClient(): Client {
let result: Client; let result: Client;
if (this.config) { if (this.config) {
result = new RestClient(this.config.apiBaseUrl, () => { console.log("401 error") }); result = new RestClient(this.config.apiBaseUrl, () => { sessionExpired() });
console.log("Service using rest client. " + JSON.stringify(this.config)) console.log("Service using rest client. " + JSON.stringify(this.config))
} else { } else {
@ -36,27 +35,40 @@ class RutimeConfig {
} }
} }
interface ServiceState { export interface ClientStatus {
instance: Client; state: 'healthy' | 'session-expired';
msg?: string
} }
const initialState: ServiceState = { export interface ClientState {
instance: new RutimeConfig().load().buildClient() instance: Client;
status: ClientStatus;
}
const initialState: ClientState = {
instance: new RutimeConfig().load().buildClient(),
status: { state: 'healthy' }
}; };
export const serviceSlice = createSlice({ export const clientSlice = createSlice({
name: "service", name: "client",
initialState: initialState, initialState: initialState,
reducers: { reducers: {
initialize(state, action: PayloadAction<void[]>) { sessionExpired(state, action: PayloadAction<void>) {
// state.instance = new RutimeConfig().load().buildClient() state.status = { state: 'session-expired', msg: 'Sessions has expired. You need to login again.' }
} }
}, },
}); });
export const activeInstance = (state: any): Client => { export const activeInstance = (state: any): Client => {
return state.service.instance; return state.client.instance;
} }
export default serviceSlice.reducer export const activeInstanceStatus = (state: any): ClientStatus => {
return state.client.status;
}
export const { sessionExpired } = clientSlice.actions;
export default clientSlice.reducer;

View File

@ -0,0 +1,12 @@
import { configureStore } from '@reduxjs/toolkit';
import clientReducer from './clientSlice';
// Create Service object...
const store = configureStore({
reducer: {
client: clientReducer
}
});
export default store;

View File

@ -1,12 +0,0 @@
import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
import serviceReducer from './reducers/serviceSlice';
// Create Service object...
const store = configureStore({
reducer: {
service: serviceReducer
}
});
export default store;