Redux initial refactor

This commit is contained in:
Paulo Gustavo Veiga 2020-12-24 17:39:06 -08:00
parent fed51ad5a9
commit 60fd57b094
12 changed files with 436 additions and 170 deletions

View File

@ -1,4 +1,55 @@
{ {
"action.cancel-button": {
"defaultMessage": "Cancel"
},
"action.delete": {
"defaultMessage": "Delete"
},
"action.delete-button": {
"defaultMessage": "Delete"
},
"action.delete-description": {
"defaultMessage": "Deleted mindmap can not be recovered. Do you want to continue ?."
},
"action.delete-title": {
"defaultMessage": "Delete"
},
"action.duplicate": {
"defaultMessage": "Duplicate"
},
"action.export": {
"defaultMessage": "Export"
},
"action.open": {
"defaultMessage": "Open"
},
"action.print": {
"defaultMessage": "Print"
},
"action.publish": {
"defaultMessage": "Publish"
},
"action.rename": {
"defaultMessage": "Rename"
},
"action.rename-button": {
"defaultMessage": "Rename"
},
"action.rename-description": {
"defaultMessage": "Please, update the name and description for your mindmap."
},
"action.rename-description-placeholder": {
"defaultMessage": "Description"
},
"action.rename-name-placeholder": {
"defaultMessage": "Name"
},
"action.rename-title": {
"defaultMessage": "Rename"
},
"action.share": {
"defaultMessage": "Info"
},
"common.wait": { "common.wait": {
"defaultMessage": "Please wait ..." "defaultMessage": "Please wait ..."
}, },
@ -57,7 +108,7 @@
"defaultMessage": "Email" "defaultMessage": "Email"
}, },
"login.error": { "login.error": {
"defaultMessage": "The login.email address or login.password you entered is not valid." "defaultMessage": "The email address or password you entered is not valid."
}, },
"login.forgotpwd": { "login.forgotpwd": {
"defaultMessage": "Forgot Password ?" "defaultMessage": "Forgot Password ?"
@ -82,7 +133,7 @@
"defaultMessage": "Welcome" "defaultMessage": "Welcome"
}, },
"login.userinactive": { "login.userinactive": {
"defaultMessage": "Sorry, your account has not been activated yet. You'll receive a notification login.email when it becomes active. Stay tuned!." "defaultMessage": "Sorry, your account has not been activated yet. You'll receive a notification email when it becomes active. Stay tuned!."
}, },
"registration.desc": { "registration.desc": {
"defaultMessage": "Signing up is free and just take a moment" "defaultMessage": "Signing up is free and just take a moment"

View File

@ -48,13 +48,15 @@
"@material-ui/core": "^4.11.2", "@material-ui/core": "^4.11.2",
"@material-ui/icons": "^4.11.2", "@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.57", "@material-ui/lab": "^4.0.0-alpha.57",
"@reduxjs/toolkit": "^1.5.0",
"@types/axios": "^0.14.0",
"@types/react-google-recaptcha": "^2.1.0", "@types/react-google-recaptcha": "^2.1.0",
"axios": "^0.21.0",
"react": "^17.0.1", "react": "^17.0.1",
"react-dom": "^17.0.1", "react-dom": "^17.0.1",
"react-google-recaptcha": "^2.1.0", "react-google-recaptcha": "^2.1.0",
"react-intl": "^5.10.6", "react-intl": "^5.10.6",
"react-redux": "^7.2.2",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"styled-components": "^5.2.1" "styled-components": "^5.2.1"
} }
} }

View File

