Launcher/src/renderer/components/login/login-field/LoginField.tsx
Daniel Scalzi c718cc741a Add functionality to LoginField component.
State management and error detection/animation added.
TBD: Connect fields to parent component.
2020-05-23 02:03:20 -04:00

169 lines
5.3 KiB
TypeScript

import * as React from 'react'
import './LoginField.css'
enum FieldError {
REQUIRED = 'Required',
INVALID = 'Invalid Value'
}
type LoginFieldProps = {
password: boolean
}
type LoginFieldSettings = {
errorText: FieldError,
hasError: boolean,
shake: boolean,
value: string
}
export default class LoginField extends React.Component<LoginFieldProps, LoginFieldSettings> {
private readonly USERNAME_REGEX = /^[a-zA-Z0-9_]{1,16}$/
private readonly BASIC_EMAIL_REGEX = /^\S+@\S+\.\S+$/
// private readonly VALID_EMAIL_REGEX = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i
private readonly SHAKE_CLASS = 'shake'
private errorSpanRef: React.RefObject<HTMLSpanElement>
constructor(props: LoginFieldProps) {
super(props)
this.state = {
errorText: FieldError.REQUIRED,
hasError: true,
shake: false,
value: ''
}
this.errorSpanRef = React.createRef()
}
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)
}
} else {
// @ts-ignore Opacity is a number, not a string..
this.errorSpanRef.current!.style.opacity = 0
}
}
private getFieldSvg(): JSX.Element {
if(this.props.password) {
return (
<>
<svg className="loginSVG" viewBox="40 32 60.36 70.43">
<g>
<path d="M86.16,54a16.38,16.38,0,1,0-32,0H44V102.7H96V54Zm-25.9-3.39a9.89,9.89,0,1,1,19.77,0A9.78,9.78,0,0,1,79.39,54H60.89A9.78,9.78,0,0,1,60.26,50.59ZM70,96.2a6.5,6.5,0,0,1-6.5-6.5,6.39,6.39,0,0,1,3.1-5.4V67h6.5V84.11a6.42,6.42,0,0,1,3.39,5.6A6.5,6.5,0,0,1,70,96.2Z"/>
</g>
</svg>
</>
)
} else {
return (
<>
<svg className="loginSVG" viewBox="40 37 65.36 61.43">
<g>
<path d="M86.77,58.12A13.79,13.79,0,1,0,73,71.91,13.79,13.79,0,0,0,86.77,58.12M97,103.67a3.41,3.41,0,0,0,3.39-3.84,27.57,27.57,0,0,0-54.61,0,3.41,3.41,0,0,0,3.39,3.84Z"/>
</g>
</svg>
</>
)
}
}
private formatError(error: FieldError): string {
return `* ${error}`
}
private getErrorState(shake: boolean, errorText: FieldError): Partial<LoginFieldSettings> {
return {
shake,
errorText,
hasError: true,
}
}
private getValidState(): Partial<LoginFieldSettings> {
return {
hasError: false
}
}
private validateEmail = (value: string, shakeOnError: boolean): void => {
let newState
if(value) {
if(!this.BASIC_EMAIL_REGEX.test(value) && !this.USERNAME_REGEX.test(value)) {
newState = this.getErrorState(shakeOnError, FieldError.INVALID)
} else {
newState = this.getValidState()
}
} else {
newState = this.getErrorState(shakeOnError, FieldError.REQUIRED)
}
this.setState({
...this.state,
...newState,
value
})
}
private validatePassword = (value: string, shakeOnError: boolean): void => {
let newState
if(value) {
newState = this.getValidState()
} else {
newState = this.getErrorState(shakeOnError, FieldError.REQUIRED)
}
this.setState({
...this.state,
...newState,
value
})
}
private getValidateFunction(): (value: string, shakeOnError: boolean) => void {
return this.props.password ? this.validatePassword : this.validateEmail
}
private handleBlur = (event: React.FocusEvent<HTMLInputElement>): void => {
this.getValidateFunction()(event.target.value, true)
}
private handleInput = (event: React.FormEvent<HTMLInputElement>): void => {
this.getValidateFunction()((event.target as HTMLInputElement).value, false)
}
render() {
return (
<>
<div className="loginFieldContainer">
{this.getFieldSvg()}
<span
className="loginErrorSpan"
ref={this.errorSpanRef}>
{this.formatError(this.state.errorText)}
</span>
<input
className="loginField"
type={this.props.password ? 'password' : 'text'}
value={this.state.value}
placeholder={this.props.password ? 'PASSWORD' : 'EMAIL OR USERNAME'}
onBlur={this.handleBlur}
onInput={this.handleInput} />
</div>
</>
)
}
}