Kinda working checkpoint magic
This commit is contained in:
parent
4eeec58c59
commit
2a626a3e1f
4 changed files with 119 additions and 3 deletions
|
@ -50,6 +50,7 @@ input[type=number] {
|
|||
*/
|
||||
.input:not(.open-label) {
|
||||
@apply .appearance-none .p-3 .rounded .border .border-neutral-200 .text-neutral-800 .w-full;
|
||||
min-width: 0;
|
||||
transition: border 150ms linear;
|
||||
|
||||
&:focus {
|
||||
|
|
105
resources/scripts/components/auth/LoginCheckpointContainer.tsx
Normal file
105
resources/scripts/components/auth/LoginCheckpointContainer.tsx
Normal file
|
@ -0,0 +1,105 @@
|
|||
import * as React from 'react';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import { connect } from 'react-redux';
|
||||
import { pushFlashMessage, clearAllFlashMessages } from '@/redux/actions/flash';
|
||||
import NetworkErrorMessage from '@/components/NetworkErrorMessage';
|
||||
|
||||
type State = Readonly<{
|
||||
isLoading: boolean;
|
||||
errorMessage?: string;
|
||||
code: string;
|
||||
}>;
|
||||
|
||||
class LoginCheckpointContainer extends React.PureComponent<RouteComponentProps, State> {
|
||||
state: State = {
|
||||
code: '',
|
||||
isLoading: false,
|
||||
};
|
||||
|
||||
moveToNextInput (e: React.KeyboardEvent<HTMLInputElement>, isBackspace: boolean = false) {
|
||||
const form = e.currentTarget.form;
|
||||
|
||||
if (form) {
|
||||
const index = Array.prototype.indexOf.call(form, e.currentTarget);
|
||||
const element = form.elements[index + (isBackspace ? -1 : 1)];
|
||||
|
||||
// @ts-ignore
|
||||
element && element.focus();
|
||||
}
|
||||
}
|
||||
|
||||
handleNumberInput = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
const number = Number(e.key);
|
||||
if (isNaN(number)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(s => ({ code: s.code + number.toString() }));
|
||||
this.moveToNextInput(e);
|
||||
};
|
||||
|
||||
handleBackspace = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
const isBackspace = e.key === 'Delete' || e.key === 'Backspace';
|
||||
|
||||
if (!isBackspace || e.currentTarget.value.length > 0) {
|
||||
e.currentTarget.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(s => ({ code: s.code.substring(0, s.code.length - 2) }));
|
||||
e.currentTarget.value = '';
|
||||
this.moveToNextInput(e, true);
|
||||
};
|
||||
|
||||
render () {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<h2 className={'text-center text-neutral-100 font-medium py-4'}>
|
||||
Device Checkpoint
|
||||
</h2>
|
||||
<NetworkErrorMessage message={this.state.errorMessage}/>
|
||||
<form className={'login-box'} onSubmit={() => null}>
|
||||
<p className={'text-sm text-neutral-700'}>
|
||||
This account is protected with two-factor authentication. Please provide an authentication
|
||||
code from your device in order to continue.
|
||||
</p>
|
||||
<div className={'flex mt-6'}>
|
||||
{
|
||||
[1, 2, 3, 4, 5, 6].map((_, index) => (
|
||||
<input
|
||||
autoFocus={index === 0}
|
||||
key={`input_${index}`}
|
||||
type={'number'}
|
||||
onKeyPress={this.handleNumberInput}
|
||||
onKeyDown={this.handleBackspace}
|
||||
maxLength={1}
|
||||
className={`input block flex-1 text-center text-lg ${index === 5 ? undefined : 'mr-6'}`}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<div className={'mt-6'}>
|
||||
<button
|
||||
type={'submit'}
|
||||
className={'btn btn-primary btn-jumbo'}
|
||||
disabled={this.state.isLoading || this.state.code.length !== 6}
|
||||
>
|
||||
{this.state.isLoading ?
|
||||
<span className={'spinner white'}> </span>
|
||||
:
|
||||
'Continue'
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
pushFlashMessage,
|
||||
clearAllFlashMessages,
|
||||
};
|
||||
|
||||
export default connect(null, mapDispatchToProps)(LoginCheckpointContainer);
|
|
@ -1,6 +1,6 @@
|
|||
import * as React from 'react';
|
||||
import OpenInputField from '@/components/forms/OpenInputField';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link, RouteComponentProps } from 'react-router-dom';
|
||||
import login from '@/api/auth/login';
|
||||
import { httpErrorToHuman } from '@/api/http';
|
||||
import NetworkErrorMessage from '@/components/NetworkErrorMessage';
|
||||
|
@ -12,7 +12,7 @@ type State = Readonly<{
|
|||
password?: string;
|
||||
}>;
|
||||
|
||||
export default class LoginContainer extends React.PureComponent<{}, State> {
|
||||
export default class LoginContainer extends React.PureComponent<RouteComponentProps, State> {
|
||||
username = React.createRef<HTMLInputElement>();
|
||||
|
||||
state: State = {
|
||||
|
@ -27,7 +27,15 @@ export default class LoginContainer extends React.PureComponent<{}, State> {
|
|||
this.setState({ isLoading: true }, () => {
|
||||
login(username!, password!)
|
||||
.then(response => {
|
||||
if (response.complete) {
|
||||
// @ts-ignore
|
||||
window.location = response.intended || '/';
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.history.replace('/login/checkpoint', {
|
||||
token: response.token,
|
||||
});
|
||||
})
|
||||
.catch(error => this.setState({
|
||||
isLoading: false,
|
||||
|
|
|
@ -5,6 +5,7 @@ import { CSSTransition, TransitionGroup } from 'react-transition-group';
|
|||
import ForgotPasswordContainer from '@/components/auth/ForgotPasswordContainer';
|
||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||
import ResetPasswordContainer from '@/components/auth/ResetPasswordContainer';
|
||||
import LoginCheckpointContainer from '@/components/auth/LoginCheckpointContainer';
|
||||
|
||||
export default class AuthenticationRouter extends React.PureComponent {
|
||||
render () {
|
||||
|
@ -17,7 +18,8 @@ export default class AuthenticationRouter extends React.PureComponent {
|
|||
<section>
|
||||
<FlashMessageRender/>
|
||||
<Switch location={location}>
|
||||
<Route path={'/login'} component={LoginContainer}/>
|
||||
<Route path={'/login'} component={LoginContainer} exact/>
|
||||
<Route path={'/login/checkpoint'} component={LoginCheckpointContainer}/>
|
||||
<Route path={'/password'} component={ForgotPasswordContainer} exact/>
|
||||
<Route path={'/password/reset/:token'} component={ResetPasswordContainer}/>
|
||||
<Route path={'/checkpoint'}/>
|
||||
|
|
Loading…
Reference in a new issue