More fields in React-admin login page - reactjs

I need to insert one more field in the Login page, therefore, one other entry in the JSON login request and, although I managed to insert the field in the login page (as a FormControl with Selects), I'm having trouble in inserting the selected option into the JSON request.
login.js:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { propTypes, reduxForm, Field } from 'redux-form';
import { connect } from 'react-redux';
import compose from 'recompose/compose';
import Avatar from '#material-ui/core/Avatar';
import Button from '#material-ui/core/Button';
import Card from '#material-ui/core/Card';
import CardActions from '#material-ui/core/CardActions';
import CircularProgress from '#material-ui/core/CircularProgress';
import TextField from '#material-ui/core/TextField';
import { withStyles } from '#material-ui/core/styles';
import LockIcon from '#material-ui/icons/Lock';
import { MenuItem, Select, FormControl, InputLabel } from '#material-ui/core';
import { Notification, translate, userLogin } from 'react-admin';
import InstitutionSelect from "./InstitutionSelect";
const styles = theme => ({
main: {
display: 'flex',
flexDirection: 'column',
minHeight: '100vh',
alignItems: 'center',
justifyContent: 'flex-start',
background: 'url(https://source.unsplash.com/random/1600x900)',
backgroundRepeat: 'no-repeat',
backgroundSize: 'cover',
},
card: {
minWidth: 300,
marginTop: '6em',
},
avatar: {
margin: '1em',
display: 'flex',
justifyContent: 'center',
},
icon: {
backgroundColor: theme.palette.secondary.main,
},
hint: {
marginTop: '1em',
display: 'flex',
justifyContent: 'center',
color: theme.palette.grey[500],
},
form: {
padding: '0 1em 1em 1em',
},
input: {
marginTop: '1em',
},
actions: {
padding: '0 1em 1em 1em',
},
});
// see http://redux-form.com/6.4.3/examples/material-ui/
const renderInput = ({
meta: { touched, error } = {},
input: { ...inputProps },
...props
}) => (
<TextField
error={!!(touched && error)}
helperText={touched && error}
{...inputProps}
{...props}
fullWidth
/>
);
class Login extends Component {
login = auth =>
this.props.userLogin(
auth,
this.props.location.state
? this.props.location.state.nextPathname
: '/'
);
render() {
const { classes, handleSubmit, isLoading, translate } = this.props;
return (
<div className={classes.main}>
<Card className={classes.card}>
<div className={classes.avatar}>
<Avatar className={classes.icon}>
<LockIcon />
</Avatar>
</div>
<form onSubmit={handleSubmit(this.login)}>
<div className={classes.form}>
<div className={classes.input}>
<InstitutionSelect
name="institution"
disabled={isLoading}
/>
</div>
<div className={classes.input}>
<Field
name="username"
component={renderInput}
label={translate('ra.auth.username')}
disabled={isLoading}
/>
</div>
<div className={classes.input}>
<Field
name="password"
component={renderInput}
label={translate('ra.auth.password')}
type="password"
disabled={isLoading}
/>
</div>
</div>
<CardActions className={classes.actions}>
<Button
variant="raised"
type="submit"
color="primary"
disabled={isLoading}
className={classes.button}
fullWidth
>
{isLoading && (
<CircularProgress size={25} thickness={2} />
)}
{translate('ra.auth.sign_in')}
</Button>
</CardActions>
</form>
</Card>
<Notification />
</div>
);
}
}
Login.propTypes = {
...propTypes,
authProvider: PropTypes.func,
classes: PropTypes.object,
previousRoute: PropTypes.string,
translate: PropTypes.func.isRequired,
// Insert Institution here?
userLogin: PropTypes.func.isRequired,
};
const mapStateToProps = state => ({ isLoading: state.admin.loading > 0 });
const enhance = compose(
translate,
reduxForm({
form: 'signIn',
validate: (values, props) => {
const errors = {};
const { translate } = props;
if (!values.institution) {
errors.institution = translate('ra.validation.required');
}
if (!values.username) {
errors.username = translate('ra.validation.required');
}
if (!values.password) {
errors.password = translate('ra.validation.required');
}
return errors;
},
}),
connect(
mapStateToProps,
{ userLogin }
),
withStyles(styles)
);
export default enhance(Login);
authProvider.js:
import { AUTH_LOGIN, AUTH_LOGOUT, AUTH_ERROR, AUTH_CHECK } from 'react-admin';
export default (type, params) => {
// called when the user attempts to log in
if (type === AUTH_LOGIN) {
const { institution, username, password } = params;
localStorage.setItem('username', username);
const request = new Request('194.117.29.119:3000/api/users/login', {
method: 'POST',
body: JSON.stringify({
institution,
username,
password
}),
headers: new Headers({ 'Content-Type': 'application/json' }),
})
return fetch(request)
.then(response => {
if (response.status < 200 || response.status >= 300) {
throw new Error(response.statusText);
}
return response.json();
})
.then(({ token }) => {
localStorage.setItem('token', token);
});
}
// called when the user clicks on the logout button
if (type === AUTH_LOGOUT) {
localStorage.removeItem('username');
return Promise.resolve();
}
// called when the API returns an error
if (type === AUTH_ERROR) {
const { status } = params;
if (status === 401 || status === 403) {
localStorage.removeItem('username');
return Promise.reject();
}
return Promise.resolve();
}
// called when the user navigates to a new location
if (type === AUTH_CHECK) {
return localStorage.getItem('username')
? Promise.resolve()
: Promise.reject();
}
return Promise.reject('Unknown method');
};
InstitutionSelect.js:
import React, { Component } from "react";
import MenuItem from "#material-ui/core/MenuItem";
import Select from "#material-ui/core/Select";
import { withStyles } from '#material-ui/core/styles';
import MuiThemeProvider from "#material-ui/core/styles/MuiThemeProvider";
import { InputLabel, FormControl } from "#material-ui/core";
const styles = theme => ({
fullWidth: {
width: "100%"
}
});
class InstitutionSelect extends Component {
constructor(props) {
super(props);
this.state = {
selectOptions: [
{
value: "blablabla",
id: "1"
},
],
selectedValue: "",
};
}
renderSelectOptions = () => {
return this.state.selectOptions.map((option, i) => (
<MenuItem key={option.id} value={option.id}>
{option.value}
</MenuItem>
));
};
handleChange = event => {
this.setState({ selectedValue: event.target.value });
};
render() {
const { classes } = this.props;
return (
<MuiThemeProvider>
<FormControl className={classes.fullWidth}>
<InputLabel>Institution</InputLabel>
<Select value={this.state.selectedValue} onChange={this.handleChange} >
{this.renderSelectOptions()}
</Select>
</FormControl>
</MuiThemeProvider>
);
}
}
export default withStyles(styles)(InstitutionSelect);

Related

Can i read uploaded excel sheet file and loop through its items to update a SharePoint site column choices inside our SPFx

