Add login behavior up to loading state.
The remaining functionality depends on implementing a new AuthManager and overlay system.
This commit is contained in:
parent
c718cc741a
commit
5944f70a2a
@ -3,18 +3,47 @@ import LoginField from './login-field/LoginField'
|
||||
|
||||
import './Login.css'
|
||||
|
||||
enum LoginStatus {
|
||||
IDLE,
|
||||
LOADING,
|
||||
SUCCESS,
|
||||
ERROR
|
||||
}
|
||||
|
||||
type LoginProperties = {
|
||||
cancelable: boolean
|
||||
}
|
||||
|
||||
export default class Login extends React.Component<LoginProperties> {
|
||||
type LoginState = {
|
||||
rememberMe: boolean,
|
||||
userValid: boolean,
|
||||
passValid: boolean,
|
||||
status: LoginStatus
|
||||
}
|
||||
|
||||
export default class Login extends React.Component<LoginProperties, LoginState> {
|
||||
|
||||
private userRef: React.RefObject<LoginField>
|
||||
private passRef: React.RefObject<LoginField>
|
||||
|
||||
constructor(props: LoginProperties) {
|
||||
super(props)
|
||||
this.state = {
|
||||
rememberMe: true,
|
||||
userValid: false,
|
||||
passValid: false,
|
||||
status: LoginStatus.IDLE
|
||||
}
|
||||
this.userRef = React.createRef()
|
||||
this.passRef = React.createRef()
|
||||
}
|
||||
|
||||
getCancelButton(): JSX.Element {
|
||||
if(this.props.cancelable) {
|
||||
return (
|
||||
<>
|
||||
<div id="loginCancelContainer">
|
||||
<button id="loginCancelButton">
|
||||
<button id="loginCancelButton" disabled={this.isFormDisabled()}>
|
||||
<div id="loginCancelIcon">X</div>
|
||||
<span id="loginCancelText">Cancel</span>
|
||||
</button>
|
||||
@ -26,32 +55,108 @@ export default class Login extends React.Component<LoginProperties> {
|
||||
}
|
||||
}
|
||||
|
||||
isFormDisabled = (): boolean => {
|
||||
return this.state.status !== LoginStatus.IDLE
|
||||
}
|
||||
|
||||
isLoading = (): boolean => {
|
||||
return this.state.status === LoginStatus.LOADING
|
||||
}
|
||||
|
||||
canSave = (): boolean => {
|
||||
return this.state.passValid && this.state.userValid && !this.isFormDisabled()
|
||||
}
|
||||
|
||||
getButtonText = (): string => {
|
||||
switch(this.state.status) {
|
||||
case LoginStatus.LOADING:
|
||||
return 'LOGGING IN'
|
||||
case LoginStatus.SUCCESS:
|
||||
return 'SUCCESS'
|
||||
case LoginStatus.ERROR:
|
||||
case LoginStatus.IDLE:
|
||||
return 'LOGIN'
|
||||
}
|
||||
}
|
||||
|
||||
handleUserValidityChange = (valid: boolean): void => {
|
||||
this.setState({
|
||||
...this.state,
|
||||
userValid: valid
|
||||
})
|
||||
}
|
||||
|
||||
handlePassValidityChange = (valid: boolean): void => {
|
||||
this.setState({
|
||||
...this.state,
|
||||
passValid: valid
|
||||
})
|
||||
}
|
||||
|
||||
handleCheckBoxChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
this.setState({
|
||||
...this.state,
|
||||
rememberMe: event.target.checked
|
||||
})
|
||||
}
|
||||
|
||||
handleFormSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
handleLoginButtonClick = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>): void => {
|
||||
console.log(this.userRef.current!.getValue())
|
||||
console.log(this.passRef.current!.getValue())
|
||||
this.setState({
|
||||
...this.state,
|
||||
status: LoginStatus.LOADING
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<div id="loginContainer">
|
||||
{this.getCancelButton()}
|
||||
<div id="loginContent">
|
||||
<form id="loginForm">
|
||||
<form id="loginForm" onSubmit={this.handleFormSubmit}>
|
||||
<img id="loginImageSeal" src="../images/SealCircle.png"/>
|
||||
<span id="loginSubheader">MINECRAFT LOGIN</span>
|
||||
|
||||
<LoginField password={false} />
|
||||
<LoginField password={true} />
|
||||
<LoginField
|
||||
ref={this.userRef}
|
||||
password={false}
|
||||
disabled={this.isFormDisabled()}
|
||||
onValidityChange={this.handleUserValidityChange} />
|
||||
<LoginField
|
||||
ref={this.passRef}
|
||||
password={true}
|
||||
disabled={this.isFormDisabled()}
|
||||
onValidityChange={this.handlePassValidityChange} />
|
||||
|
||||
<div id="loginOptions">
|
||||
<span className="loginSpanDim">
|
||||
<a href="https://my.minecraft.net/en-us/password/forgot/">forgot password?</a>
|
||||
</span>
|
||||
<label id="checkmarkContainer">
|
||||
<input id="loginRememberOption" type="checkbox" checked></input>
|
||||
<label id="checkmarkContainer" {...(this.isFormDisabled() ? {disabled: true} : {})} >
|
||||
<input
|
||||
id="loginRememberOption"
|
||||
type="checkbox"
|
||||
checked={this.state.rememberMe}
|
||||
onChange={this.handleCheckBoxChange}
|
||||
disabled={this.isFormDisabled()}
|
||||
></input>
|
||||
<span id="loginRememberText" className="loginSpanDim">remember me?</span>
|
||||
<span className="loginCheckmark"></span>
|
||||
</label>
|
||||
</div>
|
||||
<button id="loginButton" disabled>
|
||||
<button
|
||||
id="loginButton"
|
||||
disabled={!this.canSave()}
|
||||
onClick={this.handleLoginButtonClick}
|
||||
{...(this.isLoading() ? {loading: "true"} : {})}>
|
||||
<div id="loginButtonContent">
|
||||
LOGIN
|
||||
{this.getButtonText()}
|
||||
<svg id="loginSVG" viewBox="0 0 24.87 13.97">
|
||||
<defs>
|
||||
<style>{'.arrowLine{transition: 0.25s ease;}'}</style> {/** TODO */}
|
||||
|
@ -8,17 +8,19 @@ enum FieldError {
|
||||
}
|
||||
|
||||
type LoginFieldProps = {
|
||||
password: boolean
|
||||
password: boolean,
|
||||
disabled: boolean,
|
||||
onValidityChange: (valid: boolean) => void
|
||||
}
|
||||
|
||||
type LoginFieldSettings = {
|
||||
type LoginFieldState = {
|
||||
errorText: FieldError,
|
||||
hasError: boolean,
|
||||
shake: boolean,
|
||||
value: string
|
||||
}
|
||||
|
||||
export default class LoginField extends React.Component<LoginFieldProps, LoginFieldSettings> {
|
||||
export default class LoginField extends React.Component<LoginFieldProps, LoginFieldState> {
|
||||
|
||||
private readonly USERNAME_REGEX = /^[a-zA-Z0-9_]{1,16}$/
|
||||
private readonly BASIC_EMAIL_REGEX = /^\S+@\S+\.\S+$/
|
||||
@ -27,6 +29,7 @@ export default class LoginField extends React.Component<LoginFieldProps, LoginFi
|
||||
private readonly SHAKE_CLASS = 'shake'
|
||||
|
||||
private errorSpanRef: React.RefObject<HTMLSpanElement>
|
||||
private internalTrigger = false // Indicates that the component updated from an internal trigger.
|
||||
|
||||
constructor(props: LoginFieldProps) {
|
||||
super(props)
|
||||
@ -40,18 +43,25 @@ export default class LoginField extends React.Component<LoginFieldProps, LoginFi
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if(this.state.hasError) {
|
||||
// @ts-ignore Opacity is a number, not a string..
|
||||
this.errorSpanRef.current!.style.opacity = 1
|
||||
if(this.state.shake) {
|
||||
this.errorSpanRef.current!.classList.remove(this.SHAKE_CLASS)
|
||||
void this.errorSpanRef.current!.offsetWidth
|
||||
this.errorSpanRef.current!.classList.add(this.SHAKE_CLASS)
|
||||
if(this.internalTrigger) {
|
||||
if(this.state.hasError) {
|
||||
// @ts-ignore Opacity is a number, not a string..
|
||||
this.errorSpanRef.current!.style.opacity = 1
|
||||
if(this.state.shake) {
|
||||
this.errorSpanRef.current!.classList.remove(this.SHAKE_CLASS)
|
||||
void this.errorSpanRef.current!.offsetWidth
|
||||
this.errorSpanRef.current!.classList.add(this.SHAKE_CLASS)
|
||||
}
|
||||
} else {
|
||||
// @ts-ignore Opacity is a number, not a string..
|
||||
this.errorSpanRef.current!.style.opacity = 0
|
||||
}
|
||||
} else {
|
||||
// @ts-ignore Opacity is a number, not a string..
|
||||
this.errorSpanRef.current!.style.opacity = 0
|
||||
}
|
||||
this.internalTrigger = false
|
||||
}
|
||||
|
||||
public getValue(): string {
|
||||
return this.state.value
|
||||
}
|
||||
|
||||
private getFieldSvg(): JSX.Element {
|
||||
@ -86,7 +96,7 @@ export default class LoginField extends React.Component<LoginFieldProps, LoginFi
|
||||
return `* ${error}`
|
||||
}
|
||||
|
||||
private getErrorState(shake: boolean, errorText: FieldError): Partial<LoginFieldSettings> {
|
||||
private getErrorState(shake: boolean, errorText: FieldError): Partial<LoginFieldState> & Required<{hasError: boolean}> {
|
||||
return {
|
||||
shake,
|
||||
errorText,
|
||||
@ -94,7 +104,7 @@ export default class LoginField extends React.Component<LoginFieldProps, LoginFi
|
||||
}
|
||||
}
|
||||
|
||||
private getValidState(): Partial<LoginFieldSettings> {
|
||||
private getValidState(): Partial<LoginFieldState> & Required<{hasError: boolean}> {
|
||||
return {
|
||||
hasError: false
|
||||
}
|
||||
@ -111,11 +121,13 @@ export default class LoginField extends React.Component<LoginFieldProps, LoginFi
|
||||
} else {
|
||||
newState = this.getErrorState(shakeOnError, FieldError.REQUIRED)
|
||||
}
|
||||
this.internalTrigger = true
|
||||
this.setState({
|
||||
...this.state,
|
||||
...newState,
|
||||
value
|
||||
})
|
||||
this.props.onValidityChange(!newState.hasError)
|
||||
}
|
||||
|
||||
private validatePassword = (value: string, shakeOnError: boolean): void => {
|
||||
@ -125,11 +137,13 @@ export default class LoginField extends React.Component<LoginFieldProps, LoginFi
|
||||
} else {
|
||||
newState = this.getErrorState(shakeOnError, FieldError.REQUIRED)
|
||||
}
|
||||
this.internalTrigger = true
|
||||
this.setState({
|
||||
...this.state,
|
||||
...newState,
|
||||
value
|
||||
})
|
||||
this.props.onValidityChange(!newState.hasError)
|
||||
}
|
||||
|
||||
private getValidateFunction(): (value: string, shakeOnError: boolean) => void {
|
||||
@ -156,8 +170,9 @@ export default class LoginField extends React.Component<LoginFieldProps, LoginFi
|
||||
</span>
|
||||
<input
|
||||
className="loginField"
|
||||
disabled={this.props.disabled}
|
||||
type={this.props.password ? 'password' : 'text'}
|
||||
value={this.state.value}
|
||||
defaultValue={this.state.value}
|
||||
placeholder={this.props.password ? 'PASSWORD' : 'EMAIL OR USERNAME'}
|
||||
onBlur={this.handleBlur}
|
||||
onInput={this.handleInput} />
|
||||
|
Loading…
Reference in New Issue
Block a user