All files / client/src/components PasswordChangeForm.tsx

97.43% Statements 38/39
95% Branches 19/20
100% Functions 9/9
97.43% Lines 38/39

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 146 147 148 149 150                                    520x       132x   132x   132x   132x   132x 15x     132x 12x 3x   9x 1x   8x     132x 12x   12x 4x     4x     8x         8x 2x           6x           132x 6x 3x 3x 3x         132x 6x                                                                           1x         1x 126x             1x   92x   6x       1x       1x   80x    
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { ChangeEvent, ReactElement, useState } from "react";
import { useTranslation } from "react-i18next";
import styled, { css } from "styled-components";
import {
  PASSWORD_LENGTH_MAX,
  PASSWORD_LENGTH_MIN,
} from "shared/constants/validation";
import { Button } from "client/components/Button";
import { ControlledInput } from "client/components/ControlledInput";
import { ButtonStyle } from "client/components/componentStyles";
import { updateUserPassword } from "client/services/userServices";
 
interface Props {
  usernameToUpdate: string;
  isLocalLogin: boolean;
}
 
export const PasswordChangeForm = ({
  usernameToUpdate,
  isLocalLogin,
}: Props): ReactElement | null => {
  const { t } = useTranslation();
 
  const [changePasswordInput, setChangePasswordInput] = useState<string>("");
  const [passwordChangeMessage, setPasswordChangeMessage] =
    useState<ReactElement>(<Message />);
  const [passwordFieldType, setPasswordFieldType] =
    useState<string>("password");
 
  const handlePasswordChange = (event: ChangeEvent<HTMLInputElement>): void => {
    setChangePasswordInput(event.target.value);
  };
 
  const passwordLength = (value: string): string | null => {
    if (value.length < PASSWORD_LENGTH_MIN) {
      return t("validation.tooShort", { length: String(PASSWORD_LENGTH_MIN) });
    }
    if (value.length > PASSWORD_LENGTH_MAX) {
      return t("validation.tooLong", { length: String(PASSWORD_LENGTH_MAX) });
    }
    return null;
  };
 
  const submitUpdatePassword = async (): Promise<void> => {
    const validationError = passwordLength(changePasswordInput);
 
    if (validationError) {
      setPasswordChangeMessage(
        <Message error={true}>{validationError}</Message>,
      );
      return;
    }
 
    const response = await updateUserPassword(
      usernameToUpdate,
      changePasswordInput,
    );
 
    if (response.status === "error") {
      setPasswordChangeMessage(
        <Message error={true}>
          {t("passwordManagement.changingPasswordError")}
        </Message>,
      );
    } else {
      setPasswordChangeMessage(
        <Message>{t("passwordManagement.changingPasswordSuccess")}</Message>,
      );
    }
  };
 
  const togglePasswordVisibility = (): void => {
    if (passwordFieldType === "password") {
      setPasswordFieldType("text");
    } else Eif (passwordFieldType === "text") {
      setPasswordFieldType("password");
    }
  };
 
  // Password change is only available for local-login accounts (Kompassi users reset via Kompassi)
  if (!isLocalLogin) {
    return null;
  }
 
  return (
    <>
      <StyledLabel>{t("passwordManagement.changePassword")}</StyledLabel>
      <InputContainer>
        <ControlledInput
          type={passwordFieldType}
          key="new-password"
          placeholder={t("passwordManagement.newPassword")}
          value={changePasswordInput}
          onChange={handlePasswordChange}
        />
 
        <FormFieldIcon>
          <FontAwesomeIcon
            icon={passwordFieldType === "password" ? "eye" : "eye-slash"}
            onClick={togglePasswordVisibility}
            aria-label={t(
              passwordFieldType === "password"
                ? "iconAltText.showPassword"
                : "iconAltText.hidePassword",
            )}
          />
        </FormFieldIcon>
      </InputContainer>
      <ButtonWithMargin
        onClick={submitUpdatePassword}
        buttonStyle={ButtonStyle.PRIMARY}
      >
        {t("button.save")}
      </ButtonWithMargin>
      {passwordChangeMessage}
    </>
  );
};
 
const InputContainer = styled.div`
  display: flex;
  align-items: center;
`;
 
const FormFieldIcon = styled.span`
  font-size: ${(props) => props.theme.fontSizeLarge};
`;
 
interface MessageProps {
  error?: boolean;
}
 
const Message = styled.p<MessageProps>`
  ${(messageProps) =>
    messageProps.error &&
    css`
      color: ${(props) => props.theme.textError};
    `};
`;
 
const ButtonWithMargin = styled(Button)`
  margin-top: 8px;
`;
 
const StyledLabel = styled.label`
  padding: 0 0 2px 4px;
  font-size: ${(props) => props.theme.fontSizeSmall};
`;