We have the following Panel to manually add/delete choices inside a SharePoint column named Category:-
and here is the related .tsx code for the above Panel:-
import * as React from "react";
import {
Stack,
ProgressIndicator,
Panel,
PanelType,
DefaultButton,
AnimationStyles,
mergeStyles,
TextField,
PrimaryButton,
Dropdown,
IDropdownOption,
MessageBar,
MessageBarType,
Label,
Text,
ILabelStyles,
Link,
IconButton,
} from "office-ui-fabric-react";
import { _copyAndSort } from "../../controls/helpers";
import * as moment from "moment";
import * as strings from "DocumentsViewerWebPartStrings";
import { IReadonlyTheme } from "#microsoft/sp-component-base";
import Dropzone from "../../controls/DropzoneExport";
import { IDocument } from "../../models/IDocument";
export interface ICategoriesPanelProps {
themeVariant: IReadonlyTheme | undefined;
showPanel: boolean;
hidePanel: () => void;
categories: string[];
addCategory: (category: string) => void;
removeCategory: (category: string) => void;
castFiletoIDoc: (file: File) => IDocument;
}
export interface ICategoriesPanelState {
busy: boolean;
newCategory: string;
uploadPlaceholders: IDocument[];
}
export default class CategoriesPanel extends React.Component<ICategoriesPanelProps, ICategoriesPanelState> {
constructor(props: ICategoriesPanelProps) {
super(props);
this.state = { busy: true, newCategory: null ,uploadPlaceholders: []};
}
public componentDidMount(): void {
this.setState({ busy: false });
}
private handleNewCategoryFieldChange = (e, newValue: string) => {
this.setState({ newCategory: newValue });
};
private add = async () => {
this.setState({ busy: true });
await this.props.addCategory(this.state.newCategory);
this.setState({ busy: false, newCategory: null });
};
private remove = async (category: string) => {
this.setState({ busy: true });
if (category) {
this.props.removeCategory(category);
}
this.setState({ busy: false });
};
private onDrop = (moreFiles) => {
const placeholders = [...this.state.uploadPlaceholders];
moreFiles.forEach((file, i) => {
const idoc = this.props.castFiletoIDoc(file);
placeholders.push({
...idoc,
key: i.toString(),
});
});
this.setState({ uploadPlaceholders: [...placeholders] });
// Upload the file
//this.props.uploadFolderIcon(moreFiles[0], this.props.folder);
};
private removeDocument = (document: IDocument) => {
this.setState({ uploadPlaceholders: [] });
};
public render(): React.ReactElement<ICategoriesPanelProps> {
const appearingStyle = mergeStyles(AnimationStyles.scaleDownIn100);
return (
<Panel
headerText={strings.ManageCategories}
type={PanelType.medium}
isOpen={this.props.showPanel}
onDismiss={this.props.hidePanel}
// You MUST provide this prop! Otherwise screen readers will just say "button" with no label.
closeButtonAriaLabel={strings.Close}
isBlocking={true}
hasCloseButton={true}
>
<Stack tokens={{ childrenGap: 15 }}>
<Stack.Item>
<Dropzone
themeVariant={this.props.themeVariant}
onDrop={this.onDrop}
uploadPlaceholders={this.state.uploadPlaceholders}
removeDocument={this.removeDocument}
/>
{/* <PrimaryButton
text={strings.StartUpload}
onClick={this.uploadDocuments}
disabled={this.state.uploading || this.state.uploadFiles.length === 0}
/> */}
</Stack.Item>
<Stack.Item align="end">
{this.props.categories.length} {strings.Categories.toLowerCase()}
</Stack.Item>
<Stack.Item>
<Stack tokens={{ childrenGap: 24 }}>
<Stack.Item
styles={{
root: {
padding: "10px 20px",
backgroundColor: this.props.themeVariant.palette.neutralLight,
},
}}
>
<Stack tokens={{ childrenGap: 4 }}>
<Stack.Item>
{this.props.categories.map((category, i) => (
<Stack
tokens={{ childrenGap: 6 }}
horizontal
horizontalAlign="space-between"
styles={{
root: {
alignItems: "center",
},
}}
className={appearingStyle}
>
<Stack.Item>{category}</Stack.Item>
<IconButton
iconProps={{ iconName: "Delete" }}
title={`${strings.Remove} ${category}`}
onClick={() => this.remove(category)}
disabled={this.state.busy}
/>
</Stack>
))}
</Stack.Item>
<Stack.Item>
<Stack
tokens={{ childrenGap: 6 }}
horizontal
horizontalAlign="space-between"
styles={{
root: {
alignItems: "center",
},
}}
className={appearingStyle}
>
<Stack.Item>
<TextField
label={strings.AddNewCategory}
name="newCategory"
value={this.state.newCategory}
onChange={this.handleNewCategoryFieldChange}
disabled={this.state.busy}
styles={{ root: { width: 300 } }}
/>
</Stack.Item>
<IconButton
iconProps={{ iconName: "Add" }}
title={`${strings.Add} ${this.state.newCategory}`}
onClick={this.add}
disabled={this.state.busy}
/>
</Stack>
</Stack.Item>
</Stack>
</Stack.Item>
</Stack>
</Stack.Item>
</Stack>
</Panel>
);
}
}
currently the SPFx allow to manually add/edit the choices, but my question is how we can read the uploaded excel sheet file (which will contain the choices) inside the DropZone, loop through the choices >> remove existing choices and add the ones inside the sheet? Can anyone advice please?
Here is the DropZoneExport.tsx:-
import * as React from "react";
import { Stack, IStyle } from "office-ui-fabric-react";
import { IReadonlyTheme } from "#microsoft/sp-component-base";
import * as strings from "DocumentsViewerWebPartStrings";
import { IDocument } from "../models/IDocument";
import DocumentRow from "./DocumentRow";
import { useCallback, useState } from "react";
import { useDropzone } from "react-dropzone";
export interface IDropzoneExportProps {
themeVariant: IReadonlyTheme | undefined;
onDrop: (files) => void;
uploadPlaceholders: IDocument[];
removeDocument: (document: IDocument) => void;
}
export interface IDocumentsDropzoneExportState {
files: any[];
}
export default function DropzoneExport(props: IDropzoneExportProps) {
// https://www.npmjs.com/package/react-dropzone
const onDrop = useCallback(async (acceptedFiles) => {
// Do something with the files
console.log("something dropped");
props.onDrop(acceptedFiles);
}, []);
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop,
maxFiles: 1,
accept: {
"text/csv*": [".csv"],
//acceptedFiles={[".csv, text/csv, application/vnd.ms-excel, application/csv, text/x-csv, application/x-csv, text/comma-separated-values, text/x-comma-separated-values"]}
},
});
const dropBoxStyle: IStyle = {
border: "1px dashed",
borderColor: props.themeVariant.semanticColors.inputBorder,
padding: "0.5rem 1rem",
marginBottom: ".5rem",
backgroundColor: props.themeVariant.palette.neutralQuaternary,
};
return (
<Stack>
<Stack.Item styles={{ root: dropBoxStyle }}>
<div {...getRootProps()} style={{ outline: "none" }}>
<input {...getInputProps()} />
{isDragActive ? <p>{strings.Item_DropHere}</p> : <p>{strings.Item_DropInfo}</p>}
<div
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
{props.uploadPlaceholders.map((placeholder) => {
return <DocumentRow document={placeholder} themeVariant={props.themeVariant} removeDocument={props.removeDocument} />;
})}
</div>
</div>
</Stack.Item>
</Stack>
);
}
You can do that, but you may need to use a third-party library to read the excel sheet in the browser. A common solution for that is sheetjs library. There are no built-in helpers in the SPFx framework to parse Excel files, as far as I know.
But you should be able to install sheetjs using npm and then use it by import.

