All files / src/components/pages/User UserInformationInput.tsx

38.3% Statements 18/47
45.65% Branches 21/46
30% Functions 3/10
38.3% Lines 18/47

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145                                            2x 2x 2x 2x 2x 2x 2x 2x 2x   2x 2x     2x 2x     2x                 2x                                 2x                       2x               2x                                                                                                                    
import React, {ChangeEvent, FormEvent, ReactElement, useCallback, useEffect, useState} from "react";
import {Button, Form, FormGroup} from "react-bootstrap";
import check_svg from "../../../assets/images/icons/material.io/check_circle-24px.svg";
import info_svg from "../../../assets/images/icons/material.io/info-24px.svg";
import error_svg from "../../../assets/images/icons/material.io/error-24px.svg";
import {biggerMaxStrLength, notMinStrLength} from "../../../background/methods/checkInput";
import {deleteSpaces} from "../../../background/methods/strings";
import {DEFAULT_ALERT_DURATION, MAX_PASSWORD_LENGTH, MIN_PASSWORD_LENGTH} from "../../../background/constants";
 
export interface UserInformationInputInterface {
    username: string,
    password: string,
    passwordConfirmation?: string
}
 
type UserInformationInputProps = {
    triggerAlert(duration: number, color: "primary" | "secondary" | "success" | "danger" | "warning" | "info" | "light" | "dark", message: string): void,
    submitFunction(newUser: UserInformationInputInterface): void,
    presets?: UserInformationInputInterface
}
 
export default function UserInformationInput(props: UserInformationInputProps): ReactElement {
    let {triggerAlert, submitFunction, presets} = props;
    const [username, setUsername] = useState<string>(presets?.username ?? "");
    const [password, setPassword] = useState<string>(presets?.password ?? "");
    const [passwordConfirmation, setPasswordConfirmation] = useState<string>(presets?.password ?? "");
    const [passwordInformationLength, setPasswordInformationLength] = useState<boolean>(false);
    const [passwordInformationLowercase, setPasswordInformationLowercase] = useState<boolean>(false);
    const [passwordInformationUppercase, setPasswordInformationUppercase] = useState<boolean>(false);
    const [passwordInformationNumber, setPasswordInformationNumber] = useState<boolean>(false);
    const [passwordsMatch, setPasswordsMatch] = useState<boolean>(true);
 
    const reviewPasswordMatch = useCallback((): void => {
        setPasswordsMatch(password === passwordConfirmation);
    }, [password, passwordConfirmation]);
 
    useEffect(() => {
        reviewPasswordMatch()
    }, [reviewPasswordMatch])
 
    const makePasswordInputFitRules = (input: string): [string, boolean] => {
        input = deleteSpaces(input);
        if (biggerMaxStrLength(input, MAX_PASSWORD_LENGTH)) {
            triggerAlert(DEFAULT_ALERT_DURATION, "warning", "Maximum password length exceeded. Input was undone.");
            return [input, false];
        }
        return [input, true];
    }
 
    const handlePasswordChange = (event: ChangeEvent<HTMLInputElement>) => {
        event.preventDefault();
        let newValue: string;
 
        let [stringValue, isOK]: [string, boolean] = makePasswordInputFitRules(event.target.value);
        if (!isOK) {
            newValue = password;
        } else {
            newValue = stringValue
        }
        setPasswordInformationLength(!notMinStrLength(newValue, MIN_PASSWORD_LENGTH));
        setPasswordInformationLowercase(newValue.match(/[a-z]/) !== null);
        setPasswordInformationUppercase(newValue.match(/[A-Z]/) !== null);
        setPasswordInformationNumber(newValue.match(/\d/) !== null);
        setPassword(newValue)
    }
 
    const handlePasswordConfirmationChange = async (event: ChangeEvent<HTMLInputElement>) => {
        event.preventDefault();
        let newValue: string;
        const [stringValue, isOK]: [string, boolean] = makePasswordInputFitRules(event.target.value);
        if (!isOK) {
            newValue = passwordConfirmation;
        } else {
            newValue = stringValue
        }
        setPasswordConfirmation(newValue);
    }
 
    const handleSubmit = async (event: FormEvent) => {
        event.preventDefault();
        console.log("[UserInformationInput] handleSubmit")
        reviewPasswordMatch();
        let newUser = {username: username, password: password, passwordConfirmation: passwordConfirmation}
        submitFunction(newUser)
    }
 
    return (
        <Form onSubmit={handleSubmit}>
            <FormGroup controlId="formBasicUsername">
                <Form.Label>Username</Form.Label>
                <Form.Control type={"name"} value={username}
                              onChange={event => setUsername(event.target.value)}/>
            </FormGroup>
            <Form.Group controlId="formBasicPassword">
                <Form.Label>Password</Form.Label>
                <Form.Control type="password"
                              placeholder="Must contain one number, uppercase & lowercase letter each"
                              value={password}
                              onChange={(event: ChangeEvent<HTMLInputElement>) => handlePasswordChange(event)}/>
                <div>
                    <img alt={"status icon password length"}
                         src={passwordInformationLength ? check_svg : info_svg}/>
                    <span className={"sr-only"}>{passwordInformationLength ? "Done: " : "Missing: "}</span>
                    <span className={passwordInformationLength ? "text-success" : "text-muted"}>Passwords must be between 8 and 20 characters.</span>
                </div>
                <div>
                    <img alt={"status icon password contains uppercase character"}
                         src={passwordInformationUppercase ? check_svg : info_svg}/>
                    <span
                        className={"sr-only"}>{passwordInformationUppercase ? "Done: " : "Missing: "}</span>
                    <span className={passwordInformationUppercase ? "text-success" : "text-muted"}>Passwords must be at least contain 1 uppercase character.</span>
                </div>
                <div>
                    <img alt={"status icon password contains lowercase character"}
                         src={passwordInformationLowercase ? check_svg : info_svg}/>
                    <span
                        className={"sr-only"}>{passwordInformationLowercase ? "Done: " : "Missing: "}</span>
                    <span className={passwordInformationLowercase ? "text-success" : "text-muted"}>Passwords must be at least contain 1 lowercase character.</span>
                </div>
                <div>
                    <img alt={"status icon password contains number"}
                         src={passwordInformationNumber ? check_svg : info_svg}/>
                    <span className={"sr-only"}>{passwordInformationNumber ? "Done: " : "Missing: "}</span>
                    <span className={passwordInformationNumber ? "text-success" : "text-muted"}>Passwords must be at least contain 1 number.</span>
                </div>
            </Form.Group>
            <Form.Group controlId="formConfirmPassword">
                <Form.Label>Re-enter password</Form.Label>
                <Form.Control type="password"
                              value={passwordConfirmation}
                              onChange={(event: ChangeEvent<HTMLInputElement>) => handlePasswordConfirmationChange(event)}/>
                <div>
                    <img alt={"status icon passwords match"}
                         src={!passwordConfirmation ? info_svg : passwordsMatch ? check_svg : error_svg}/>
                    <span className={"sr-only"}>{passwordsMatch ? "Done: " : "Missing: "}</span>
                    <span
                        className={!passwordConfirmation ? "text-muted" : passwordsMatch ? "text-success" : "text-danger"}>Passwords must match.</span>
                </div>
            </Form.Group>
            <Button variant="primary" type="submit">
                Submit
            </Button>
        </Form>
    )
}