Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate ra-ui-materialui to TypeScript (Part 1: auth) #2984

Merged
merged 6 commits into from
Apr 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion packages/ra-core/src/i18n/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import defaultI18nProvider from './defaultI18nProvider';
import translate from './translate';
import TranslationProvider from './TranslationProvider';

export { defaultI18nProvider, translate, TranslationProvider };
// Alias to translate to avoid shadowed variable names error with tsling
const withTranslate = translate;

export { defaultI18nProvider, translate, withTranslate, TranslationProvider };
export const DEFAULT_LOCALE = 'en';

export * from './TranslationUtils';
export * from './TranslationContext';
1 change: 1 addition & 0 deletions packages/ra-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@ export {
} from './reducer/admin/references/oneToMany';

export * from './sideEffect';
export * from './types';
1 change: 1 addition & 0 deletions packages/ra-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export interface ReduxState {
[relatedTo: string]: { ids: Identifier[]; total: number };
};
};
loading: number;
};
i18n: {
locale: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import classNames from 'classnames';
import { Link as RRLink } from 'react-router-dom';
import { withStyles, createStyles } from '@material-ui/core/styles';

const styles = theme => createStyles({
link: {
textDecoration: 'none',
color: theme.palette.primary.main,
},
});
const styles = theme =>
createStyles({
link: {
textDecoration: 'none',
color: theme.palette.primary.main,
},
});

/**
* @deprecated Use react-router-dom's Link instead
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import React, { Component } from 'react';
import React, {
Component,
ReactElement,
ComponentType,
HtmlHTMLAttributes,
} from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import Card from '@material-ui/core/Card';
Expand All @@ -8,49 +13,48 @@ import {
createMuiTheme,
withStyles,
createStyles,
WithStyles,
Theme,
} from '@material-ui/core/styles';
import LockIcon from '@material-ui/icons/Lock';
import { StaticContext } from 'react-router';

import defaultTheme from '../defaultTheme';
import Notification from '../layout/Notification';
import DefaultLoginForm from './LoginForm';

const styles = theme => createStyles({
main: {
display: 'flex',
flexDirection: 'column',
minHeight: '100vh',
height: '1px',
alignItems: 'center',
justifyContent: 'flex-start',
backgroundRepeat: 'no-repeat',
backgroundSize: 'cover',
},
card: {
minWidth: 300,
marginTop: '6em',
},
avatar: {
margin: '1em',
display: 'flex',
justifyContent: 'center',
},
icon: {
backgroundColor: theme.palette.secondary[500],
},
});
interface Props {
backgroundImage?: string;
loginForm: ReactElement<any>;
theme: object;
staticContext: StaticContext;
}

const sanitizeRestProps = ({
array,
backgroundImage,
classes,
className,
location,
staticContext,
theme,
title,
...rest
}) => rest;
const styles = (theme: Theme) =>
createStyles({
main: {
display: 'flex',
flexDirection: 'column',
minHeight: '100vh',
height: '1px',
alignItems: 'center',
justifyContent: 'flex-start',
backgroundRepeat: 'no-repeat',
backgroundSize: 'cover',
},
card: {
minWidth: 300,
marginTop: '6em',
},
avatar: {
margin: '1em',
display: 'flex',
justifyContent: 'center',
},
icon: {
backgroundColor: theme.palette.secondary[500],
},
});

/**
* A standalone login page, to serve as authentication gate to the admin
Expand All @@ -70,31 +74,31 @@ const sanitizeRestProps = ({
* </Admin>
* );
*/
class Login extends Component {
constructor(props) {
super(props);
this.theme = createMuiTheme(props.theme);
this.containerRef = React.createRef();
this.backgroundImageLoaded = false;
}
class Login extends Component<
Props & WithStyles<typeof styles> & HtmlHTMLAttributes<HTMLDivElement>
> {
static propTypes = {
backgroundImage: PropTypes.string,
loginForm: PropTypes.element,
theme: PropTypes.object,
};

static defaultProps = {
backgroundImage: 'https://source.unsplash.com/random/1600x900/daily',
theme: defaultTheme,
loginForm: <DefaultLoginForm />,
};

// Even though the React doc ensure the ref creation is done before the
// componentDidMount, it can happen that the ref is set to null until the
// next render.
// So, to handle this case the component will now try to load the image on
// the componentDidMount, but if the ref doesn't exist, it will try again
// on the following componentDidUpdate. The try will be done only once.
// @see https://reactjs.org/docs/refs-and-the-dom.html#adding-a-ref-to-a-dom-element
updateBackgroundImage = (lastTry = false) => {
theme = createMuiTheme(this.props.theme);
containerRef = React.createRef<HTMLDivElement>();
backgroundImageLoaded = false;

updateBackgroundImage = () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did you remove the lastTry? See the comment above the function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First, because this code does not work at all, read the original carefully and look where we were passing lastTry. Second because TypeScript agrees with me

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then you should remove the comment above the method, too

if (!this.backgroundImageLoaded && this.containerRef.current) {
const { backgroundImage } = this.props;
this.containerRef.current.style.backgroundImage = `url(${backgroundImage})`;
this.backgroundImageLoaded = true;
}

if (lastTry) {
this.backgroundImageLoaded = true;
}
};

// Load background image asynchronously to speed up time to interactive
Expand All @@ -114,18 +118,25 @@ class Login extends Component {

componentDidUpdate() {
if (!this.backgroundImageLoaded) {
this.lazyLoadBackgroundImage(true);
this.lazyLoadBackgroundImage();
}
}

render() {
const { classes, className, loginForm, ...rest } = this.props;
const {
backgroundImage,
classes,
className,
loginForm,
staticContext,
...rest
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of the props that were in sanitizeRestProps are missing here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because they were unnecessary. I checked

} = this.props;

return (
<MuiThemeProvider theme={this.theme}>
<div
className={classnames(classes.main, className)}
{...sanitizeRestProps(rest)}
{...rest}
fzaninotto marked this conversation as resolved.
Show resolved Hide resolved
ref={this.containerRef}
>
<Card className={classes.card}>
Expand All @@ -143,21 +154,6 @@ class Login extends Component {
}
}

Login.propTypes = {
authProvider: PropTypes.func,
backgroundImage: PropTypes.string,
classes: PropTypes.object,
className: PropTypes.string,
input: PropTypes.object,
loginForm: PropTypes.element,
meta: PropTypes.object,
previousRoute: PropTypes.string,
};

Login.defaultProps = {
backgroundImage: 'https://source.unsplash.com/random/1600x900/daily',
theme: defaultTheme,
loginForm: <DefaultLoginForm />,
};
const EnhancedLogin = withStyles(styles)(Login) as ComponentType<Props>;

export default withStyles(styles)(Login);
export default EnhancedLogin;
Original file line number Diff line number Diff line change
@@ -1,26 +1,48 @@
import React from 'react';
import React, { SFC } from 'react';
import PropTypes from 'prop-types';
import { Field, propTypes, reduxForm } from 'redux-form';
import { Field, reduxForm, InjectedFormProps } from 'redux-form';
import { connect } from 'react-redux';
import compose from 'recompose/compose';
import CardActions from '@material-ui/core/CardActions';
import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
import CircularProgress from '@material-ui/core/CircularProgress';
import { withStyles, createStyles } from '@material-ui/core/styles';
import { translate, userLogin } from 'ra-core';
import { withStyles, createStyles, WithStyles } from '@material-ui/core/styles';
import {
withTranslate,
userLogin,
TranslationContextProps,
ReduxState,
} from 'ra-core';

const styles = () => createStyles({
form: {
padding: '0 1em 1em 1em',
},
input: {
marginTop: '1em',
},
button: {
width: '100%',
},
});
interface Props {
redirectTo?: string;
}

interface FormData {
username: string;
password: string;
}

interface EnhancedProps
extends TranslationContextProps,
InjectedFormProps<FormData>,
WithStyles<typeof styles> {
isLoading: boolean;
}

const styles = () =>
createStyles({
form: {
padding: '0 1em 1em 1em',
},
input: {
marginTop: '1em',
},
button: {
width: '100%',
},
});

// see http://redux-form.com/6.4.3/examples/material-ui/
const renderInput = ({
Expand All @@ -39,7 +61,12 @@ const renderInput = ({
const login = (auth, dispatch, { redirectTo }) =>
dispatch(userLogin(auth, redirectTo));

const LoginForm = ({ classes, isLoading, handleSubmit, translate }) => (
const LoginForm: SFC<Props & EnhancedProps> = ({
classes,
isLoading,
handleSubmit,
translate,
}) => (
<form onSubmit={handleSubmit(login)}>
<div className={classes.form}>
<div className={classes.input}>
Expand Down Expand Up @@ -77,30 +104,35 @@ const LoginForm = ({ classes, isLoading, handleSubmit, translate }) => (
</CardActions>
</form>
);
LoginForm.propTypes = {
...propTypes,
classes: PropTypes.object,
redirectTo: PropTypes.string,
};

const mapStateToProps = state => ({ isLoading: state.admin.loading > 0 });
const mapStateToProps = (state: ReduxState) => ({
isLoading: state.admin.loading > 0,
});

const enhance = compose(
const enhance = compose<Props & EnhancedProps, Props>(
withStyles(styles),
translate,
withTranslate,
connect(mapStateToProps),
reduxForm({
form: 'signIn',
validate: (values, props) => {
const errors = {};
validate: (values: FormData, props: TranslationContextProps) => {
const errors = { username: '', password: '' };
fzaninotto marked this conversation as resolved.
Show resolved Hide resolved
const { translate } = props;
if (!values.username)
if (!values.username) {
errors.username = translate('ra.validation.required');
if (!values.password)
}
if (!values.password) {
errors.password = translate('ra.validation.required');
}
return errors;
},
})
);

export default enhance(LoginForm);
const EnhancedLoginForm = enhance(LoginForm);

EnhancedLoginForm.propTypes = {
redirectTo: PropTypes.string,
};

export default EnhancedLoginForm;
Loading