Unable to get redux-form formValueSelector working correctly

I'm having difficulty getting formValueSelector from redux-form (v8.3) to work. I've tried all variants I can find with the selector inside and outside ```mapStateToProps``.
In this component, I am getting the value for the field 'radius' (number) and reusing it to resize the Leaflet circle on the map.
import React, { Fragment, useRef } from "react";
import L from "leaflet";
import { connect, useDispatch } from "react-redux";
import { change } from "redux-form";
import { useLeaflet } from "react-leaflet";
import AdjustIcon from '#material-ui/icons/Adjust';
import MapButton from "./MapButton";
import { Grid, Typography, Popover, Button } from '#material-ui/core'
import { makeStyles } from '#material-ui/core/styles'
import { RenderTextField, } from 'components/form/FormFields'
import { Field } from 'redux-form'
import { firestoreConnect } from 'react-redux-firebase'
import { compose } from 'redux'
import { reduxForm, formValueSelector } from 'redux-form'
import { networkStatus } from 'actions/onlineStatus'
import { handleFormAdd } from 'actions/formActions'
import { withSnackbar } from 'notistack'
const useStyles = makeStyles(theme => ({
legend: {
padding: theme.spacing(1, 0.5),
borderRadius: '4px',
boxShadow: '0 1px 5px rgba(0, 0, 0, 0.65)'
},
list: {
padding: theme.spacing(1)
},
row: {
verticalAlign: 'middle',
margin: theme.spacing(0, 0, 0.5, 0),
height: '24px',
fontSize: '13px'
}
}))
const formName = 'geopoints'
function Circle(props) {
const {
handleSubmit,
radius,
pristine,
reset,
submitting
} = props
console.log(radius)
const onSubmit = formProps => {
const data = Object.assign(
{
author: props._userUID,
authorName: props._authorName,
createdAt: new Date().getTime()
},
formProps
)
props
.networkStatus()
.then(() => {
props.handleFormAdd(
{
formName: formName,
formData: data
},
'ONLINE'
)
})
.catch(() => {
props.handleFormAdd(
{
formName: formName,
formData: data
},
'OFFLINE'
)
})
}
const classes = useStyles()
const { map } = useLeaflet();
const [anchorEl, setAnchorEl] = React.useState(null)
const dispatch = useDispatch();
const layerRef = useRef(L.featureGroup());
const handleClick = event => {
layerRef.current.clearLayers()
setAnchorEl(event.currentTarget)
dispatch(change(formName, "latitude", map.getCenter().lat));
dispatch(change(formName, "longitude", map.getCenter().lng));
L.circle(
[map.getCenter().lat, map.getCenter().lng],
radius, // this is where I am trying to use the value for 'radius'
{
weight: 1,
color: "blue",
fillColor: "blue",
fillOpacity: 0.2
}).addTo(layerRef.current);
layerRef.current.addTo(map);
}
const handleClose = () => {
layerRef.current.clearLayers()
reset()
setAnchorEl(null)
}
const open = Boolean(anchorEl)
return (
<Fragment>
<MapButton
title={'Add an area circle'}
onClick={handleClick}
right
bottom={148}
>
<AdjustIcon
fontSize="small"
/>
</MapButton>
<Popover
className={classes.legend}
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: 'top',
horizontal: 'left'
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center'
}}
>
<Grid
className={classes.list}
container
direction="column"
justify="flex-start"
alignItems="center"
>
<Grid item>
<Typography variant="body2" align="center" gutterBottom>
Add Area Marker
</Typography>
</Grid>
<form onSubmit={handleSubmit(onSubmit)}>
<Field
name="radius"
label="Radius"
type="number"
component={RenderTextField}
/>
Radius: {radius} // trying to render the value here
<Field
name="description"
label="Description"
type="text"
component={RenderTextField}
/>
<Button variant="container" type="submit" disabled={pristine || submitting}>Save</Button>
<Button type="button" disabled={pristine || submitting} onClick={handleClose}>Cancel</Button>
</form>
</Grid>
</Popover>
</Fragment>
);
}
const selector = formValueSelector(formName)
const mapStateToProps = ({
firebase: { auth },
network: { status },
state
}) => ({
_userUID: auth.uid,
_authorName: auth.displayName,
_status: status,
radius: selector(state, 'radius')
})
export default compose(
connect(
mapStateToProps,
{ networkStatus, handleFormAdd }
),
reduxForm({
form: formName,
destroyOnUnmount: true,
forceUnregisterOnUnmount: true,
enableReinitialize: true
}),
firestoreConnect()
)(withSnackbar(Circle))
Thanks in advance for any advice or solutions

Material UI TextField Not Showing Value

I have a form in react where I'm using a Material UI TextField. I want a value present in the TextField and I can see that it is being set when I use the Inspect option of Google Chrome. In addition to that, I see that the type="hidden". I have changed this to type="text" and to no avail, the value is still not presented. Curious if anyone here has any understanding as to why this would happen. Below is the primary code that is causing the problem:
<TextField
name="Property"
select
fullWidth
margin="normal"
className={clsx(selectInputStyle.margin, selectInputStyle.textField, selectInputStyle.root)}
value={viewProperties[index].name}
onChange={this.handleSelectChange}
>
{propertyKeys.map((key, index) => (
<MenuItem value={key} key={index}>
{key}
</MenuItem>
))}
</TextField>
Here is the full code file just for a complete context of what is going on.
import React, { Component } from 'react';
import { Container, Form, Button, Row, Col, Nav, NavItem, NavLink, Input, FormGroup } from 'reactstrap';
import { connect } from 'react-redux';
import { reduxForm, FieldArray, arrayRemoveAll } from 'redux-form/immutable';
import * as Immutable from 'immutable';
import _ from 'lodash';
import { bindActionCreators } from 'redux';
import BlockUi from 'react-block-ui';
import MaterialButton from '#material-ui/core/Button';
import DeleteIcon from '#material-ui/icons/Delete';
import { makeStyles } from '#material-ui/core/styles';
import AppBar from '#material-ui/core/AppBar';
import Tabs from '#material-ui/core/Tabs';
import Tab from '#material-ui/core/Tab';
import MenuItem from '#material-ui/core/MenuItem';
import Select from '#material-ui/core/Select';
import InputMaterial from '#material-ui/core/Input';
import FormControl from '#material-ui/core/FormControl';
import TextField from '#material-ui/core/TextField';
import OutlinedInput from '#material-ui/core/OutlinedInput';
import clsx from 'clsx';
import { AvText, AvSelect } from '../forms/components';
import { showNotification, loadPayerProperties, updatePayerProperties } from '../actions';
class PayerPropertiesEditor extends Component {
constructor(props) {
super(props);
this.uploadRef = React.createRef();
this.state = {
errors: [],
refeshProperties: false,
blocking: false
};
this.showButton = false;
this.divPadding = { padding: '20px' };
this.doSubmit = this.doSubmit.bind(this);
this.handleInvalidSubmit = this.handleInvalidSubmit.bind(this);
this.renderProperties = this.renderProperties.bind(this);
this.handleSelectChange = this.handleSelectChange.bind(this);
this.useStyles = makeStyles(theme => ({
button: {
margin: theme.spacing(1)
},
leftIcon: {
marginRight: theme.spacing(1)
},
rightIcon: {
marginLeft: theme.spacing(1)
},
iconSmall: {
fontSize: 20
},
root: {
display: 'flex',
flexWrap: 'wrap'
},
formControl: {
margin: theme.spacing(1),
minWidth: 120
},
selectEmpty: {
marginTop: theme.spacing(2)
},
container: {
display: 'flex',
flexWrap: 'wrap'
},
input: {
margin: theme.spacing(1)
}
}));
}
componentDidMount() {
this.setState({ view: 'payer' });
}
componentDidUpdate(prevProps) {
const { loadPayerProperties } = this.props;
if (this.state.refeshProperties) {
this.props.arrayRemoveAll('payerPropertiesEditorForm', 'properties');
loadPayerProperties();
this.setState({ refeshProperties: false });
this.setState({ blocking: false });
this.showButton = false;
}
if (!prevProps.properties && this.props.properties) {
this.props.change('properties', Immutable.fromJS(this.props.properties));
}
}
doSubmit(values) {
const { updatePayerProperties } = this.props;
return new Promise(resolve => {
this.setState({
blocking: true
});
updatePayerProperties(values.toJS(), () => {
this.setState({ refeshProperties: true });
});
resolve();
});
}
handleInvalidSubmit() {
this.props.showNotification({
level: 'error',
message: 'Errors were found.'
});
}
handleSelectChange(event) {
console.log(event);
}
renderProperties({ fields }) {
const inputUseStyles = makeStyles(theme => ({
root: {
display: 'flex',
flexWrap: 'wrap'
},
formControl: {
margin: theme.spacing(1),
minWidth: 120
},
selectEmpty: {
marginTop: theme.spacing(2)
},
container: {
display: 'flex',
flexWrap: 'wrap'
},
margin: {
margin: theme.spacing(1)
},
textField: {
flexBasis: 200
},
input: {
margin: theme.spacing(1)
}
}));
const selectInputStyle = inputUseStyles().input;
const useStyles = this.useStyles();
const { formProperties, unsetPropertiesKeys } = this.props;
const viewProperties = formProperties[`${this.state.view}`];
const propertyKeys = unsetPropertiesKeys[`${this.state.view}`];
return (
<div maxWidth="sm" className={selectInputStyle.root}>
{fields.map((property, index) => (
<Row
key={index}
style={{
display: viewProperties[index].action === 'remove' ? 'none' : ''
}}
>
<Col xs={5}>
<TextField
select
fullWidth
margin="normal"
className={clsx(selectInputStyle.margin, selectInputStyle.textField, selectInputStyle.root)}
value={viewProperties[index].name}
onChange={this.handleSelectChange}
>
{propertyKeys.map((key, index) => (
<MenuItem value={key} key={index}>
{key}
</MenuItem>
))}
</TextField>
</Col>
<Col xs={5}>
<AvText
name={`${property}.value`}
onChange={() => {
if (viewProperties[index].action !== 'add') {
this.props.change(`${property}.action`, 'update');
this.showButton = true;
}
}}
/>
</Col>
<Col xs={1}>
<MaterialButton
variant="contained"
className="{classes.button}"
onClick={() => {
fields.remove(index);
if (viewProperties[index].action !== 'add') {
fields.insert(
index,
Immutable.fromJS(Object.assign({}, viewProperties[index], { action: 'remove' }))
);
this.showButton = true;
}
}}
>
Delete
<DeleteIcon className={useStyles.rightIcon} />
</MaterialButton>
</Col>
</Row>
))}
<Row>
<Col xs={12}>
<Button
color="primary"
onClick={() => {
fields.push(
Immutable.fromJS({
owner: this.props.ownerKeys[this.state.view],
action: 'add'
})
);
this.showButton = true;
}}
>
Add Property
</Button>
</Col>
</Row>
<br />
{this.showButton === true && (
<Row>
<Col xs={12}>
<Button color="primary" type="submit">
Submit
</Button>
</Col>
</Row>
)}
</div>
);
}
render() {
const { handleSubmit, properties, payerName, chsId, parentChsId } = this.props;
const formStyles = makeStyles(theme => ({
root: {
display: 'flex',
flexWrap: 'wrap'
},
formControl: {
margin: theme.spacing(1),
minWidth: 120
},
selectEmpty: {
marginTop: theme.spacing(2)
}
}));
const navItems = ['payer', 'clearinghouse', 'parent'].map(key => (
<Tab
textColor="primary"
key={key}
label={
key === 'payer'
? 'PAYER: ' + payerName
: key === 'clearinghouse'
? 'CLEARING HOUSE: ' + chsId
: 'PARENT: ' + parentChsId
}
onClick={() => this.setState({ view: key })}
/>
));
const overrides = this.state.view === 'payer' ? ['clearinghouse'] : this.state.view === 'parent' ? ['parent'] : [];
const readonly = properties
? overrides
.filter(key => key !== this.state.view)
.map(key => properties[key])
.reduce((acc, val) => acc.concat(val), [])
.map((property, idx) => {
return (
<Row key={idx}>
<Col xs={5}>
<FormGroup>
<Input value={property.name} disabled />
</FormGroup>
</Col>
<Col xs={5}>
<FormGroup>
<Input value={property.value} disabled />
</FormGroup>
</Col>
</Row>
);
})
: [];
return (
<BlockUi tag="div" blocking={this.state.blocking} className="my-2">
<Container maxWidth="sm">
<AppBar position="static" color="default">
<Tabs variant="fullWidth" textColor="primary">
{navItems}
</Tabs>
</AppBar>
<FormControl
fullWidth
className="mt-4"
onSubmit={handleSubmit(this.doSubmit)}
ref={form => (this.formRef = form)}
>
{readonly}
<FieldArray
name={`properties.${this.state.view}`}
component={this.renderProperties}
rerenderOnEveryChange
/>
</FormControl>
</Container>
</BlockUi>
);
}
}
const mapStateToProps = state => {
const {
payerPropertiesStore: {
payer: { payerId, payerName, chsId, parentChsId },
properties,
propertyKeys
},
form: {
payerPropertiesEditorForm: {
values: { properties: formProperties }
}
}
} = state.toJS();
const unsetPropertiesKeys = {};
for (const view of ['payer', 'clearinghouse', 'parent']) {
unsetPropertiesKeys[view] = propertyKeys.filter(key => !_.find(formProperties[view], { name: key }));
}
const ownerKeys = { payer: payerId, clearinghouse: chsId, parent: parentChsId };
return { formProperties, properties, ownerKeys, unsetPropertiesKeys, payerId, payerName, chsId, parentChsId };
};
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
showNotification,
loadPayerProperties,
updatePayerProperties,
arrayRemoveAll
},
dispatch
);
export default reduxForm({
form: 'payerPropertiesEditorForm',
enableReinitialize: true,
initialValues: Immutable.fromJS({ properties: {} })
})(
connect(
mapStateToProps,
mapDispatchToProps
)(PayerPropertiesEditor)
);
In the TextField you are setting value from viewProperties like,
value={viewProperties[index].name}
You are getting data in viewProperties from formProperties based on this.state.view
const viewProperties = formProperties[`${this.state.view}`];
As you are setting view state in componentDidMount, on intial render view is not set and don't have value so you are not getting any value from formProperties.
You need to have a default state,
this.state = {
errors: [],
refeshProperties: false,
blocking: false,
view: 'payer' // default set
};

How to update other component when change in single component

I am creating timer component and implement with every task. So when I start my timer for a single task then other task timer will be disabled or hidden. I am trying to disable other timer component on start timer but it gives me only value for current component. So how can I update all components when I start a single timer?
DeveloperTasks.js
import { Mutation, Query } from "react-apollo";
import gql from "graphql-tag";
import React, { Component, Fragment } from "react";
import PropTypes from "prop-types";
import { withStyles } from "#material-ui/core/styles";
import TaskTimer from "./TaskTimer";
import Note from "./Note";
import getCDTime from "../util/commonfunc";
import Button from "#material-ui/core/Button";
import IconButton from "#material-ui/core/IconButton";
import Paper from "#material-ui/core/Paper";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemText from "#material-ui/core/ListItemText";
import CircularProgress from "#material-ui/core/CircularProgress";
import Avatar from "#material-ui/core/Avatar";
import FormControl from "#material-ui/core/FormControl";
import InputLabel from "#material-ui/core/InputLabel";
import Select from "#material-ui/core/Select";
import MenuItem from "#material-ui/core/MenuItem";
import TextField from "#material-ui/core/TextField";
import Dialog from "#material-ui/core/Dialog";
import MuiDialogTitle from "#material-ui/core/DialogTitle";
import MuiDialogContent from "#material-ui/core/DialogContent";
import MuiDialogActions from "#material-ui/core/DialogActions";
import Popover from "#material-ui/core/Popover";
import DeleteIcon from "#material-ui/icons/Delete";
import AssignmentIcon from "#material-ui/icons/Assignment";
import NotesIcon from "#material-ui/icons/EventNote";
import AssignmentInd from "#material-ui/icons/AssignmentInd";
import CheckCircleOutline from "#material-ui/icons/CheckCircleOutline";
import CheckCircle from "#material-ui/icons/CheckCircle";
import CloseIcon from "#material-ui/icons/Close";
import Typography from "#material-ui/core/Typography";
import EditIcon from "#material-ui/icons/Edit";
import DateFnsUtils from "#date-io/date-fns";
import {
MuiPickersUtilsProvider,
TimePicker,
DatePicker
} from "material-ui-pickers";
import UserList from "../components/UserList";
import emails from "../components/UserList";
import TodoInlineForm from "../components/TodoInlineForm";
const ms = require("pretty-ms");
//Kanban Quearies
export const tasksQuery = gql`
query Developertasklist($contact_id_c: String) {
Developertasklist(contact_id_c: $contact_id_c) {
id
name
due_date
dtask_start_time
time_tracking_flag
dtask_total_time
status
}
}
`;
//Delete Task Mutation
export const DELETE_TODO = gql`
mutation todo_operations($id: String, $deleted: String) {
todo_operations(id: $id, deleted: $deleted) {
id
}
}
`;
//Complete Task Mutation
const COMPLETE_TASK_OPERATIONS = gql`
mutation todo_operations(
$id: String
$status: String
$actual_due_date: String
) {
todo_operations(
id: $id
status: $status
actual_due_date: $actual_due_date
) {
id
}
}
`;
const styles = theme => ({
root: {
width: "100%",
marginTop: theme.spacing(3),
overflowX: "auto"
},
icon: {
margin: theme.spacing.unit,
fontSize: 20
},
button: {
margin: theme.spacing.unit
},
listroot: {
width: "100%",
minWidth: 900,
backgroundColor: theme.palette.background.paper
},
tasklist: {
marginTop: 30
},
taskwidth: {
width: "55%",
display: "inline-flex"
},
timerwidth: {
width: "25%"
},
width5: {
width: "5%"
},
margin: {
margin: theme.spacing.unit
},
input: {
display: "none"
},
datepadding: {
"padding-right": "10px;",
width: "17%"
},
formControl: {
minWidth: 120
},
elementpadding: {
"padding-right": "10px;"
},
completeIcon: {
color: "Green"
},
popover: {
pointerEvents: "none"
},
label: {
display: "inline",
padding: ".2em .6em .3em",
"font-size": "75%",
"font-weight": "700",
"line-height": 1,
color: "#fff",
"text-align": "center",
"white-space": "nowrap",
"vertical-align": "baseline",
"border-radius": ".25em"
},
labelcomplete: {
"background-color": "#5cb85c"
},
labelprogress: {
"background-color": "#5bc0de"
},
labelonhold: {
"background-color": "#d9534f"
},
labelqafail: {
"background-color": "#d9534f"
},
labelnotstated: {
"background-color": "#777"
},
labelqa: {
"background-color": "#337ab7"
},
labelqapassed: {
"background-color": "#777"
},
labeldefered: {
"background-color": "#f0ad4e"
},
hideelement: {
display: "none"
},
showelement: {
display: "block"
}
});
const DialogTitle = withStyles(theme => ({
root: {
borderBottom: `1px solid ${theme.palette.divider}`,
margin: 0,
padding: theme.spacing.unit * 2
},
closeButton: {
position: "absolute",
right: theme.spacing.unit,
top: theme.spacing.unit,
color: theme.palette.grey[500]
}
}))(props => {
const { children, classes, onClose } = props;
return (
<MuiDialogTitle disableTypography className={classes.root}>
<Typography variant="h6">{children}</Typography>
{onClose ? (
<IconButton
aria-label="Close"
className={classes.closeButton}
onClick={onClose}
>
<CloseIcon />
</IconButton>
) : null}
</MuiDialogTitle>
);
});
const DialogContent = withStyles(theme => ({
root: {
margin: 0,
padding: theme.spacing.unit * 2
}
}))(MuiDialogContent);
const DialogActions = withStyles(theme => ({
root: {
borderTop: `1px solid ${theme.palette.divider}`,
margin: 0,
padding: theme.spacing.unit
}
}))(MuiDialogActions);
class DeveloperTasks extends React.Component {
state = {
start_date: new Date(),
end_date: new Date(),
status: "",
task: "",
searchTerm: "",
open: false,
anchorEl: null,
selectedValue: emails[1],
openreport: false,
openTodoForm: false,
taskid: ""
};
constructor(props) {
super(props);
this.searchUpdated = this.searchUpdated.bind(this);
}
handleDateChange = name => date => {
this.setState({ [name]: date });
};
handleChange = name => event => {
this.setState({ [name]: event.target.value });
};
handleClickOpen = name => event => {
this.setState({
open: true
});
};
handleClose = () => {
this.setState({ open: false });
};
handleClickDialogOpen = () => {
this.setState({ openreport: true });
};
handleDialogClose = value => {
this.setState({ selectedValue: value, openreport: false });
};
searchUpdated(term) {
this.setState({ searchTerm: term });
}
handlePopoverOpen = event => {
this.setState({ anchorEl: event.currentTarget });
};
handlePopoverClose = () => {
this.setState({ anchorEl: null });
};
handleClickTodoOpen(taskid) {
this.setState({ taskid: taskid, openTodoForm: true });
}
componentWillReceiveProps(newProps) {
this.setState({ openTodoForm: newProps.open });
}
render() {
let todoinlineform = "";
const { classes, contact_id } = this.props;
const { anchorEl } = this.state;
const open = Boolean(anchorEl);
let currdatetime = getCDTime.getCurrentDateTime();
let shownbutton = {
display: "block"
};
if (
this.state.openTodoForm &&
this.state.openTodoForm === true &&
this.state.taskid != ""
) {
todoinlineform = (
<TodoInlineForm
open={this.state.openTodoForm}
taskid={this.state.taskid}
modaltitle="Edit Todo"
/>
);
}
return contact_id === "" ? (
""
) : (
<Query query={tasksQuery} variables={{ contact_id_c: contact_id }}>
{({ loading, error, data: { Developertasklist } }) => {
if (error) return <p>{error}</p>;
if (loading) return <CircularProgress className={classes.progress} />;
//Filter with task name
if (this.state.task && this.state.task != "") {
Developertasklist = Developertasklist.filter(
developertasklist =>
developertasklist.name
.toLowerCase()
.indexOf(this.state.task.toLowerCase()) != -1
);
}
//Task status wise filter
if (this.state.status && this.state.status != "") {
Developertasklist = Developertasklist.filter(
developertasklist => developertasklist.status == this.state.status
);
}
//Label array for apply class on status label
let labelcolor = [
{ status: "In Progress", class: classes.labelprogress },
{ status: "Completed", class: classes.labelcomplete },
{ status: "On Hold", class: classes.labelonhold },
{ status: "QA Fail", class: classes.labelqafail },
{ status: "Not Started", class: classes.labelnotstated },
{ status: "QA", class: classes.labelqa },
{ status: "QA Passed", class: classes.labelqapassed },
{ status: "Deferred", class: classes.labeldefered }
];
return (
<Fragment>
<br />
<div className={classes.tasklist}>
<div className="picker">
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<DatePicker
label="Start Date"
name="start_date"
value={this.state.start_date}
format="yyyy-MM-dd"
onChange={this.handleDateChange("start_date")}
className={classes.datepadding}
animateYearScrolling
/>
<DatePicker
label="End Date"
name="end_date"
value={this.state.end_date}
format="yyyy-MM-dd"
onChange={this.handleDateChange("end_date")}
className={classes.datepadding}
animateYearScrolling
/>
</MuiPickersUtilsProvider>
<Button
type="submit"
variant="contained"
color="primary"
className={classes.button}
>
Search
</Button>
<Button
variant="contained"
color="secondary"
className={classes.button}
>
Reset
</Button>
</div>
<FormControl className={classes.formControl}>
<InputLabel htmlFor="status-simple">Status</InputLabel>
<Select
value={this.state.status}
onChange={this.handleChange("status")}
className={classes.elementpadding}
inputProps={{
name: "status",
id: "status"
}}
>
<MenuItem value="">
<em>Please Select</em>
</MenuItem>
<MenuItem value="Not Started">Not Started</MenuItem>
<MenuItem value="In Progress">In Progress</MenuItem>
<MenuItem value="On Hold">On Hold</MenuItem>
<MenuItem value="Deferred">Deferred</MenuItem>
<MenuItem value="Completed">Completed</MenuItem>
<MenuItem value="QA">QA</MenuItem>
<MenuItem value="QA Passed">QA Passed</MenuItem>
<MenuItem value="QA Fail">QA Fail</MenuItem>
</Select>
</FormControl>
<TextField
id="standard-name"
label="Task"
className={classes.textField}
value={this.state.task}
onChange={this.handleChange("task")}
/>
</div>
<div className={classes.tasklist}>
<Paper className={classes.listroot}>
<List className={classes.listroot}>
{Developertasklist.map((task, index) => {
let statusLabel = labelcolor.filter(
obj => obj.status == task.status
);
let completeStatusClass = classes.hideelement;
let pendingStatusClass = "";
let hidetimer = "";
if (task.status === "Completed") {
pendingStatusClass = classes.hideelement;
hidetimer = "hide";
completeStatusClass = "";
}
if (statusLabel.length > 0)
statusLabel = statusLabel[0].class;
return (
<ListItem key={index} divider="true">
<div className={classes.taskwidth}>
<Avatar>
<AssignmentIcon />
</Avatar>
<ListItemText
primary={
<React.Fragment>
{task.name} - {task.due_date}
</React.Fragment>
}
secondary={
<React.Fragment>
<Typography
component="span"
className={[classes.label, statusLabel]}
color="textPrimary"
>
{task.status}
</Typography>
</React.Fragment>
}
/>
</div>
<div className={classes.timerwidth}>
<div>
<TaskTimer
developerlist={task}
hidetimer={hidetimer}
/>
</div>
</div>
<div className={classes.width5}>
<EditIcon
onClick={event => {
this.handleClickTodoOpen(task.id);
}}
/>
</div>
<div className={classes.width5}>
<Mutation mutation={COMPLETE_TASK_OPERATIONS}>
{(todo_operations, { loading, error }) => (
<CheckCircleOutline
className={pendingStatusClass}
aria-owns={
open ? "mouse-over-popover" : undefined
}
aria-haspopup="true"
onMouseEnter={this.handlePopoverOpen}
onMouseLeave={this.handlePopoverClose}
onClick={event => {
todo_operations({
variables: {
id: task.id,
actual_due_date: currdatetime,
status: "Completed"
}
});
}}
/>
)}
</Mutation>
<Popover
id="mouse-over-popover"
className={classes.popover}
classes={{
paper: classes.paper
}}
open={open}
anchorEl={anchorEl}
anchorOrigin={{
vertical: "bottom",
horizontal: "left"
}}
transformOrigin={{
vertical: "top",
horizontal: "left"
}}
onClose={this.handlePopoverClose}
disableRestoreFocus
>
<Typography>Mark as completed.</Typography>
</Popover>
<CheckCircle
className={[
classes.completeIcon,
completeStatusClass
]}
/>
</div>
<div className={classes.width5}>
<div className={pendingStatusClass}>
{/* <Typography variant="subtitle1">
Selected: {this.state.selectedValue}
</Typography> */}
<AssignmentInd
onClick={this.handleClickDialogOpen}
/>
<UserList
selectedValue={this.state.selectedValue}
open={this.state.openreport}
onClose={this.handleDialogClose}
/>
</div>
</div>
<div className={classes.width5}>
<NotesIcon onClick={this.handleClickOpen()} />
<Dialog
onClose={this.handleClose}
aria-labelledby="customized-dialog-title"
open={this.state.open}
>
<DialogTitle
id="customized-dialog-title"
onClose={this.handleClose}
>
Notes
</DialogTitle>
<DialogContent>
<Note />
</DialogContent>
</Dialog>
</div>
<div className={classes.width5}>
<Mutation mutation={DELETE_TODO}>
{(todo_operations, { loading, error }) => (
<DeleteIcon
aria-label="Delete"
onClick={event => {
todo_operations({
variables: {
id: task.id,
deleted: "1"
}
});
}}
/>
)}
</Mutation>
</div>
</ListItem>
);
})}
</List>
</Paper>
</div>
{todoinlineform}
</Fragment>
);
}}
</Query>
);
}
}
export default withStyles(styles, { withTheme: true })(DeveloperTasks);
TaskTimer.js
import { Mutation, Query } from "react-apollo";
import gql from "graphql-tag";
const React = require("react");
const ms = require("pretty-ms");
import PropTypes from "prop-types";
import { withStyles } from "#material-ui/core/styles";
import StartIcon from "#material-ui/icons/PlayCircleFilled";
import StopIcon from "#material-ui/icons/Stop";
import getCDTime from "../util/commonfunc";
//Start timer mutation
const TODO_OPERATIONS = gql`
mutation todo_operations(
$id: String
$status: String
$dtask_start_time: String
$time_tracking_flag: String
$developer_daily_hours: String
$is_task_started: String
$actual_start_date: String
) {
todo_operations(
id: $id
status: $status
dtask_start_time: $dtask_start_time
time_tracking_flag: $time_tracking_flag
developer_daily_hours: $developer_daily_hours
is_task_started: $is_task_started
actual_start_date: $actual_start_date
) {
id
}
}
`;
//Stop timer mutation
const STOP_TIMER = gql`
mutation todo_operations(
$id: String
$dtask_stop_time: String
$dtask_total_time: String
$time_tracking_flag: String
) {
todo_operations(
id: $id
dtask_stop_time: $dtask_stop_time
dtask_total_time: $dtask_total_time
time_tracking_flag: $time_tracking_flag
) {
id
}
}
`;
const styles = theme => ({
button: {
margin: theme.spacing.unit
},
stopbutton: {
margin: theme.spacing.unit,
color: "Red"
},
input: {
display: "none"
},
clock: {
color: "Green",
fontWeight: "700",
fontSize: "15px"
},
hideelement: {
display: "none"
},
timerClass: {
display: "none"
}
});
class TaskTimer extends React.Component {
constructor(props) {
const total_time = !props.developerlist.dtask_total_time
? parseInt(0)
: parseInt(props.developerlist.dtask_total_time);
let statetime = total_time;
let stateison = false;
super(props);
if (props.developerlist.time_tracking_flag === "yes") {
let currentdatetime = new Date(getCDTime.getCurrentDateTime());
let start_time = new Date(props.developerlist.dtask_start_time);
let time_diff = Math.abs(currentdatetime - start_time) / 1000;
statetime = time_diff + total_time;
stateison = true;
this.state = {
time: statetime,
isOn: stateison
};
this.startTimer();
}
this.state = {
time: statetime,
isOn: stateison,
timerClass: "test"
};
this.startTimer = this.startTimer.bind(this);
this.stopTimer = this.stopTimer.bind(this);
}
startTimer(next) {
this.setState({
isOn: true,
time: this.state.time,
timerClass: "abc"
});
this.timer = setInterval(
() =>
this.setState({
time: parseInt(this.state.time + 1)
}),
1000
);
}
stopTimer() {
this.state.time = parseInt(this.state.time);
this.setState({ isOn: false });
clearInterval(this.timer);
}
render() {
let totalTaskTime = parseInt(this.state.time) * 1000;
const { classes, theme } = this.props;
let hideTimerClass =
this.props.hidetimer === "hide" ? classes.hideelement : "";
let currdatetime = getCDTime.getCurrentDateTime();
let start =
(this.state.time == 0 || this.state.time > 0) && this.state.isOn == 0 ? (
<Mutation mutation={TODO_OPERATIONS}>
{(todo_operations, { loading, error }) => (
<StartIcon
variant="contained"
color="primary"
className={[
classes.button,
hideTimerClass,
this.state.timerClass
]}
//className={this.state.timerClass}
onClick={event => {
this.startTimer();
todo_operations({
variables: {
id: this.props.developerlist.id,
status: "In Progress",
dtask_start_time: currdatetime,
time_tracking_flag: "yes",
developer_daily_hours: dailyhours,
is_task_started: "yes",
actual_start_date: currdatetime
}
});
}}
/>
)}
</Mutation>
) : null;
let stop =
this.state.isOn && this.state.isOn == 1 ? (
<Mutation mutation={STOP_TIMER}>
{(todo_operations, { loading, error }) => (
<StopIcon
variant="contained"
className={[classes.stopbutton, hideTimerClass]}
disabled={true}
onClick={event => {
this.stopTimer();
todo_operations({
variables: {
id: this.props.developerlist.id,
dtask_stop_time: currdatetime,
dtask_total_time: this.state.time,
time_tracking_flag: "stop"
}
});
}}
/>
)}
</Mutation>
) : null;
return (
<div>
<div className={classes.clock}>{ms(totalTaskTime)}</div>
{start}
{stop}
</div>
);
}
}
export default withStyles(styles, { withTheme: true })(TaskTimer);
if I'm understanding your problem correctly you are trying to cause an update in sister components when a certain event happens.
I believe that the best way to do this would be to have a state in your parent component (DeveloperTasks) that holds whether or not each timer should be disabled. Then pass a callback into each timer that would update the disabled list in the way that you're looking for.
The way I'm imagining such a callback would work is:
function getDisableOtherTimersCallback(timerNum) {
return timerNum => {
return this.state.setState({disabledList: disabledList.map((value, index) => index == timerNum)})
// this line just goes through and makes sure that the index corresponding to timerNum is the only one that's true
};
}
Then when you render your component for timer n you would pass in the timer you get for timer n.
<TaskTimer
developerlist={task}
hidetimer={this.state.disabledList[n]}
disableOtherTimersCallback={getDisableOtherTimersCallback(n)}
/>
Then whenever you want to disable the other timers you would call this method and your done!
(Also note that you can now use the disabledList to show or hide each timer!)
(Also also note that you need to add the disabledList to your state and initiate it in the way you are looking for)
Hope this helps!

How to remove components from a react-admin app based on the route?

I have created a user register page on the react-admin template, but when it's rendered, the sidebar and appbar are being rendered as well.
The default login page in the template gets rendered without these 2 components.
How do I configure the app so that the register page also gets rendered without the sidebar and appbar?
MyLayout.js
// in src/MyLayout.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { withStyles, MuiThemeProvider, createMuiTheme } from '#material-ui/core/styles';
import {
AppBar,
Menu,
Notification,
Sidebar,
setSidebarVisibility,
} from 'react-admin';
const styles = theme => ({
root: {
display: 'flex',
flexDirection: 'column',
zIndex: 1,
minHeight: '100vh',
backgroundColor: theme.palette.background.default,
position: 'relative',
},
appFrame: {
display: 'flex',
flexDirection: 'column',
overflowX: 'auto',
},
contentWithSidebar: {
display: 'flex',
flexGrow: 1,
},
content: {
display: 'flex',
flexDirection: 'column',
flexGrow: 2,
padding: theme.spacing.unit * 3,
marginTop: '4em',
paddingLeft: 5,
},
});
class MyLayout extends Component {
componentWillMount() {
this.props.setSidebarVisibility(true);
}
render() {
const {
children,
classes,
dashboard,
isLoading,
logout,
open,
title,
} = this.props;
return (
<div className={classes.root}>
<div className={classes.appFrame}>
<AppBar title={title} open={open} logout={logout} color="primary"/>
<main className={classes.contentWithSidebar}>
<Sidebar>
<Menu logout={logout} hasDashboard={!!dashboard} />
</Sidebar>
<div className={classes.content}>
{children}
</div>
</main>
<Notification />
</div>
</div>
);
}
}
MyLayout.propTypes = {
children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
dashboard: PropTypes.oneOfType([
PropTypes.func,
PropTypes.string,
]),
isLoading: PropTypes.bool.isRequired,
logout: 'componentPropType',
setSidebarVisibility: PropTypes.func.isRequired,
title: PropTypes.string.isRequired,
};
const mapStateToProps = state => ({ isLoading: state.admin.loading > 0 });
export default connect(mapStateToProps, { setSidebarVisibility })(withStyles(styles)(MyLayout));
register.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { propTypes, reduxForm, Field } from 'redux-form';
import { connect } from 'react-redux';
import compose from 'recompose/compose';
import Avatar from '#material-ui/core/Avatar';
import Button from '#material-ui/core/Button';
import Card from '#material-ui/core/Card';
import CardActions from '#material-ui/core/CardActions';
import CircularProgress from '#material-ui/core/CircularProgress';
import TextField from '#material-ui/core/TextField';
import { withStyles } from '#material-ui/core/styles';
import LockIcon from '#material-ui/icons/Lock';
import { Notification, translate, userLogin } from 'react-admin';
import { Link } from 'react-router-dom';
import dataProvider from './dataProvider';
const styles = theme => ({
main: {
display: 'flex',
flexDirection: 'column',
minHeight: '100vh',
alignItems: 'center',
justifyContent: 'flex-start',
background: 'url(https://source.unsplash.com/random/1600x900)',
backgroundRepeat: 'no-repeat',
backgroundSize: 'cover',
},
card: {
minWidth: 300,
marginTop: '6em',
},
avatar: {
margin: '1em',
display: 'flex',
justifyContent: 'center',
},
icon: {
backgroundColor: theme.palette.secondary.main,
},
hint: {
marginTop: '1em',
display: 'flex',
justifyContent: 'center',
color: theme.palette.grey[500],
},
form: {
padding: '0 1em 1em 1em',
},
input: {
marginTop: '1em',
},
actions: {
padding: '0 1em 1em 1em',
},
});
// see http://redux-form.com/6.4.3/examples/material-ui/
const renderInput = ({
meta: { touched, error } = {},
input: { ...inputProps },
...props
}) => (
<TextField
error={!!(touched && error)}
helperText={touched && error}
{...inputProps}
{...props}
fullWidth
/>
);
class Register extends Component {
constructor(props) {
super(props);
this.state = {
firstName: '',
lastName: '',
email: '',
password: '',
submitted: false
};
//this.handleSubmit = this.handleSubmit.bind(this);
this.handleFirstNameChange = this.handleFirstNameChange.bind(this);
this.handleLastNameChange = this.handleLastNameChange.bind(this);
this.handleEmailChange = this.handleEmailChange.bind(this);
this.handlePasswordChange = this.handlePasswordChange.bind(this);
}
handleFirstNameChange(e){
this.setState({firstName:e.target.value});
}
handleLastNameChange(e) {
this.setState({ lastName: e.target.value });
}
handleEmailChange(e) {
this.setState({ email: e.target.value });
}
handlePasswordChange(e) {
this.setState({ password: e.target.value });
}
register = () => {
this.setState({ submitted: true });
const { firstName, lastName, email, password } = this.state;
//console.log(data);
//console.log(firstName);
dataProvider('CREATE', 'user/register', { 'data': { 'fname': firstName, 'lname': lastName, 'email': email, 'password': password } });
/*dataProvider('CREATE', 'user/register', { data: {
fname: 1, lname: 5, email: test, password: 1234
} });*/
}
render() {
const { classes, handleSubmit, isLoading, translate } = this.props;
return (
<div className={classes.main}>
<Card className={classes.card}>
<div className={classes.avatar}>
<Avatar className={classes.icon}>
<LockIcon />
</Avatar>
</div>
<form onSubmit={handleSubmit(this.register)}>
<div className={classes.hint}></div>
<div className={classes.form}>
<div className={classes.input}>
<Field
name="firstName"
component={renderInput}
label={'First Name'}
disabled={isLoading}
value={this.state.firstName}
onChange={this.handleFirstNameChange}
/>
</div>
<div className={classes.input}>
<Field
name="lastName"
component={renderInput}
label={'Last Name'}
disabled={isLoading}
value={this.state.lastName}
onChange={this.handleLastNameChange}
/>
</div>
<div className={classes.input}>
<Field
name="email"
component={renderInput}
label={'Email'}
disabled={isLoading}
value={this.state.email}
onChange={this.handleEmailChange}
/>
</div>
<div className={classes.input}>
<Field
name="password"
component={renderInput}
label={translate('ra.auth.password')}
type="password"
disabled={isLoading}
value={this.state.password}
onChange={this.handlePasswordChange}
/>
</div>
</div>
<CardActions className={classes.actions}>
<Button
variant="raised"
type="submit"
color="primary"
disabled={isLoading}
className={classes.button}
fullWidth
>
{isLoading && (
<CircularProgress size={25} thickness={2} />
)}
{'Register'}
</Button>
</CardActions>
<CardActions className={classes.actions}>
<Button
variant="raised"
color="secondary"
disabled={isLoading}
className={classes.button}
fullWidth
>
<Link to={{pathname: "/login"}} style={{textDecoration: 'none', color:'#fff'}} >Sign In</Link>
</Button>
</CardActions>
</form>
</Card>
<Notification />
</div>
);
}
}
Register.propTypes = {
...propTypes,
authProvider: PropTypes.func,
classes: PropTypes.object,
previousRoute: PropTypes.string,
translate: PropTypes.func.isRequired,
userLogin: PropTypes.func.isRequired,
};
const mapStateToProps = state => ({ isLoading: state.admin.loading > 0 });
const enhance = compose(
translate,
reduxForm({
form: 'signIn',
validate: (values, props) => {
const errors = {};
const { translate } = props;
if (!values.fname) {
errors.fname = translate('ra.validation.required');
}
if (!values.lname) {
errors.lname = translate('ra.validation.required');
}
if (!values.email) {
errors.email = translate('ra.validation.required');
}
if (!/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email) ) {
errors.email = 'A valid email is required';
}
if (!values.password) {
errors.password = translate('ra.validation.required');
}
return errors;
},
}),
connect(
mapStateToProps,
{ userLogin }
),
withStyles(styles)
);
export default enhance(Register);
I believe you will use a customRoute to add your register page to router. You can add a 'noLayout' for not displaying appbar and sidebar
https://github.com/marmelab/react-admin/blob/master/docs/Admin.md#customroutes

Resources