@ -1,5 +1,4 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Service, RestService } from './services/Service';
import { IntlProvider } from 'react-intl' import { IntlProvider } from 'react-intl'
import { GlobalStyle } from './theme/global-style'; import { GlobalStyle } from './theme/global-style';
@ -7,7 +6,8 @@ import RegistrationSuccessPage from './components/registration-success-page';
import ForgotPasswordSuccessPage from './components/forgot-password-success-page'; 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 { import {
Route, Route,
@ -17,6 +17,7 @@ import {
} from 'react-router-dom'; } from 'react-router-dom';
import { ForgotPasswordPage } from './components/forgot-password-page'; import { ForgotPasswordPage } from './components/forgot-password-page';
import { Provider } from 'react-redux';
function loadLocaleData(language: string) { function loadLocaleData(language: string) {
switch (language) { switch (language) {
@ -25,13 +26,13 @@ function loadLocaleData(language: string) {
default: default:
return require('./compiled-lang/en.json') return require('./compiled-lang/en.json')
} }
} }
type AppProps = { type AppProps = {
baseRestUrl: string; baseRestUrl: string;
} }
const App = (props: AppProps) => { const App = () => {
const [messages, setMessages] = useState(undefined); const [messages, setMessages] = useState(undefined);
// Boostrap i18n ... // Boostrap i18n ...
@ -50,33 +51,32 @@ const App = (props: AppProps) => {
fetchData(); fetchData();
}, []); }, []);
// Create Service object...
const service: Service = new RestService(props.baseRestUrl, () => { console.log("401 error") });
return messages ? ( return messages ? (
<IntlProvider locale={locale} defaultLocale='en' messages={messages}> <Provider store={store}>
<GlobalStyle /> <IntlProvider locale={locale} defaultLocale='en' messages={messages}>
<Router> <GlobalStyle />
<Switch> <Router>
<Route exact path="/"> <Switch>
<Redirect to="/c/login" /> <Route exact path="/">
</Route> <Redirect to="/c/login" />
<Route path="/c/login" component={LoginPage} /> </Route>
<Route path="/c/registration"> <Route path="/c/login" component={LoginPage} />
<RegistationPage service={service} /> <Route path="/c/registration">
</Route> <RegistationPage />
<Route path="/c/registration-success" component={RegistrationSuccessPage} /> </Route>
<Route path="/c/forgot-password"> <Route path="/c/registration-success" component={RegistrationSuccessPage} />
<ForgotPasswordPage service={service} /> <Route path="/c/forgot-password">
</Route> <ForgotPasswordPage />
<Route path="/c/forgot-password-success" component={ForgotPasswordSuccessPage} /> </Route>
<Route path="/c/maps/"> <Route path="/c/forgot-password-success" component={ForgotPasswordSuccessPage} />
<MapsPage service={service} /> <Route path="/c/maps/">
</Route> <MapsPage />
</Switch> </Route>
</Router> </Switch>
</IntlProvider> </Router>
</IntlProvider>
</Provider>
) : <div>Loading ... </div> ) : <div>Loading ... </div>
} }

View File

