mirror of
https://github.com/dscalzi/HeliosLauncher.git
synced 2024-12-22 11:42:14 -08:00
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'
|
import './Login.css'
|
||||||
|
|
||||||
|
enum LoginStatus {
|
||||||
|
IDLE,
|
||||||
|
LOADING,
|
||||||
|
SUCCESS,
|
||||||
|
ERROR
|
||||||
|
}
|
||||||
|
|
||||||
type LoginProperties = {
|
type LoginProperties = {
|
||||||
cancelable: boolean
|
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 {
|
getCancelButton(): JSX.Element {
|
||||||
if(this.props.cancelable) {
|
if(this.props.cancelable) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div id="loginCancelContainer">
|
<div id="loginCancelContainer">
|
||||||
<button id="loginCancelButton">
|
<button id="loginCancelButton" disabled={this.isFormDisabled()}>
|
||||||
<div id="loginCancelIcon">X</div>
|
<div id="loginCancelIcon">X</div>
|
||||||
<span id="loginCancelText">Cancel</span>
|
<span id="loginCancelText">Cancel</span>
|
||||||
</button>
|
</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() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div id="loginContainer">
|
<div id="loginContainer">
|
||||||
{this.getCancelButton()}
|
{this.getCancelButton()}
|
||||||
<div id="loginContent">
|
<div id="loginContent">
|
||||||
<form id="loginForm">
|
<form id="loginForm" onSubmit={this.handleFormSubmit}>
|
||||||
<img id="loginImageSeal" src="../images/SealCircle.png"/>
|
<img id="loginImageSeal" src="../images/SealCircle.png"/>
|
||||||
<span id="loginSubheader">MINECRAFT LOGIN</span>
|
<span id="loginSubheader">MINECRAFT LOGIN</span>
|
||||||
|
|
||||||
<LoginField password={false} />
|
<LoginField
|
||||||
<LoginField password={true} />
|
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">
|
<div id="loginOptions">
|
||||||
<span className="loginSpanDim">
|
<span className="loginSpanDim">
|
||||||
<a href="https://my.minecraft.net/en-us/password/forgot/">forgot password?</a>
|
<a href="https://my.minecraft.net/en-us/password/forgot/">forgot password?</a>
|
||||||
</span>
|
</span>
|
||||||
<label id="checkmarkContainer">
|
<label id="checkmarkContainer" {...(this.isFormDisabled() ? {disabled: true} : {})} >
|
||||||
<input id="loginRememberOption" type="checkbox" checked></input>
|
<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 id="loginRememberText" className="loginSpanDim">remember me?</span>
|
||||||
<span className="loginCheckmark"></span>
|
<span className="loginCheckmark"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<button id="loginButton" disabled>
|
<button
|
||||||
|
id="loginButton"
|
||||||
|
disabled={!this.canSave()}
|
||||||
|
onClick={this.handleLoginButtonClick}
|
||||||
|
{...(this.isLoading() ? {loading: "true"} : {})}>
|
||||||
<div id="loginButtonContent">
|
<div id="loginButtonContent">
|
||||||
LOGIN
|
{this.getButtonText()}
|
||||||
<svg id="loginSVG" viewBox="0 0 24.87 13.97">
|
<svg id="loginSVG" viewBox="0 0 24.87 13.97">
|
||||||
<defs>
|
<defs>
|
||||||
<style>{'.arrowLine{transition: 0.25s ease;}'}</style> {/** TODO */}
|
<style>{'.arrowLine{transition: 0.25s ease;}'}</style> {/** TODO */}
|
||||||
|
@ -8,17 +8,19 @@ enum FieldError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type LoginFieldProps = {
|
type LoginFieldProps = {
|
||||||
password: boolean
|
password: boolean,
|
||||||
|
disabled: boolean,
|
||||||
|
onValidityChange: (valid: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoginFieldSettings = {
|
type LoginFieldState = {
|
||||||
errorText: FieldError,
|
errorText: FieldError,
|
||||||
hasError: boolean,
|
hasError: boolean,
|
||||||
shake: boolean,
|
shake: boolean,
|
||||||
value: string
|
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 USERNAME_REGEX = /^[a-zA-Z0-9_]{1,16}$/
|
||||||
private readonly BASIC_EMAIL_REGEX = /^\S+@\S+\.\S+$/
|
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 readonly SHAKE_CLASS = 'shake'
|
||||||
|
|
||||||
private errorSpanRef: React.RefObject<HTMLSpanElement>
|
private errorSpanRef: React.RefObject<HTMLSpanElement>
|
||||||
|
private internalTrigger = false // Indicates that the component updated from an internal trigger.
|
||||||
|
|
||||||
constructor(props: LoginFieldProps) {
|
constructor(props: LoginFieldProps) {
|
||||||
super(props)
|
super(props)
|
||||||
@ -40,18 +43,25 @@ export default class LoginField extends React.Component<LoginFieldProps, LoginFi
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
if(this.state.hasError) {
|
if(this.internalTrigger) {
|
||||||
// @ts-ignore Opacity is a number, not a string..
|
if(this.state.hasError) {
|
||||||
this.errorSpanRef.current!.style.opacity = 1
|
// @ts-ignore Opacity is a number, not a string..
|
||||||
if(this.state.shake) {
|
this.errorSpanRef.current!.style.opacity = 1
|
||||||
this.errorSpanRef.current!.classList.remove(this.SHAKE_CLASS)
|
if(this.state.shake) {
|
||||||
void this.errorSpanRef.current!.offsetWidth
|
this.errorSpanRef.current!.classList.remove(this.SHAKE_CLASS)
|
||||||
this.errorSpanRef.current!.classList.add(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 {
|
private getFieldSvg(): JSX.Element {
|
||||||
@ -86,7 +96,7 @@ export default class LoginField extends React.Component<LoginFieldProps, LoginFi
|
|||||||
return `* ${error}`
|
return `* ${error}`
|
||||||
}
|
}
|
||||||
|
|
||||||
private getErrorState(shake: boolean, errorText: FieldError): Partial<LoginFieldSettings> {
|
private getErrorState(shake: boolean, errorText: FieldError): Partial<LoginFieldState> & Required<{hasError: boolean}> {
|
||||||
return {
|
return {
|
||||||
shake,
|
shake,
|
||||||
errorText,
|
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 {
|
return {
|
||||||
hasError: false
|
hasError: false
|
||||||
}
|
}
|
||||||
@ -111,11 +121,13 @@ export default class LoginField extends React.Component<LoginFieldProps, LoginFi
|
|||||||
} else {
|
} else {
|
||||||
newState = this.getErrorState(shakeOnError, FieldError.REQUIRED)
|
newState = this.getErrorState(shakeOnError, FieldError.REQUIRED)
|
||||||
}
|
}
|
||||||
|
this.internalTrigger = true
|
||||||
this.setState({
|
this.setState({
|
||||||
...this.state,
|
...this.state,
|
||||||
...newState,
|
...newState,
|
||||||
value
|
value
|
||||||
})
|
})
|
||||||
|
this.props.onValidityChange(!newState.hasError)
|
||||||
}
|
}
|
||||||
|
|
||||||
private validatePassword = (value: string, shakeOnError: boolean): void => {
|
private validatePassword = (value: string, shakeOnError: boolean): void => {
|
||||||
@ -125,11 +137,13 @@ export default class LoginField extends React.Component<LoginFieldProps, LoginFi
|
|||||||
} else {
|
} else {
|
||||||
newState = this.getErrorState(shakeOnError, FieldError.REQUIRED)
|
newState = this.getErrorState(shakeOnError, FieldError.REQUIRED)
|
||||||
}
|
}
|
||||||
|
this.internalTrigger = true
|
||||||
this.setState({
|
this.setState({
|
||||||
...this.state,
|
...this.state,
|
||||||
...newState,
|
...newState,
|
||||||
value
|
value
|
||||||
})
|
})
|
||||||
|
this.props.onValidityChange(!newState.hasError)
|
||||||
}
|
}
|
||||||
|
|
||||||
private getValidateFunction(): (value: string, shakeOnError: boolean) => void {
|
private getValidateFunction(): (value: string, shakeOnError: boolean) => void {
|
||||||
@ -156,8 +170,9 @@ export default class LoginField extends React.Component<LoginFieldProps, LoginFi
|
|||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
className="loginField"
|
className="loginField"
|
||||||
|
disabled={this.props.disabled}
|
||||||
type={this.props.password ? 'password' : 'text'}
|
type={this.props.password ? 'password' : 'text'}
|
||||||
value={this.state.value}
|
defaultValue={this.state.value}
|
||||||
placeholder={this.props.password ? 'PASSWORD' : 'EMAIL OR USERNAME'}
|
placeholder={this.props.password ? 'PASSWORD' : 'EMAIL OR USERNAME'}
|
||||||
onBlur={this.handleBlur}
|
onBlur={this.handleBlur}
|
||||||
onInput={this.handleInput} />
|
onInput={this.handleInput} />
|
||||||
|
Loading…
Reference in New Issue
Block a user