21720: replaced theme.spacing.unit * x with theme.spacing(x) everywhere
[arvados.git] / services / workbench2 / src / views-components / login-form / login-form.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import React from 'react';
6 import { useState, useEffect, useRef } from 'react';
7 import { CustomStyleRulesCallback } from 'common/custom-theme';
8 import { withStyles, WithStyles } from '@material-ui/core/styles';
9 import CircularProgress from '@material-ui/core/CircularProgress';
10 import { Button, Card, CardContent, TextField, CardActions } from '@material-ui/core';
11 import { green } from '@material-ui/core/colors';
12 import { AxiosPromise } from 'axios';
13 import { DispatchProp } from 'react-redux';
14 import { saveApiToken } from 'store/auth/auth-action';
15 import { navigateToRootProject } from 'store/navigation/navigation-action';
16 import { replace } from 'react-router-redux';
17 import { PasswordLoginResponse } from 'views/login-panel/login-panel';
18
19 type CssRules = 'root' | 'loginBtn' | 'card' | 'wrapper' | 'progress';
20
21 const styles: CustomStyleRulesCallback<CssRules> = theme => ({
22     root: {
23         display: 'flex',
24         flexWrap: 'wrap',
25         width: '100%',
26         margin: `${theme.spacing(1)} auto`
27     },
28     loginBtn: {
29         marginTop: theme.spacing(1),
30         flexGrow: 1
31     },
32     card: {
33         marginTop: theme.spacing(1),
34         width: '100%'
35     },
36     wrapper: {
37         margin: theme.spacing(1),
38         position: 'relative',
39     },
40     progress: {
41         color: green[500],
42         position: 'absolute',
43         top: '50%',
44         left: '50%',
45         marginTop: -12,
46         marginLeft: -12,
47     },
48 });
49
50 type LoginFormProps = DispatchProp<any> & WithStyles<CssRules> & {
51     handleSubmit: (username: string, password: string) => AxiosPromise<PasswordLoginResponse>;
52     loginLabel?: string,
53 };
54
55 export const LoginForm = withStyles(styles)(
56     ({ handleSubmit, loginLabel, dispatch, classes }: LoginFormProps) => {
57         const userInput = useRef<HTMLInputElement>(null);
58         const [username, setUsername] = useState('');
59         const [password, setPassword] = useState('');
60         const [isButtonDisabled, setIsButtonDisabled] = useState(true);
61         const [isSubmitting, setSubmitting] = useState(false);
62         const [helperText, setHelperText] = useState('');
63         const [error, setError] = useState(false);
64
65         useEffect(() => {
66             setError(false);
67             setHelperText('');
68             if (username.trim() && password.trim()) {
69                 setIsButtonDisabled(false);
70             } else {
71                 setIsButtonDisabled(true);
72             }
73         }, [username, password]);
74
75         // This only runs once after render.
76         useEffect(() => {
77             setFocus();
78         }, []);
79
80         const setFocus = () => {
81             userInput.current!.focus();
82         };
83
84         const handleLogin = () => {
85             setError(false);
86             setHelperText('');
87             setSubmitting(true);
88             handleSubmit(username, password)
89                 .then((response) => {
90                     setSubmitting(false);
91                     if (response.data.uuid && response.data.api_token) {
92                         const apiToken = `v2/${response.data.uuid}/${response.data.api_token}`;
93                         const rd = new URL(window.location.href);
94                         const rdUrl = rd.pathname + rd.search;
95                         dispatch<any>(saveApiToken(apiToken)).finally(
96                             () => {
97                                 if ((new URL(window.location.href).pathname) !== '/my-account') {
98                                     rdUrl === '/' ? dispatch(navigateToRootProject) : dispatch(replace(rdUrl))
99                                 }
100                             }
101                         );
102                     } else {
103                         setError(true);
104                         setHelperText(response.data.message || 'Please try again');
105                         setFocus();
106                     }
107                 })
108                 .catch((err) => {
109                     setError(true);
110                     setSubmitting(false);
111                     setHelperText(`${(err.response && err.response.data && err.response.data.errors[0]) || 'Error logging in: ' + err}`);
112                     setFocus();
113                 });
114         };
115
116         const handleKeyPress = (e: any) => {
117             if (e.keyCode === 13 || e.which === 13) {
118                 if (!isButtonDisabled) {
119                     handleLogin();
120                 }
121             }
122         };
123
124         return (
125             <React.Fragment>
126                 <form className={classes.root} noValidate autoComplete="off">
127                     <Card className={classes.card}>
128                         <div className={classes.wrapper}>
129                             <CardContent>
130                                 <TextField
131                                     inputRef={userInput}
132                                     disabled={isSubmitting}
133                                     error={error} fullWidth id="username" type="email"
134                                     label="Username" margin="normal"
135                                     onChange={(e) => setUsername(e.target.value)}
136                                     onKeyPress={(e) => handleKeyPress(e)}
137                                 />
138                                 <TextField
139                                     disabled={isSubmitting}
140                                     error={error} fullWidth id="password" type="password"
141                                     label="Password" margin="normal"
142                                     helperText={helperText}
143                                     onChange={(e) => setPassword(e.target.value)}
144                                     onKeyPress={(e) => handleKeyPress(e)}
145                                 />
146                             </CardContent>
147                             <CardActions>
148                                 <Button variant="contained" size="large" color="primary"
149                                     className={classes.loginBtn} onClick={() => handleLogin()}
150                                     disabled={isSubmitting || isButtonDisabled}>
151                                     {loginLabel || 'Log in'}
152                                 </Button>
153                             </CardActions>
154                             {isSubmitting && <CircularProgress color='secondary' className={classes.progress} />}
155                         </div>
156                     </Card>
157                 </form>
158             </React.Fragment>
159         );
160     });