@ -1,4 +1,106 @@
{ {
"action.cancel-button": [
{
"type": 0,
"value": "Cancel"
}
],
"action.delete": [
{
"type": 0,
"value": "Delete"
}
],
"action.delete-button": [
{
"type": 0,
"value": "Delete"
}
],
"action.delete-description": [
{
"type": 0,
"value": "Deleted mindmap can not be recovered. Do you want to continue ?."
}
],
"action.delete-title": [
{
"type": 0,
"value": "Delete"
}
],
"action.duplicate": [
{
"type": 0,
"value": "Duplicate"
}
],
"action.export": [
{
"type": 0,
"value": "Export"
}
],
"action.open": [
{
"type": 0,
"value": "Open"
}
],
"action.print": [
{
"type": 0,
"value": "Print"
}
],
"action.publish": [
{
"type": 0,
"value": "Publish"
}
],
"action.rename": [
{
"type": 0,
"value": "Rename"
}
],
"action.rename-button": [
{
"type": 0,
"value": "Rename"
}
],
"action.rename-description": [
{
"type": 0,
"value": "Please, update the name and description for your mindmap."
}
],
"action.rename-description-placeholder": [
{
"type": 0,
"value": "Description"
}
],
"action.rename-name-placeholder": [
{
"type": 0,
"value": "Name"
}
],
"action.rename-title": [
{
"type": 0,
"value": "Rename"
}
],
"action.share": [
{
"type": 0,
"value": "Info"
}
],
"common.wait": [ "common.wait": [
{ {
"type": 0, "type": 0,
@ -116,7 +218,7 @@
"login.error": [ "login.error": [
{ {
"type": 0, "type": 0,
"value": "The login.email address or login.password you entered is not valid." "value": "The email address or password you entered is not valid."
} }
], ],
"login.forgotpwd": [ "login.forgotpwd": [
@ -164,7 +266,7 @@
"login.userinactive": [ "login.userinactive": [
{ {
"type": 0, "type": 0,
"value": "Sorry, your account has not been activated yet. You'll receive a notification login.email when it becomes active. Stay tuned!." "value": "Sorry, your account has not been activated yet. You'll receive a notification email when it becomes active. Stay tuned!."
} }
], ],
"registration.desc": [ "registration.desc": [

View File

@ -13,7 +13,7 @@ type ForgotPasswordProps = {
email: string; email: string;
} }
const ForgotPassword = (props: ServiceProps) => { const ForgotPassword = () => {
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [errorMsg, setErrorMsg] = useState(''); const [errorMsg, setErrorMsg] = useState('');
const [disableButton, setDisableButton] = useState(false); const [disableButton, setDisableButton] = useState(false);
@ -26,20 +26,20 @@ const ForgotPassword = (props: ServiceProps) => {
setDisableButton(true); setDisableButton(true);
// Call Service ... // Call Service ...
const service = props.service; // const service = props.service;
service.resetPassword(email) // service.resetPassword(email)
.then(() => { // .then(() => {
history.push("/c/forgot-password-success"); // history.push("/c/forgot-password-success");
}).catch((error: ErrorInfo) => { // }).catch((error: ErrorInfo) => {
setErrorMsg(error.msg ? error.msg : ''); // setErrorMsg(error.msg ? error.msg : '');
setDisableButton(false); // setDisableButton(false);
}); // });
} }
return ( return (
<PageContent> <PageContent>
<h1><FormattedMessage id="forgot.title" defaultMessage="Reset your password"/></h1> <h1><FormattedMessage id="forgot.title" defaultMessage="Reset your password" /></h1>
<p><FormattedMessage id="forgot.desc" defaultMessage="We will send you an email to reset your password"/></p> <p><FormattedMessage id="forgot.desc" defaultMessage="We will send you an email to reset your password" /></p>
<form onSubmit={handleOnSubmit}> <form onSubmit={handleOnSubmit}>
<input type="email" name="email" onChange={e => setEmail(e.target.value)} placeholder={intl.formatMessage({ id: "forgot.email", defaultMessage: "Email" })} required={true} autoComplete="email" /> <input type="email" name="email" onChange={e => setEmail(e.target.value)} placeholder={intl.formatMessage({ id: "forgot.email", defaultMessage: "Email" })} required={true} autoComplete="email" />
@ -55,7 +55,7 @@ const ForgotPassword = (props: ServiceProps) => {
type ServiceProps = { type ServiceProps = {
service: Service service: Service
} }
const ForgotPasswordPage = (props: ServiceProps) => { const ForgotPasswordPage = () => {
useEffect(() => { useEffect(() => {
document.title = 'Reset Password | WiseMapping'; document.title = 'Reset Password | WiseMapping';
@ -64,7 +64,7 @@ const ForgotPasswordPage = (props: ServiceProps) => {
return ( return (
<div> <div>
<Header type='only-signin' /> <Header type='only-signin' />
<ForgotPassword service={props.service} /> <ForgotPassword />
<Footer /> <Footer />
</div> </div>
); );

View File

@ -6,26 +6,41 @@ 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 { FormattedMessage, useIntl } from 'react-intl'; import { FormattedMessage, useIntl } from 'react-intl';
import { BasicMapInfo, ErrorInfo, Service } from '../../services/Service'; import { ErrorInfo, MapInfo, Service } from '../../services/Service';
import { FormControl, TextField } from '@material-ui/core'; import { FormControl, TextField } from '@material-ui/core';
import { Alert, AlertTitle } from '@material-ui/lab'; import { Alert, AlertTitle } from '@material-ui/lab';
import { useDispatch, useSelector } from 'react-redux';
import {
allMaps,
remove,
rename
} from '../../reducers/mapsListSlice'
import { Description } from '@material-ui/icons';
type DialogProps = { type DialogProps = {
open: boolean, open: boolean,
mapId: number, mapId: number,
onClose: (reload: boolean) => void, onClose: () => void
service: Service
} }
function DeleteConfirmDialog(props: DialogProps) { export type BasicMapInfo = {
name: string;
description: string | undefined;
}
function DeleteDialog(props: DialogProps) {
const dispatch = useDispatch()
const mapId = props.mapId;
const mapInfo: MapInfo | undefined = useSelector(allMaps).
find(m => m.id == mapId);
const handleOnClose = (action: 'accept' | undefined): void => { const handleOnClose = (action: 'accept' | undefined): void => {
let result = false; if (action == 'accept' && mapInfo) {
if (action == 'accept') { dispatch(remove({ id: mapId }))
props.service.deleteMap(props.mapId);
result = true;
} }
props.onClose(result); props.onClose();
}; };
return ( return (
@ -35,12 +50,10 @@ function DeleteConfirmDialog(props: DialogProps) {
onClose={() => handleOnClose(undefined)} > onClose={() => handleOnClose(undefined)} >
<DialogTitle><FormattedMessage id="action.delete-title" defaultMessage="Delete" /></DialogTitle> <DialogTitle><FormattedMessage id="action.delete-title" defaultMessage="Delete" /></DialogTitle>
<DialogContent> <DialogContent>
<DialogContentText> <Alert severity="warning">
<Alert severity="warning"> <AlertTitle>Delete '{mapInfo?.name}'</AlertTitle>
<AlertTitle>Map to be deleted</AlertTitle> <FormattedMessage id="action.delete-description" defaultMessage="Deleted mindmap can not be recovered. Do you want to continue ?." />
<FormattedMessage id="action.delete-description" defaultMessage="Deleted mindmap can not be recovered. Do you want to continue ?." /> </Alert>
</Alert>
</DialogContentText>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={() => handleOnClose('accept')} variant="outlined" color="primary"> <Button onClick={() => handleOnClose('accept')} variant="outlined" color="primary">
@ -54,19 +67,32 @@ function DeleteConfirmDialog(props: DialogProps) {
</div> </div>
); );
} }
export type RenameModel = {
id: number;
name: string;
description?: string;
}
function RenameDialog(props: DialogProps) { function RenameDialog(props: DialogProps) {
const defaultModel: BasicMapInfo = { name: '', description: '' };
const [model, setModel] = React.useState<BasicMapInfo>(defaultModel);
const [errorInfo, setErroInfo] = React.useState<ErrorInfo>();
const defaultModel: RenameModel = { name: '', description: '', id: -1 };
const [model, setModel] = React.useState<RenameModel>(defaultModel);
const [errorInfo, setErroInfo] = React.useState<ErrorInfo>();
const dispatch = useDispatch()
const intl = useIntl(); const intl = useIntl();
useEffect(() => { useEffect(() => {
props.service.loadMapInfo(props.mapId) const mapId: number = props.mapId;
.then((info: BasicMapInfo) => { if (mapId != -1) {
setModel(info); const mapInfo: MapInfo | undefined = useSelector(allMaps)
}) .find(m => m.id == props.mapId);
if (!mapInfo) {
throw "Please, reflesh the page.";
}
setModel({ ...mapInfo });
}
}, []); }, []);
const handleOnClose = (): void => { const handleOnClose = (): void => {
@ -74,21 +100,22 @@ function RenameDialog(props: DialogProps) {
setModel(defaultModel); setModel(defaultModel);
setErroInfo(undefined); setErroInfo(undefined);
props.onClose(false); props.onClose();
}; };
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => { const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
// Stop form submit ... // Stop form submit ...
event.preventDefault(); event.preventDefault();
// Fire rest call ... // Fire rename ...
const mapId = props.mapId; const mapId: number = props.mapId;
props.service.renameMap(mapId, model). try {
then(() => { dispatch(rename({ id: mapId, name: model.name, description: model.description }))
props.onClose(true); handleOnClose();
}).catch((errorInfo: ErrorInfo) => {
setErroInfo(errorInfo) } catch (errorInfo) {
}); setErroInfo(errorInfo)
}
}; };
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => { const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
@ -118,9 +145,9 @@ function RenameDialog(props: DialogProps) {
{Boolean(errorInfo?.msg) ? <Alert severity="error" variant="filled" hidden={!Boolean(errorInfo?.msg)}>{errorInfo?.msg}</Alert> : null} {Boolean(errorInfo?.msg) ? <Alert severity="error" variant="filled" hidden={!Boolean(errorInfo?.msg)}>{errorInfo?.msg}</Alert> : null}
<FormControl margin="normal" required fullWidth> <FormControl margin="normal" required fullWidth>
<TextField name="name" label={intl.formatMessage({ id: "action.rename-name-placeholder", defaultMessage: "Name" })} <TextField name="name" label={intl.formatMessage({ id: "action.rename-name-placeholder", defaultMessage: "Name" })}
value={model.name} onChange={handleOnChange} value={model.name} onChange={handleOnChange}
error={Boolean(errorInfo?.fields?.get('name'))} helperText={errorInfo?.fields?.get('name')} error={Boolean(errorInfo?.fields?.get('name'))} helperText={errorInfo?.fields?.get('name')}
variant="filled" required={true}/> variant="filled" required={true} />
</FormControl> </FormControl>
<FormControl margin="normal" required fullWidth> <FormControl margin="normal" required fullWidth>
<TextField name="description" label={intl.formatMessage({ id: "action.rename-description-placeholder", defaultMessage: "Description" })} value={model.description} onChange={handleOnChange} variant="filled" /> <TextField name="description" label={intl.formatMessage({ id: "action.rename-description-placeholder", defaultMessage: "Description" })} value={model.description} onChange={handleOnChange} variant="filled" />
@ -129,7 +156,7 @@ function RenameDialog(props: DialogProps) {
<DialogActions> <DialogActions>
<Button color="primary" variant="outlined" type="submit"> <Button color="primary" variant="outlined" type="submit">
<FormattedMessage id="action.rename-button" defaultMessage="Rename"/> <FormattedMessage id="action.rename-button" defaultMessage="Rename" />
</Button> </Button>
<Button color="secondary" variant="outlined" autoFocus onClick={handleOnClose}> <Button color="secondary" variant="outlined" autoFocus onClick={handleOnClose}>
@ -145,23 +172,23 @@ function RenameDialog(props: DialogProps) {
export type DialogType = 'share' | 'delete' | 'info' | 'duplicate' | 'export' | 'rename' | 'publish'; export type DialogType = 'share' | 'delete' | 'info' | 'duplicate' | 'export' | 'rename' | 'publish';
type ActionDialogProps = { type ActionDialogProps = {
action: DialogType | undefined, action?: DialogType,
mapId: number, mapId: number,
service: Service, onClose: () => void
onClose: (reload: boolean) => void
} }
const ActionDialog = (props: ActionDialogProps) => { const ActionDialog = (props: ActionDialogProps) => {
const handleOnClose = (): void => {
const handleOnClose = (reload: boolean): void => { props.onClose();
props.onClose(reload);
} }
const mapId = props.mapId;
const action = props.action;
return ( return (
<span> <span>
<DeleteConfirmDialog open={props.action === 'delete'} service={props.service} onClose={handleOnClose} mapId={props.mapId} /> <DeleteDialog open={action === 'delete'} onClose={handleOnClose} mapId={mapId} />
<RenameDialog open={props.action === 'rename'} service={props.service} onClose={handleOnClose} mapId={props.mapId} /> <RenameDialog open={action === 'rename'} onClose={handleOnClose} mapId={mapId} />
</span > </span >
); );
} }

View File

@ -1,5 +1,4 @@
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import { MapInfo, Service } from '../../services/Service'
import { PageContainer, MapsListArea, NavArea, HeaderArea, StyledTableCell } from './styled'; import { PageContainer, MapsListArea, NavArea, HeaderArea, StyledTableCell } from './styled';
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; import { createStyles, makeStyles, Theme } from '@material-ui/core/styles';
@ -24,6 +23,8 @@ import MoreHorizIcon from '@material-ui/icons/MoreHoriz';
import { CSSProperties } from 'react'; import { CSSProperties } from 'react';
import MapActionMenu, { ActionType } from './MapActionMenu'; import MapActionMenu, { ActionType } from './MapActionMenu';
import ActionDialog, { DialogType } from './ActionDialog'; import ActionDialog, { DialogType } from './ActionDialog';
import { useSelector } from 'react-redux';
import { allMaps, MapInfo } from '../../reducers/mapsListSlice';
function descendingComparator<T>(a: T, b: T, orderBy: keyof T) { function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
if (b[orderBy] < a[orderBy]) { if (b[orderBy] < a[orderBy]) {
@ -84,6 +85,7 @@ interface EnhancedTableProps {
function EnhancedTableHead(props: EnhancedTableProps) { function EnhancedTableHead(props: EnhancedTableProps) {
const { classes, onSelectAllClick, order, orderBy, numSelected, rowCount, onRequestSort } = props; const { classes, onSelectAllClick, order, orderBy, numSelected, rowCount, onRequestSort } = props;
const createSortHandler = (property: keyof MapInfo) => (event: React.MouseEvent<unknown>) => { const createSortHandler = (property: keyof MapInfo) => (event: React.MouseEvent<unknown>) => {
onRequestSort(event, property); onRequestSort(event, property);
}; };
@ -209,19 +211,16 @@ type ActionPanelState = {
mapId: number mapId: number
} }
function EnhancedTable(props: ServiceProps) { function EnhancedTable() {
const classes = useStyles(); const classes = useStyles();
const [order, setOrder] = React.useState<Order>('asc'); const [order, setOrder] = React.useState<Order>('asc');
const [orderBy, setOrderBy] = React.useState<keyof MapInfo>('modified'); const [orderBy, setOrderBy] = React.useState<keyof MapInfo>('modified');
const [selected, setSelected] = React.useState<number[]>([]); const [selected, setSelected] = React.useState<number[]>([]);
const [page, setPage] = React.useState(0); const [page, setPage] = React.useState(0);
const [rowsPerPage, setRowsPerPage] = React.useState(5); const [rowsPerPage, setRowsPerPage] = React.useState(5);
const [rows, setRows] = React.useState<MapInfo[]>([]); const mapsInfo: MapInfo[] = useSelector(allMaps);
const [activeRowAction, setActiveRowAction] = React.useState<ActionPanelState | undefined>(undefined); const [activeRowAction, setActiveRowAction] = React.useState<ActionPanelState | undefined>(undefined);
type ActiveDialog = { type ActiveDialog = {
actionType: DialogType; actionType: DialogType;
mapId: number mapId: number
@ -229,13 +228,6 @@ function EnhancedTable(props: ServiceProps) {
}; };
const [activeDialog, setActiveDialog] = React.useState<ActiveDialog | undefined>(undefined); const [activeDialog, setActiveDialog] = React.useState<ActiveDialog | undefined>(undefined);
useEffect(() => {
(async () => {
const mapsInfo = await props.service.fetchAllMaps();
setRows(mapsInfo);
})();
}, []);
const handleRequestSort = (event: React.MouseEvent<unknown>, property: keyof MapInfo) => { const handleRequestSort = (event: React.MouseEvent<unknown>, property: keyof MapInfo) => {
const isAsc = orderBy === property && order === 'asc'; const isAsc = orderBy === property && order === 'asc';
setOrder(isAsc ? 'desc' : 'asc'); setOrder(isAsc ? 'desc' : 'asc');
@ -244,7 +236,7 @@ function EnhancedTable(props: ServiceProps) {
const handleSelectAllClick = (event: React.ChangeEvent<HTMLInputElement>): void => { const handleSelectAllClick = (event: React.ChangeEvent<HTMLInputElement>): void => {
if (event.target.checked) { if (event.target.checked) {
const newSelecteds = rows.map((n) => n.id); const newSelecteds = mapsInfo.map((n) => n.id);
setSelected(newSelecteds); setSelected(newSelecteds);
return; return;
} }
@ -306,7 +298,7 @@ function EnhancedTable(props: ServiceProps) {
const isSelected = (id: number) => selected.indexOf(id) !== -1; const isSelected = (id: number) => selected.indexOf(id) !== -1;
const emptyRows = rowsPerPage - Math.min(rowsPerPage, rows.length - page * rowsPerPage); const emptyRows = rowsPerPage - Math.min(rowsPerPage, mapsInfo.length - page * rowsPerPage);
return ( return (
<div className={classes.root}> <div className={classes.root}>
@ -327,10 +319,10 @@ function EnhancedTable(props: ServiceProps) {
orderBy={orderBy} orderBy={orderBy}
onSelectAllClick={handleSelectAllClick} onSelectAllClick={handleSelectAllClick}
onRequestSort={handleRequestSort} onRequestSort={handleRequestSort}
rowCount={rows.length} rowCount={mapsInfo.length}
/> />
<TableBody> <TableBody>
{stableSort(rows, 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) => {
const isItemSelected = isSelected(row.id); const isItemSelected = isSelected(row.id);
@ -390,7 +382,7 @@ function EnhancedTable(props: ServiceProps) {
<TablePagination <TablePagination
rowsPerPageOptions={[5, 10, 25]} rowsPerPageOptions={[5, 10, 25]}
component="div" component="div"
count={rows.length} count={mapsInfo.length}
rowsPerPage={rowsPerPage} rowsPerPage={rowsPerPage}
page={page} page={page}
onChangePage={handleChangePage} onChangePage={handleChangePage}
@ -399,16 +391,12 @@ function EnhancedTable(props: ServiceProps) {
</Paper> </Paper>
{/* Action Dialog */} {/* Action Dialog */}
<ActionDialog action={activeDialog?.actionType} onClose={(refresh: boolean) => setActiveDialog(undefined)} mapId={activeDialog ? activeDialog.mapId : -1} service={props.service}/> <ActionDialog action={activeDialog?.actionType} onClose={() => setActiveDialog(undefined)} mapId={activeDialog ? activeDialog.mapId : -1}/>
</div> </div>
); );
} }
type ServiceProps = { const MapsPage = () => {
service: Service
}
const MapsPage = (props: ServiceProps) => {
useEffect(() => { useEffect(() => {
document.title = 'Maps | WiseMapping'; document.title = 'Maps | WiseMapping';
@ -423,7 +411,7 @@ const MapsPage = (props: ServiceProps) => {
<h1> Nav </h1> <h1> Nav </h1>
</NavArea> </NavArea>
<MapsListArea> <MapsListArea>
<EnhancedTable service={props.service} /> <EnhancedTable/>
</MapsListArea> </MapsListArea>
</PageContainer> </PageContainer>
); );

View File

@ -12,7 +12,7 @@ import SubmitButton from '../submit-button'
import { StyledReCAPTCHA } from './styled'; import { StyledReCAPTCHA } from './styled';
import { PageContent } from '../../theme/global-style'; import { PageContent } from '../../theme/global-style';
const RegistrationForm = (props: ServiceProps) => { const RegistrationForm = () => {
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [lastname, setLastname] = useState('') const [lastname, setLastname] = useState('')
const [firstname, setFirstname] = useState(''); const [firstname, setFirstname] = useState('');
@ -40,15 +40,15 @@ const RegistrationForm = (props: ServiceProps) => {
}; };
// Call Service ... // Call Service ...
const service = props.service; // const service = props.service;
service.registerNewUser(user) // service.registerNewUser(user)
.then(() => { // .then(() => {
history.push("/c/registration-success") // history.push("/c/registration-success")
}).catch((error: ErrorInfo) => { // }).catch((error: ErrorInfo) => {
const errorMsg = error.msg ? error.msg : undefined; // const errorMsg = error.msg ? error.msg : undefined;
setErrorMsg(errorMsg); // setErrorMsg(errorMsg);
setDisableButton(false); // setDisableButton(false);
}); // });
} }
@ -82,10 +82,7 @@ const RegistrationForm = (props: ServiceProps) => {
); );
} }
type ServiceProps = { const RegistationPage = () => {
service: Service
}
const RegistationPage = (props: ServiceProps) => {
useEffect(() => { useEffect(() => {
document.title = 'Registration | WiseMapping'; document.title = 'Registration | WiseMapping';
@ -94,7 +91,7 @@ const RegistationPage = (props: ServiceProps) => {
return ( return (
<div> <div>
<Header type='only-signin' /> <Header type='only-signin' />
<RegistrationForm service={props.service} /> <RegistrationForm/>
<Footer /> <Footer />
</div> </div>
); );

View File

@ -2,43 +2,13 @@ import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import App from './app'; import App from './app';
import { BrowserRouter as Router } from 'react-router-dom'; import { BrowserRouter as Router } from 'react-router-dom';
import axios from 'axios' import axios from 'axios';
type RutimeConfig = {
apiBaseUrl: string;
}
async function loadRuntimeConfig() {
let result: RutimeConfig | undefined;
await axios.get("runtime-config.json"
).then(response => {
// All was ok, let's sent to success page ...
result = response.data as RutimeConfig;
console.log("Dynamic configuration->" + response.data);
}).catch(e => {
console.log(e)
});
if (!result) {
// Ok, try to create a default configuration relative to the current path ...
console.log("Configuration could not be loaded, falback to default config.")
const location = window.location;
const basePath = location.protocol + "//" + location.host + "/" + location.pathname.split('/')[1]
result = {
apiBaseUrl: basePath
}
}
return result;
}
async function bootstrapApplication() { async function bootstrapApplication() {
const config: RutimeConfig = await loadRuntimeConfig();
ReactDOM.render( ReactDOM.render(
<Router> <Router>
<App baseRestUrl={config.apiBaseUrl} /> <App/>
</Router>, </Router>,
document.getElementById('root') as HTMLElement document.getElementById('root') as HTMLElement
) )

View File

@ -0,0 +1,113 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import axios from 'axios';
import { RestService, Service } from '../services/Service';
function createMapInfo(
id: number,
starred: boolean,
name: string,
labels: [string],
creator: string,
modified: number,
description: string
): MapInfo {
return { id, name, labels, creator, modified, starred, description };
}
const maps = [
createMapInfo(1, true, "El Mapa", [""], "Paulo", 67, ""),
createMapInfo(2, false, "El Mapa2", [""], "Paulo2", 67, ""),
createMapInfo(3, false, "El Mapa3", [""], "Paulo3", 67, "")
];
export type MapInfo = {
id: number;
starred: boolean;
name: string;
labels: [string];
creator: string;
modified: number;
description: string
}
interface MapsListState {
maps: MapInfo[]
}
type RutimeConfig = {
apiBaseUrl: string;
}
async function loadRuntimeConfig() {
let result: RutimeConfig | undefined;
await axios.get("runtime-config.json"
).then(response => {
// All was ok, let's sent to success page ...
result = response.data as RutimeConfig;
console.log("Dynamic configuration->" + response.data);
}).catch(e => {
console.log(e)
});
if (!result) {
// Ok, try to create a default configuration relative to the current path ...
console.log("Configuration could not be loaded, falback to default config.")
const location = window.location;
const basePath = location.protocol + "//" + location.host + "/" + location.pathname.split('/')[1]
result = {
apiBaseUrl: basePath
}
}
return result;
}
const initialState: MapsListState = { maps: maps };
const service: Service = new RestService("", () => { console.log("401 error") });
type RemovePayload = {
id: number;
}
type RenamePayload = {
id: number;
name: string;
description: string | undefined;
}
export const mapsListSlice = createSlice({
name: 'maps',
initialState: initialState,
reducers: {
remove(state, action: PayloadAction<RemovePayload>) {
const maps: MapInfo[] = state.maps as MapInfo[];
const payload = action.payload;
state.maps = maps.filter(map => map.id != payload.id);
},
rename(state, action: PayloadAction<RenamePayload>) {
let maps: MapInfo[] = state.maps as MapInfo[];
const payload = action.payload;
const mapInfo = maps.find(m => m.id == payload.id);
if (mapInfo) {
mapInfo.name = payload.name;
mapInfo.description = payload.description ? payload.description: "";
// Remove and add the new map.
maps = maps.filter(map => map.id != payload.id);
maps.push(mapInfo);
state.maps = maps;
}
}
},
});
export const allMaps = (state: any): MapInfo[] => state.mapsList.maps;
export const { remove, rename } = mapsListSlice.actions
export default mapsListSlice.reducer

View File

@ -1,3 +1,4 @@
import { Description } from '@material-ui/icons'
import axios from 'axios' import axios from 'axios'
export type NewUser = { export type NewUser = {
@ -15,11 +16,12 @@ export type MapInfo = {
labels: [string]; labels: [string];
creator: string; creator: string;
modified: number; modified: number;
description: string;
} }
export type BasicMapInfo = { export type BasicMapInfo = {
name: string; name: string;
description: string; description?: string;
} }
export type FieldError = { export type FieldError = {
@ -86,6 +88,7 @@ class RestService implements Service {
} }
async fetchAllMaps(): Promise<MapInfo[]> { async fetchAllMaps(): Promise<MapInfo[]> {
function createMapInfo( function createMapInfo(
id: number, id: number,
starred: boolean, starred: boolean,
@ -93,14 +96,15 @@ class RestService implements Service {
labels: [string], labels: [string],
creator: string, creator: string,
modified: number, modified: number,
description: string
): MapInfo { ): MapInfo {
return { id, name, labels, creator, modified, starred }; return { id, name, labels, creator, modified, starred, description};
} }
const maps = [ const maps = [
createMapInfo(1, true, "El Mapa", [""], "Paulo", 67,), createMapInfo(1, true, "El Mapa", [""], "Paulo", 67,""),
createMapInfo(2, false, "El Mapa2", [""], "Paulo2", 67), createMapInfo(2, false, "El Mapa2", [""], "Paulo2", 67,""),
createMapInfo(3, false, "El Mapa3", [""], "Paulo3", 67) createMapInfo(3, false, "El Mapa3", [""], "Paulo3", 67,"")
]; ];
return Promise.resolve(maps); return Promise.resolve(maps);

View File

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