diff --git a/src/renderer/components/login/login-field/LoginField.tsx b/src/renderer/components/login/login-field/LoginField.tsx index 0a7709d8..67a8bd78 100644 --- a/src/renderer/components/login/login-field/LoginField.tsx +++ b/src/renderer/components/login/login-field/LoginField.tsx @@ -2,14 +2,59 @@ import * as React from 'react' import './LoginField.css' -type LoginFieldProps = { - password: boolean, - +enum FieldError { + REQUIRED = 'Required', + INVALID = 'Invalid Value' } -export default class LoginField extends React.Component { +type LoginFieldProps = { + password: boolean +} - getFieldSvg(): JSX.Element { +type LoginFieldSettings = { + errorText: FieldError, + hasError: boolean, + shake: boolean, + value: string +} + +export default class LoginField extends React.Component { + + 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 + + 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 ( @@ -37,29 +82,85 @@ export default class LoginField extends React.Component { } } - getDefaultErrorMessage(): string { - if(this.props.password) { - return '* Required' - } else { - return '* Invalid Value' + private formatError(error: FieldError): string { + return `* ${error}` + } + + private getErrorState(shake: boolean, errorText: FieldError): Partial { + return { + shake, + errorText, + hasError: true, } } - getPlaceholder(): string { - if(this.props.password) { - return 'PASSWORD' - } else { - return 'EMAIL OR USERNAME' + private getValidState(): Partial { + 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): void => { + this.getValidateFunction()(event.target.value, true) + } + + private handleInput = (event: React.FormEvent): void => { + this.getValidateFunction()((event.target as HTMLInputElement).value, false) + } + render() { return ( <>
{this.getFieldSvg()} - {this.getDefaultErrorMessage()} - + + {this.formatError(this.state.errorText)} + +
)