diff --git a/packages/ra-core/src/i18n/index.ts b/packages/ra-core/src/i18n/index.ts index 932ea3efb9f..8f02471aa23 100644 --- a/packages/ra-core/src/i18n/index.ts +++ b/packages/ra-core/src/i18n/index.ts @@ -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'; diff --git a/packages/ra-core/src/index.ts b/packages/ra-core/src/index.ts index 1fdbc54f5bd..0422262ee4e 100644 --- a/packages/ra-core/src/index.ts +++ b/packages/ra-core/src/index.ts @@ -44,3 +44,4 @@ export { } from './reducer/admin/references/oneToMany'; export * from './sideEffect'; +export * from './types'; diff --git a/packages/ra-core/src/types.ts b/packages/ra-core/src/types.ts index b6cd11fd768..b406242d64c 100644 --- a/packages/ra-core/src/types.ts +++ b/packages/ra-core/src/types.ts @@ -57,6 +57,7 @@ export interface ReduxState { [relatedTo: string]: { ids: Identifier[]; total: number }; }; }; + loading: number; }; i18n: { locale: string; diff --git a/packages/ra-ui-materialui/src/Link.js b/packages/ra-ui-materialui/src/Link.tsx similarity index 80% rename from packages/ra-ui-materialui/src/Link.js rename to packages/ra-ui-materialui/src/Link.tsx index 2cea8130035..97d573e929c 100644 --- a/packages/ra-ui-materialui/src/Link.js +++ b/packages/ra-ui-materialui/src/Link.tsx @@ -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 */ diff --git a/packages/ra-ui-materialui/src/auth/Login.js b/packages/ra-ui-materialui/src/auth/Login.tsx similarity index 69% rename from packages/ra-ui-materialui/src/auth/Login.js rename to packages/ra-ui-materialui/src/auth/Login.tsx index 49fd471238c..533b2b45fa8 100644 --- a/packages/ra-ui-materialui/src/auth/Login.js +++ b/packages/ra-ui-materialui/src/auth/Login.tsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { Component, ReactElement, ComponentType } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import Card from '@material-ui/core/Card'; @@ -8,6 +8,7 @@ import { createMuiTheme, withStyles, createStyles, + WithStyles, } from '@material-ui/core/styles'; import LockIcon from '@material-ui/icons/Lock'; @@ -15,42 +16,38 @@ 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; + className: string; + loginForm: ReactElement; + theme: object; +} -const sanitizeRestProps = ({ - array, - backgroundImage, - classes, - className, - location, - staticContext, - theme, - title, - ...rest -}) => rest; +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], + }, + }); /** * A standalone login page, to serve as authentication gate to the admin @@ -70,13 +67,10 @@ const sanitizeRestProps = ({ * * ); */ -class Login extends Component { - constructor(props) { - super(props); - this.theme = createMuiTheme(props.theme); - this.containerRef = React.createRef(); - this.backgroundImageLoaded = false; - } +class LoginView extends Component> { + theme = createMuiTheme(this.props.theme); + containerRef = React.createRef(); + backgroundImageLoaded = false; // 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 @@ -85,16 +79,12 @@ class Login extends Component { // 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) => { + updateBackgroundImage = () => { 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 @@ -114,18 +104,24 @@ 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, + ...rest + } = this.props; return (
@@ -143,15 +139,13 @@ class Login extends Component { } } +const Login = withStyles(styles)(LoginView) as ComponentType; + 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, + theme: PropTypes.object, }; Login.defaultProps = { @@ -160,4 +154,4 @@ Login.defaultProps = { loginForm: , }; -export default withStyles(styles)(Login); +export default Login; diff --git a/packages/ra-ui-materialui/src/auth/LoginForm.js b/packages/ra-ui-materialui/src/auth/LoginForm.tsx similarity index 64% rename from packages/ra-ui-materialui/src/auth/LoginForm.js rename to packages/ra-ui-materialui/src/auth/LoginForm.tsx index 45921804797..25c574bac3f 100644 --- a/packages/ra-ui-materialui/src/auth/LoginForm.js +++ b/packages/ra-ui-materialui/src/auth/LoginForm.tsx @@ -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, + WithStyles { + 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 = ({ @@ -39,7 +61,13 @@ const renderInput = ({ const login = (auth, dispatch, { redirectTo }) => dispatch(userLogin(auth, redirectTo)); -const LoginForm = ({ classes, isLoading, handleSubmit, translate }) => ( +const LoginFormView: SFC = ({ + classes, + isLoading, + handleSubmit, + translate, + ...rest +}) => (
@@ -77,30 +105,35 @@ const LoginForm = ({ classes, isLoading, handleSubmit, translate }) => ( ); -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( withStyles(styles), - translate, + withTranslate, connect(mapStateToProps), reduxForm({ form: 'signIn', - validate: (values, props) => { - const errors = {}; + validate: (values: FormData, props: TranslationContextProps) => { + const errors = { username: '', password: '' }; 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 LoginForm = enhance(LoginFormView); + +LoginForm.propTypes = { + redirectTo: PropTypes.string, +}; + +export default LoginForm; diff --git a/packages/ra-ui-materialui/src/auth/Logout.js b/packages/ra-ui-materialui/src/auth/Logout.tsx similarity index 54% rename from packages/ra-ui-materialui/src/auth/Logout.js rename to packages/ra-ui-materialui/src/auth/Logout.tsx index a5bb21d8e15..d0ecc1f3ca8 100644 --- a/packages/ra-ui-materialui/src/auth/Logout.js +++ b/packages/ra-ui-materialui/src/auth/Logout.tsx @@ -1,44 +1,64 @@ -import React from 'react'; +import React, { SFC } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import compose from 'recompose/compose'; import MenuItem from '@material-ui/core/MenuItem'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { + withStyles, + createStyles, + Theme, + WithStyles, +} from '@material-ui/core/styles'; import ExitIcon from '@material-ui/icons/PowerSettingsNew'; import classnames from 'classnames'; -import { translate, userLogout as userLogoutAction } from 'ra-core'; +import { + withTranslate, + userLogout as userLogoutAction, + TranslationContextProps, +} from 'ra-core'; -const styles = theme => createStyles({ - menuItem: { - color: theme.palette.text.secondary, - }, - iconMenuPaddingStyle: { - paddingRight: '1.2em', - }, - iconPaddingStyle: { - paddingRight: theme.spacing.unit, - }, -}); +interface Props { + className?: string; + redirectTo?: string; +} + +interface EnhancedProps + extends TranslationContextProps, + WithStyles { + userLogout: () => void; +} + +const styles = (theme: Theme) => + createStyles({ + menuItem: { + color: theme.palette.text.secondary, + }, + iconMenuPaddingStyle: { + paddingRight: '1.2em', + }, + iconPaddingStyle: { + paddingRight: theme.spacing.unit, + }, + }); -const sanitizeRestProps = ({ - classes, - className, - translate, - userLogout, - locale, - redirectTo, - ...rest -}) => rest; /** * Logout button component, to be passed to the Admin component * * Used for the Logout Menu item in the sidebar */ -const Logout = ({ classes, className, translate, userLogout, ...rest }) => ( +const LogoutView: SFC = ({ + classes, + className, + locale, + redirectTo, + translate, + userLogout, + ...rest +}) => ( @@ -47,14 +67,6 @@ const Logout = ({ classes, className, translate, userLogout, ...rest }) => ( ); -Logout.propTypes = { - classes: PropTypes.object, - className: PropTypes.string, - translate: PropTypes.func, - userLogout: PropTypes.func, - redirectTo: PropTypes.string, -}; - const mapStateToProps = state => ({ theme: state.theme, }); @@ -63,8 +75,8 @@ const mapDispatchToProps = (dispatch, { redirectTo }) => ({ userLogout: () => dispatch(userLogoutAction(redirectTo)), }); -const enhance = compose( - translate, +const enhance = compose( + withTranslate, connect( mapStateToProps, mapDispatchToProps @@ -72,4 +84,11 @@ const enhance = compose( withStyles(styles) ); -export default enhance(Logout); +const Logout = enhance(LogoutView); + +Logout.propTypes = { + className: PropTypes.string, + redirectTo: PropTypes.string, +}; + +export default Logout; diff --git a/packages/ra-ui-materialui/src/auth/index.js b/packages/ra-ui-materialui/src/auth/index.ts similarity index 100% rename from packages/ra-ui-materialui/src/auth/index.js rename to packages/ra-ui-materialui/src/auth/index.ts diff --git a/packages/ra-ui-materialui/src/defaultTheme.js b/packages/ra-ui-materialui/src/defaultTheme.ts similarity index 100% rename from packages/ra-ui-materialui/src/defaultTheme.js rename to packages/ra-ui-materialui/src/defaultTheme.ts