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