1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import * as React from 'react';
6 import { useState, useEffect, useRef } from 'react';
7 import { withStyles, WithStyles, StyleRulesCallback } from '@material-ui/core/styles';
8 import CircularProgress from '@material-ui/core/CircularProgress';
9 import { Button, Card, CardContent, TextField, CardActions } from '@material-ui/core';
10 import { green } from '@material-ui/core/colors';
11 import { AxiosPromise } from 'axios';
12 import { DispatchProp } from 'react-redux';
13 import { saveApiToken } from '~/store/auth/auth-action';
14 import { navigateToRootProject } from '~/store/navigation/navigation-action';
16 type CssRules = 'root' | 'loginBtn' | 'card' | 'wrapper' | 'progress';
18 const styles: StyleRulesCallback<CssRules> = theme => ({
23 margin: `${theme.spacing.unit} auto`
26 marginTop: theme.spacing.unit,
30 marginTop: theme.spacing.unit,
34 margin: theme.spacing.unit,
47 type LoginFormProps = DispatchProp<any> & WithStyles<CssRules> & {
48 handleSubmit: (username: string, password: string) => AxiosPromise;
52 export const LoginForm = withStyles(styles)(
53 ({ handleSubmit, loginLabel, dispatch, classes }: LoginFormProps) => {
54 const userInput = useRef<HTMLInputElement>(null);
55 const [username, setUsername] = useState('');
56 const [password, setPassword] = useState('');
57 const [isButtonDisabled, setIsButtonDisabled] = useState(true);
58 const [isSubmitting, setSubmitting] = useState(false);
59 const [helperText, setHelperText] = useState('');
60 const [error, setError] = useState(false);
65 if (username.trim() && password.trim()) {
66 setIsButtonDisabled(false);
68 setIsButtonDisabled(true);
70 }, [username, password]);
72 // This only runs once after render.
77 const setFocus = () => {
78 userInput.current!.focus();
81 const handleLogin = () => {
85 handleSubmit(username, password)
88 if (response.data.uuid && response.data.api_token) {
89 const apiToken = `v2/${response.data.uuid}/${response.data.api_token}`;
90 dispatch<any>(saveApiToken(apiToken)).finally(
91 () => dispatch(navigateToRootProject));
94 setHelperText(response.data.message || 'Please try again');
100 setSubmitting(false);
101 setHelperText(`${err.response && err.response.data && err.response.data.errors[0] || 'Error logging in: '+err}`);
106 const handleKeyPress = (e: any) => {
107 if (e.keyCode === 13 || e.which === 13) {
108 if (!isButtonDisabled) {
116 <form className={classes.root} noValidate autoComplete="off">
117 <Card className={classes.card}>
118 <div className={classes.wrapper}>
122 disabled={isSubmitting}
123 error={error} fullWidth id="username" type="email"
124 label="Username" margin="normal"
125 onChange={(e) => setUsername(e.target.value)}
126 onKeyPress={(e) => handleKeyPress(e)}
129 disabled={isSubmitting}
130 error={error} fullWidth id="password" type="password"
131 label="Password" margin="normal"
132 helperText={helperText}
133 onChange={(e) => setPassword(e.target.value)}
134 onKeyPress={(e) => handleKeyPress(e)}
138 <Button variant="contained" size="large" color="primary"
139 className={classes.loginBtn} onClick={() => handleLogin()}
140 disabled={isSubmitting || isButtonDisabled}>
141 {loginLabel || 'Log in'}
144 { isSubmitting && <CircularProgress color='secondary' className={classes.progress} />}