diff --git a/src/apis/eduidSecurity.ts b/src/apis/eduidSecurity.ts
index be7ef4a7e..42cd6150a 100644
--- a/src/apis/eduidSecurity.ts
+++ b/src/apis/eduidSecurity.ts
@@ -174,14 +174,13 @@ export const fetchSuggestedPassword = createAsyncThunk<
undefined,
{ dispatch: EduIDAppDispatch; state: EduIDAppRootState }
>("chpass/fetchSuggestedPassword", async (args, thunkAPI) => {
- return makeSecurityRequest(thunkAPI, "suggested-password")
+ return makeSecurityRequest(thunkAPI, "change-password/suggested-password")
.then((response) => response.payload.suggested_password)
.catch((err) => thunkAPI.rejectWithValue(err));
});
/*********************************************************************************************************************/
export interface ChangePasswordPayload {
- old_password: string;
new_password: string;
}
@@ -199,7 +198,7 @@ export const changePassword = createAsyncThunk<
ChangePasswordPayload,
{ dispatch: EduIDAppDispatch; state: EduIDAppRootState }
>("chpass/changePassword", async (args, thunkAPI) => {
- return makeSecurityRequest(thunkAPI, "change-password", args)
+ return makeSecurityRequest(thunkAPI, "change-password/set-password", args)
.then((response) => response.payload)
.catch((err) => thunkAPI.rejectWithValue(err));
});
diff --git a/src/components/Common/ConfirmUserInfo.tsx b/src/components/Common/ConfirmUserInfo.tsx
new file mode 100644
index 000000000..44653bac5
--- /dev/null
+++ b/src/components/Common/ConfirmUserInfo.tsx
@@ -0,0 +1,49 @@
+import { idUserEmail, idUserPassword } from "components/Signup/SignupUserCreated";
+import { FormattedMessage } from "react-intl";
+
+interface EmailProps {
+ email?: string;
+}
+
+interface ConfirmUserInfoProps {
+ readonly email_address: string;
+ readonly new_password: string;
+}
+
+export const EmailFieldset = ({ email }: EmailProps): JSX.Element => {
+ return (
+
+
+
+
+
+ {email}
+
+
+ );
+};
+
+export function ConfirmUserInfo(props: ConfirmUserInfoProps) {
+ return (
+
+
+
+
+
+
+
+
+ {props.new_password}
+
+
+
+
+
+ );
+}
diff --git a/src/components/Common/CustomInput.tsx b/src/components/Common/CustomInput.tsx
index 8b6198d1a..cf98e95df 100644
--- a/src/components/Common/CustomInput.tsx
+++ b/src/components/Common/CustomInput.tsx
@@ -2,6 +2,8 @@ import { FieldRenderProps } from "react-final-form";
import { Input } from "reactstrap";
import { InputWrapper } from "./InputWrapper";
+type InputType = "text" | "password" | "email";
+
export default function CustomInput(props: FieldRenderProps): JSX.Element {
// the InputWrapper renders it's children plus a label, helpBlock and any error message from the field validation
return (
@@ -16,7 +18,7 @@ const InputElement = (props: FieldRenderProps): JSX.Element => {
{errorMsg || submitErrorMsg}
+ {props.passwordStrengthMeter ? props.passwordStrengthMeter : null}
);
}
diff --git a/src/components/Common/NewPasswordForm.tsx b/src/components/Common/NewPasswordForm.tsx
index d5db90ca4..f7b89aa44 100644
--- a/src/components/Common/NewPasswordForm.tsx
+++ b/src/components/Common/NewPasswordForm.tsx
@@ -2,7 +2,6 @@ import { ExtraSecurityAlternatives } from "apis/eduidResetPassword";
import CustomInput from "components/Common/CustomInput";
import EduIDButton from "components/Common/EduIDButton";
import { GoBackButton } from "components/ResetPassword/GoBackButton";
-import { FormApi, SubmissionErrors } from "final-form";
import { emptyStringPattern } from "helperFunctions/validation/regexPatterns";
import { Field as FinalField, Form as FinalForm } from "react-final-form";
import { FormattedMessage } from "react-intl";
@@ -17,12 +16,14 @@ interface NewPasswordFormProps {
readonly goBack?: () => void;
readonly extra_security?: ExtraSecurityAlternatives;
readonly suggested_password: string | undefined;
- readonly submitNewPasswordForm: (
- values: NewPasswordFormData,
- form: FormApi>,
- callback?: ((errors?: SubmissionErrors) => void) | undefined
- ) => void | Promise;
+ readonly submitNewPasswordForm: any;
+ // submitNewPasswordForm: (
+ // values: NewPasswordFormData,
+ // form: FormApi>,
+ // callback?: ((errors?: SubmissionErrors) => void) | undefined
+ // ) => void | Promise;
readonly submitButtonText: React.ReactChild;
+ readonly handleCancel?: (event: React.MouseEvent) => void;
}
export function NewPasswordForm(props: NewPasswordFormProps): JSX.Element {
@@ -61,6 +62,11 @@ export function NewPasswordForm(props: NewPasswordFormProps): JSX.Element {
{props.extra_security && Object.keys(props.extra_security).length > 0 && (
)}
+ {props.handleCancel && (
+
+
+
+ )}
{props.submitButtonText}
diff --git a/src/components/Common/NewPasswordInput.tsx b/src/components/Common/NewPasswordInput.tsx
new file mode 100644
index 000000000..f24b2caa5
--- /dev/null
+++ b/src/components/Common/NewPasswordInput.tsx
@@ -0,0 +1,12 @@
+import { FieldRenderProps } from "react-final-form";
+import { InputWrapper } from "./InputWrapper";
+import { PasswordInputElement } from "./PasswordInput";
+
+export default function NewPasswordInput(props: FieldRenderProps): JSX.Element {
+ // the InputWrapper renders it's children plus a label, helpBlock and any error message from the field validation
+ return (
+
+
+
+ );
+}
diff --git a/src/components/Common/PasswordInput.tsx b/src/components/Common/PasswordInput.tsx
index ad8ae4fb4..452228732 100644
--- a/src/components/Common/PasswordInput.tsx
+++ b/src/components/Common/PasswordInput.tsx
@@ -50,7 +50,7 @@ export function WrappedPasswordInput(props: FieldRenderProps): JSX.Eleme
* @param props
* @returns
*/
-function PasswordInputElement(props: InputProps): JSX.Element {
+export function PasswordInputElement(props: InputProps): JSX.Element {
const [showPassword, setShowPassword] = useState(false);
function toggleShowPassword() {
diff --git a/src/components/Common/PasswordStrengthMeter.tsx b/src/components/Common/PasswordStrengthMeter.tsx
index 73c7ed6ea..c43a735d8 100644
--- a/src/components/Common/PasswordStrengthMeter.tsx
+++ b/src/components/Common/PasswordStrengthMeter.tsx
@@ -21,7 +21,6 @@ function PasswordStrengthMeter(props: PasswordStrengthMeterProps) {
const emails = useAppSelector((state) => state.emails.emails);
const [pwScore, setPwScore] = useState(0);
const intl = useIntl();
-
const pwStrengthMessages = ["pwfield.terrible", "pwfield.bad", "pwfield.weak", "pwfield.good", "pwfield.strong"];
useEffect(() => {
@@ -53,10 +52,10 @@ function PasswordStrengthMeter(props: PasswordStrengthMeterProps) {
return (
-
- {intl.formatMessage({ id: pwStrengthMessages[pwScore] })}
+ {props.password !== undefined && {intl.formatMessage({ id: pwStrengthMessages[pwScore] })} }
+
);
}
diff --git a/src/components/Dashboard/ChangePassword.tsx b/src/components/Dashboard/ChangePassword.tsx
index b4a89d36e..0b9659626 100644
--- a/src/components/Dashboard/ChangePassword.tsx
+++ b/src/components/Dashboard/ChangePassword.tsx
@@ -1,17 +1,40 @@
-import { fetchSuggestedPassword } from "apis/eduidSecurity";
+import { changePassword, fetchSuggestedPassword } from "apis/eduidSecurity";
+import Splash from "components/Common/Splash";
import { useAppDispatch, useAppSelector } from "eduid-hooks";
-import React, { useEffect } from "react";
+import React, { useEffect, useState } from "react";
+import { Form as FinalForm, FormRenderProps } from "react-final-form";
import { FormattedMessage, useIntl } from "react-intl";
-import ChangePasswordForm from "./ChangePasswordForm";
+import { useNavigate } from "react-router-dom";
+import ChangePasswordCustomForm from "./ChangePasswordCustom";
+import ChangePasswordSuggestedForm from "./ChangePasswordSuggested";
+import { ChangePasswordSwitchToggle } from "./ChangePasswordSwitchToggle";
// exported for use in tests
export const finish_url = "/profile/security";
-function ChangePassword() {
+export interface ChangePasswordFormProps {
+ finish_url: string; // URL to direct browser to when user cancels password change, or completes it
+}
+
+export interface ChangePasswordChildFormProps {
+ formProps: FormRenderProps;
+ handleCancel?: (event: React.MouseEvent) => void;
+}
+
+interface ChangePasswordFormData {
+ custom?: string; // used with custom password
+ score?: number; // used with custom password
+ suggested?: string; // used with suggested password
+}
+
+export function ChangePassword() {
const suggested_password = useAppSelector((state) => state.chpass.suggested_password);
const is_app_loaded = useAppSelector((state) => state.config.is_app_loaded);
const dispatch = useAppDispatch();
const intl = useIntl();
+ const suggested = useAppSelector((state) => state.chpass.suggested_password);
+ const [renderSuggested, setRenderSuggested] = useState(true); // toggle display of custom or suggested password forms
+ const navigate = useNavigate();
useEffect(() => {
document.title = intl.formatMessage({
@@ -22,21 +45,101 @@ function ChangePassword() {
useEffect(() => {
if (is_app_loaded && suggested_password === undefined) {
- // call fetchSuggestedPassword once state.config.security_service_url is initialised
- dispatch(fetchSuggestedPassword());
+ handleSuggestedPassword();
}
}, [suggested_password, is_app_loaded]);
+ async function handleSuggestedPassword() {
+ const response = await dispatch(fetchSuggestedPassword());
+ if (fetchSuggestedPassword.rejected.match(response)) {
+ navigate(finish_url);
+ }
+ }
+
+ async function handleSubmitPasswords(values: ChangePasswordFormData) {
+ // Use the right form field for the currently displayed password mode
+ const newPassword = renderSuggested ? values.suggested : values.custom;
+
+ // Callback from sub-component when the user clicks on the button to change password
+ if (newPassword) {
+ const response = await dispatch(changePassword({ new_password: newPassword }));
+ if (changePassword.fulfilled.match(response)) {
+ navigate("/profile/chpass/success", {
+ state: newPassword,
+ });
+ }
+ }
+ }
+
+ function handleCancel(event: React.MouseEvent) {
+ // Callback from sub-component when the user clicks on the button to abort changing password
+ event.preventDefault();
+
+ navigate(finish_url);
+ }
+
+ const initialValues = { suggested };
+
+ function handleSwitchChange() {
+ setRenderSuggested(!renderSuggested);
+ }
+
return (
-
-
-
-
-
-
-
-
+
+ onSubmit={handleSubmitPasswords}
+ initialValues={initialValues}
+ render={(formProps) => {
+ const child_props: ChangePasswordChildFormProps = { formProps };
+
+ return (
+
+ {renderSuggested ? (
+
+ ) : (
+
+ )}
+
+ {renderSuggested ? (
+
+ ) : (
+
+ )}
+
+ );
+ }}
+ />
);
}
-
-export const ChangePasswordContainer = ChangePassword;
diff --git a/src/components/Dashboard/ChangePasswordCustom.tsx b/src/components/Dashboard/ChangePasswordCustom.tsx
index 63bcc9237..8b122614a 100644
--- a/src/components/Dashboard/ChangePasswordCustom.tsx
+++ b/src/components/Dashboard/ChangePasswordCustom.tsx
@@ -1,128 +1,148 @@
-import TextInput from "components/Common/EduIDTextInput";
-import PasswordStrengthMeter, { PasswordStrengthData } from "components/Common/PasswordStrengthMeter";
-import { useState } from "react";
-import { Field as FinalField } from "react-final-form";
-import { FormattedMessage } from "react-intl";
-import { ChangePasswordChildFormProps } from "./ChangePasswordForm";
+import EduIDButton from "components/Common/EduIDButton";
+import NewPasswordInput from "components/Common/NewPasswordInput";
+import PasswordStrengthMeter from "components/Common/PasswordStrengthMeter";
+import { emptyStringPattern } from "helperFunctions/validation/regexPatterns";
+import { Field as FinalField, Form as FinalForm } from "react-final-form";
+import { FormattedMessage, useIntl } from "react-intl";
+import { ChangePasswordChildFormProps } from "./ChangePassword";
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface ChangePasswordCustomFormProps extends ChangePasswordChildFormProps {}
export default function ChangePasswordCustomForm(props: ChangePasswordCustomFormProps) {
- const [passwordData, setPasswordData] = useState({});
+ const intl = useIntl();
- function updatePasswordData(value: PasswordStrengthData) {
+ const new_password_placeholder = intl.formatMessage({
+ id: "placeholder.new_password_placeholder",
+ defaultMessage: "enter new password",
+ description: "placeholder text for new password",
+ });
+
+ const repeat_new_password_placeholder = intl.formatMessage({
+ id: "placeholder.repeat_new_password_placeholder",
+ defaultMessage: "repeat new password",
+ description: "placeholder text for repeat new password",
+ });
+
+ function updatePasswordData() {
// This function is called when the password strength meter has calculated password strength
// on the current value in the form. We need to trigger validation of the field again at this
// point, since validation uses this calculated value (and will already have executed when we
// get here).
- setPasswordData(value);
props.formProps.form.change("custom", props.formProps.values.custom);
}
- // Form field validators
- const required = (value?: string) => (value ? undefined : "required");
-
- function strongEnough(value?: string): string | undefined {
- // check that the custom password is strong enough, using a score computed in the
- // PasswordStrengthMeter component.
- if (!value) {
- return "required";
- }
- if (passwordData.isTooWeak !== false) {
- return "chpass.low-password-entropy";
+ function validateNewPassword(values: { custom?: string; repeat?: string }) {
+ const errors: { custom?: string; repeat?: string } = {};
+ if (values !== undefined) {
+ (["custom", "repeat"] as Array).forEach((inputName) => {
+ if (!values[inputName] || emptyStringPattern.test(values[inputName] as string)) {
+ errors[inputName] = "required";
+ } else if (values["custom"]?.replace(/\s/g, "") !== values["repeat"]?.replace(/\s/g, "")) {
+ // Remove whitespace from both passwords before comparing
+ errors["repeat"] = "chpass.different-repeat";
+ }
+ });
}
- }
- function mustMatch(value?: string): string | undefined {
- // validate that the repeated password is the same as the first one (called 'custom')
- if (!value) {
- return "required";
- }
- if (value !== props.formProps.values.custom) {
- return "chpass.different-repeat";
- }
+ return errors;
}
return (
-
+ );
+ }}
+ />
);
}
diff --git a/src/components/Dashboard/ChangePasswordDisplay.tsx b/src/components/Dashboard/ChangePasswordDisplay.tsx
index 84e80581a..479c19eeb 100644
--- a/src/components/Dashboard/ChangePasswordDisplay.tsx
+++ b/src/components/Dashboard/ChangePasswordDisplay.tsx
@@ -14,7 +14,7 @@ function ChangePasswordDisplay(props: ChangePasswordDisplayProps) {
function handleAcceptModal() {
const chpassURL = config.authn_service_url + "chpass";
- // the "chpass" path will route to the ChangePasswordContainer when we get back
+ // the "chpass" path will route to the ChangePassword when we get back
const nextURL = config.dashboard_link + "chpass";
const url = chpassURL + "?next=" + encodeURIComponent(nextURL);
window.location.assign(url);
diff --git a/src/components/Dashboard/ChangePasswordForm.tsx b/src/components/Dashboard/ChangePasswordForm.tsx
deleted file mode 100644
index d3ed187f8..000000000
--- a/src/components/Dashboard/ChangePasswordForm.tsx
+++ /dev/null
@@ -1,113 +0,0 @@
-import { changePassword } from "apis/eduidSecurity";
-import EduIDButton from "components/Common/EduIDButton";
-import { useAppDispatch, useAppSelector } from "eduid-hooks";
-import React, { useState } from "react";
-import { Form as FinalForm, FormRenderProps } from "react-final-form";
-import { FormattedMessage } from "react-intl";
-import { useNavigate } from "react-router-dom";
-import { ButtonGroup } from "reactstrap";
-import ChangePasswordCustomForm from "./ChangePasswordCustom";
-import ChangePasswordSuggestedForm from "./ChangePasswordSuggested";
-
-export interface ChangePasswordFormProps {
- finish_url: string; // URL to direct browser to when user cancels password change, or completes it
-}
-
-// These are the props we pass to the sub-components with the different forms
-export interface ChangePasswordChildFormProps {
- formProps: FormRenderProps;
-}
-
-interface ChangePasswordFormData {
- old?: string; // used by both modes
- custom?: string; // used with custom password
- score?: number; // used with custom password
- suggested?: string; // used with suggested password
-}
-
-function ChangePasswordForm(props: ChangePasswordFormProps) {
- const suggested = useAppSelector((state) => state.chpass.suggested_password);
- const [renderSuggested, setRenderSuggested] = useState(true); // toggle display of custom or suggested password forms
- const dispatch = useAppDispatch();
- const navigate = useNavigate();
-
- function togglePasswordType() {
- // Toggle between rendering the suggested password form, or the custom password form
- setRenderSuggested(!renderSuggested);
- }
-
- async function handleSubmitPasswords(values: ChangePasswordFormData) {
- // Use the right form field for the currently displayed password mode
- const newPassword = renderSuggested ? values.suggested : values.custom;
- // Callback from sub-component when the user clicks on the button to change password
- if (values.old && newPassword) {
- const response = await dispatch(changePassword({ old_password: values.old, new_password: newPassword }));
- if (changePassword.fulfilled.match(response)) {
- navigate(props.finish_url);
- }
- }
- }
-
- function handleCancel(event: React.MouseEvent) {
- // Callback from sub-component when the user clicks on the button to abort changing password
- event.preventDefault();
- // TODO: should clear passwords from form to avoid browser password manager asking user to save the password
- navigate(props.finish_url);
- }
-
- const initialValues = { suggested };
-
- return (
-
- onSubmit={handleSubmitPasswords}
- initialValues={initialValues}
- render={(formProps) => {
- const child_props: ChangePasswordChildFormProps = { formProps };
-
- return (
-
- {renderSuggested ? (
-
- ) : (
-
- )}
-
-
-
-
- {renderSuggested ? (
-
- ) : (
-
- )}
-
-
-
-
-
-
-
-
-
-
-
-
- );
- }}
- />
- );
-}
-
-export default ChangePasswordForm;
diff --git a/src/components/Dashboard/ChangePasswordSuccess.tsx b/src/components/Dashboard/ChangePasswordSuccess.tsx
new file mode 100644
index 000000000..63a7fa278
--- /dev/null
+++ b/src/components/Dashboard/ChangePasswordSuccess.tsx
@@ -0,0 +1,39 @@
+import { ConfirmUserInfo } from "components/Common/ConfirmUserInfo";
+import EduIDButton from "components/Common/EduIDButton";
+import { useAppSelector } from "eduid-hooks";
+import { FormattedMessage } from "react-intl";
+import { useLocation } from "react-router-dom";
+import { finish_url } from "./ChangePassword";
+
+export function ChangePasswordSuccess(): JSX.Element {
+ const emails = useAppSelector((state) => state.emails.emails);
+ const location = useLocation();
+ const password = location.state;
+
+ return (
+
+
+ mail.primary)[0].email} new_password={password} />
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/Dashboard/ChangePasswordSuggested.tsx b/src/components/Dashboard/ChangePasswordSuggested.tsx
index a35a7ec80..219106072 100644
--- a/src/components/Dashboard/ChangePasswordSuggested.tsx
+++ b/src/components/Dashboard/ChangePasswordSuggested.tsx
@@ -1,39 +1,35 @@
-import TextInput from "components/Common/EduIDTextInput";
-import { Field as FinalField } from "react-final-form";
+import { CopyToClipboard } from "components/Common/CopyToClipboard";
+import { NewPasswordForm } from "components/Common/NewPasswordForm";
+import { useAppSelector } from "eduid-hooks";
+import React, { useRef } from "react";
import { FormattedMessage } from "react-intl";
-import { ChangePasswordChildFormProps } from "./ChangePasswordForm";
+import { ChangePasswordChildFormProps } from "./ChangePassword";
export default function ChangePasswordSuggestedForm(props: ChangePasswordChildFormProps) {
- // Form field validator
- const required = (value: string) => (value ? undefined : "required");
+ const ref = useRef(null);
+ const suggested_password = useAppSelector((state) => state.chpass.suggested_password);
return (
-
-
-
- name="old"
- component={TextInput}
- componentClass="input"
- type="password"
- id="old-password-field"
- label={ }
- validate={required}
- autoComplete="current-password"
+
+
+
+
+
+
-
-
-
- name="suggested"
- component={TextInput}
- componentClass="input"
- type="text"
- id="suggested-password-field"
- className="suggested-password"
- label={ }
- disabled={true}
- autoComplete="new-password"
- />
-
-
+
+
+ }
+ handleCancel={props.handleCancel}
+ />
+
);
}
diff --git a/src/components/Dashboard/ChangePasswordSwitchToggle.tsx b/src/components/Dashboard/ChangePasswordSwitchToggle.tsx
new file mode 100644
index 000000000..6a0a99ca9
--- /dev/null
+++ b/src/components/Dashboard/ChangePasswordSwitchToggle.tsx
@@ -0,0 +1,30 @@
+import { FormattedMessage } from "react-intl";
+
+export function ChangePasswordSwitchToggle(props: {
+ readonly handleSwitchChange: (event: React.ChangeEvent) => void;
+ readonly renderSuggested: boolean;
+}) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/Dashboard/NinForm.tsx b/src/components/Dashboard/NinForm.tsx
index 796ff37a4..99b33208f 100644
--- a/src/components/Dashboard/NinForm.tsx
+++ b/src/components/Dashboard/NinForm.tsx
@@ -67,7 +67,7 @@ function NinForm(): JSX.Element {
render={({ handleSubmit, pristine, invalid }) => {
return (
-
+
} />
} />
} />
- } />
+ } />
+ } />
} />
{/* Navigates for old paths. TODO: redirect in backend server instead */}
} />
diff --git a/src/components/ResetPassword/SetNewPassword.tsx b/src/components/ResetPassword/SetNewPassword.tsx
index 7dfa4c5b8..93278cd9a 100644
--- a/src/components/ResetPassword/SetNewPassword.tsx
+++ b/src/components/ResetPassword/SetNewPassword.tsx
@@ -4,6 +4,7 @@ import {
postSetNewPasswordExtraSecurityPhone,
postSetNewPasswordExtraSecurityToken,
} from "apis/eduidResetPassword";
+import { ConfirmUserInfo } from "components/Common/ConfirmUserInfo";
import { CopyToClipboard } from "components/Common/CopyToClipboard";
import EduIDButton from "components/Common/EduIDButton";
import { NewPasswordForm, NewPasswordFormData } from "components/Common/NewPasswordForm";
@@ -100,7 +101,7 @@ export function SetNewPassword(): JSX.Element | null {
-
+
@@ -150,32 +151,7 @@ export function ResetPasswordSuccess(): JSX.Element {
-
-
-
-
-
-
- {email_address}
-
-
-
-
-
-
-
-
- {new_password}
-
-
-
-
-
+
diff --git a/src/components/Signup/SignupApp.tsx b/src/components/Signup/SignupApp.tsx
index 100d78489..a463075d6 100644
--- a/src/components/Signup/SignupApp.tsx
+++ b/src/components/Signup/SignupApp.tsx
@@ -14,7 +14,6 @@ import { SignupConfirmPassword, SignupUserCreated } from "./SignupUserCreated";
export function SignupApp(): JSX.Element {
const signupContext = useContext(SignupGlobalStateContext);
const [state] = useActor(signupContext.signupService);
-
const intl = useIntl();
useEffect(() => {
diff --git a/src/components/Signup/SignupUserCreated.tsx b/src/components/Signup/SignupUserCreated.tsx
index d0d5301e6..2ff8b2c50 100644
--- a/src/components/Signup/SignupUserCreated.tsx
+++ b/src/components/Signup/SignupUserCreated.tsx
@@ -1,4 +1,5 @@
import { createUserRequest } from "apis/eduidSignup";
+import { ConfirmUserInfo, EmailFieldset } from "components/Common/ConfirmUserInfo";
import { CopyToClipboard } from "components/Common/CopyToClipboard";
import EduIDButton from "components/Common/EduIDButton";
import { NewPasswordForm, NewPasswordFormData } from "components/Common/NewPasswordForm";
@@ -13,23 +14,6 @@ export const idUserEmail = "user-email";
export const idUserPassword = "user-password";
export const idFinishedButton = "finished-button";
-interface EmailProps {
- email?: string;
-}
-
-const EmailFieldset = ({ email }: EmailProps): JSX.Element => {
- return (
-
-
-
-
-
- {email}
-
-
- );
-};
-
export function SignupConfirmPassword() {
const dispatch = useAppDispatch();
const signupContext = useContext(SignupGlobalStateContext);
@@ -70,14 +54,6 @@ export function SignupConfirmPassword() {
- {/*
-
-
-
-
- {signupState?.email.address}
-
- */}
@@ -125,26 +101,10 @@ export function SignupUserCreated(): JSX.Element {
/>
-
-
-
-
-
-
-
-
- {formatPassword(signupState?.credentials.password)}
-
-
-
-
-
-
+
diff --git a/src/styles/_ChangePassword.scss b/src/styles/_ChangePassword.scss
index 436f549e1..7179ad7cf 100644
--- a/src/styles/_ChangePassword.scss
+++ b/src/styles/_ChangePassword.scss
@@ -8,11 +8,10 @@
margin-bottom: 1rem;
}
-#changePasswordDialog {
- // background: yellow;
- & fieldset {
- margin-bottom: 1rem;
- }
+fieldset.toggle-change-password-options {
+ padding-bottom: 2rem;
+ margin-bottom: 2rem;
+ border-bottom: 1px solid #d8d8d8;
}
#password-custom-help {
@@ -22,7 +21,55 @@
meter {
width: 100%;
- // background: yellow;
+ height: 2.5rem;
+}
+
+meter[value="1"]::-webkit-meter-optimum-value {
+ background: red;
+ border-top-left-radius: 10px;
+ border-bottom-left-radius: 10px;
+}
+meter[value="2"]::-webkit-meter-optimum-value {
+ background: yellow;
+ border-top-left-radius: 10px;
+ border-bottom-left-radius: 10px;
+}
+meter[value="3"]::-webkit-meter-optimum-value {
+ background: orange;
+ border-top-left-radius: 10px;
+ border-bottom-left-radius: 10px;
+}
+meter[value="4"]::-webkit-meter-optimum-value {
+ background: green;
+ border-top-left-radius: 10px;
+ border-bottom-left-radius: 10px;
+}
+
+/* Gecko based browsers */
+meter[value="1"]::-moz-meter-bar {
+ background: red;
+ border-top-left-radius: 10px;
+ border-bottom-left-radius: 10px;
+}
+meter[value="2"]::-moz-meter-bar {
+ background: yellow;
+ border-top-left-radius: 10px;
+ border-bottom-left-radius: 10px;
+}
+meter[value="3"]::-moz-meter-bar {
+ background: orange;
+ border-top-left-radius: 10px;
+ border-bottom-left-radius: 10px;
+}
+meter[value="4"]::-moz-meter-bar {
+ background: green;
+ border-top-left-radius: 10px;
+ border-bottom-left-radius: 10px;
+}
+
+.form-field-error-area small.text-muted {
+ font-size: 0.9rem !important;
+ color: #d2143a !important;
}
@media (max-width: 375px) {
diff --git a/src/styles/_ResetPassword.scss b/src/styles/_ResetPassword.scss
index 07ea14473..0b6082e37 100644
--- a/src/styles/_ResetPassword.scss
+++ b/src/styles/_ResetPassword.scss
@@ -152,7 +152,7 @@ a#resend-phone:not(.button-active) {
}
}
-.reset-password-input {
+.copy-password-input {
position: relative; // not needed since Splash is relative but best leave for safety.
}
diff --git a/src/styles/_anti-bootstrap.scss b/src/styles/_anti-bootstrap.scss
index 0398f2e9f..37f47cb5e 100644
--- a/src/styles/_anti-bootstrap.scss
+++ b/src/styles/_anti-bootstrap.scss
@@ -30,10 +30,6 @@ input {
small {
font-size: 100% !important;
- &.text-muted {
- color: $txt-black !important;
- }
-
&.form-text {
margin-top: 0 !important;
}
diff --git a/src/styles/_inputs.scss b/src/styles/_inputs.scss
index c00d3f218..8982b9df5 100644
--- a/src/styles/_inputs.scss
+++ b/src/styles/_inputs.scss
@@ -157,6 +157,12 @@ input[readonly]:focus-visible {
}
}
+fieldset.change-password-custom-inputs {
+ span.input-validate-error {
+ position: relative !important;
+ }
+}
+
// login username-pw / reset-password set-new-password
.password-input {
display: flex;
diff --git a/src/tests/ChangePasswordForm-test.tsx b/src/tests/ChangePasswordForm-test.tsx
index 3f06f91f2..fe20ed1e3 100644
--- a/src/tests/ChangePasswordForm-test.tsx
+++ b/src/tests/ChangePasswordForm-test.tsx
@@ -1,38 +1,38 @@
-import { finish_url } from "components/Dashboard/ChangePassword";
-import ChangePasswordForm from "components/Dashboard/ChangePasswordForm";
-import { fireEvent, render, screen, waitFor } from "./helperFunctions/DashboardTestApp-rtl";
+import { ChangePassword } from "components/Dashboard/ChangePassword";
+import { initialState as configInitialState } from "slices/IndexConfig";
-test("renders ChangePasswordForm, suggested password value is field in suggested-password-field", () => {
- render( );
+import { fireEvent, render, screen, waitFor } from "./helperFunctions/DashboardTestApp-rtl";
- const oldPasswordInput = screen.getByLabelText(/Current password/i) as HTMLInputElement;
- expect(oldPasswordInput.value).toBe("");
+const suggestPassword = "test-password";
- const suggestedPasswordInput = screen.getByLabelText(/Suggested password/i) as HTMLInputElement;
+test("renders ChangePasswordForm, suggested password value is field in suggested-password-field", () => {
+ render( , {
+ state: {
+ config: { ...configInitialState, is_app_loaded: true },
+ chpass: {
+ suggested_password: suggestPassword,
+ },
+ },
+ });
+ expect(screen.getByRole("heading")).toHaveTextContent(/^Change password: Suggested password/);
+ const suggestedPasswordInput = screen.getByLabelText(/Repeat new password/i) as HTMLInputElement;
expect(suggestedPasswordInput.value).toBeDefined();
});
-test("save button will be enabled once current password field is filled", () => {
- render( );
-
- const input = screen.getByLabelText(/Current password/i) as HTMLInputElement;
- // change password save button is initially disabled
- const savePasswordButton = screen.getByRole("button", { name: /save/i });
- expect(savePasswordButton).toBeDisabled();
-
- fireEvent.change(input, { target: { value: "current password" } });
- expect(input.value).toBe("current password");
-
- expect(savePasswordButton).toBeEnabled();
-});
-
test("renders custom password form after clicking do not want a suggested password", async () => {
- render( );
- const customPasswordButton = screen.getByRole("button", { name: /I don't want a suggested password/i });
+ render( , {
+ state: {
+ config: { ...configInitialState, is_app_loaded: true },
+ chpass: {
+ suggested_password: suggestPassword,
+ },
+ },
+ });
+ const customPasswordButton = screen.getByRole("checkbox", { name: /Create a custom password?/i });
expect(customPasswordButton).toBeInTheDocument();
fireEvent.click(customPasswordButton);
- expect(screen.getByText(/Suggest a password for me/i)).toBeInTheDocument();
+ expect(customPasswordButton).toBeChecked();
const newPasswordInput = screen.getByLabelText(/Enter new password/i) as HTMLInputElement;
const repeatNewPasswordInput = screen.getByLabelText(/Repeat new password/i) as HTMLInputElement;