Complete the compounding

This commit is contained in:
Half-Shot
2025-03-13 14:40:00 +00:00
parent 856a35175f
commit e3f43e1060
2 changed files with 30 additions and 31 deletions

View File

@@ -52,8 +52,8 @@ const DEFAULT_PROPS = {
labelAllowedButUnsafe: _td("auth|password_field_weak_label"), labelAllowedButUnsafe: _td("auth|password_field_weak_label"),
}; };
const NewPassphraseField: React.FC<IProps> = (props) => { const PassphraseField: React.FC<IProps> = (props) => {
const { labelEnterPassword, userInputs, minScore, label, labelStrongPassword, labelAllowedButUnsafe, className, id, fieldRef, autoFocus} = {...DEFAULT_PROPS, ...props}; const { labelEnterPassword, userInputs, minScore, label, labelStrongPassword, labelAllowedButUnsafe, className, id, fieldRef, autoFocus, onChange, onValidate} = {...DEFAULT_PROPS, ...props};
const validateFn = useMemo(() => withValidation<{}, ZxcvbnResult | null>({ const validateFn = useMemo(() => withValidation<{}, ZxcvbnResult | null>({
description: function (complexity) { description: function (complexity) {
const score = complexity ? complexity.score : 0; const score = complexity ? complexity.score : 0;
@@ -103,12 +103,14 @@ const NewPassphraseField: React.FC<IProps> = (props) => {
const [feedback, setFeedback]= useState<string|JSX.Element>(); const [feedback, setFeedback]= useState<string|JSX.Element>();
const onInputChange = useCallback<React.ChangeEventHandler<HTMLInputElement>>((ev) => { const onInputChange = useCallback<React.ChangeEventHandler<HTMLInputElement>>((ev) => {
onChange(ev);
validateFn({ validateFn({
value: ev.target.value, value: ev.target.value,
focused: true, focused: true,
}).then((v) => { }).then((v) => {
setFeedback(v.feedback); setFeedback(v.feedback);
}) onValidate?.(v);
});
}, [validateFn]); }, [validateFn]);
@@ -119,4 +121,4 @@ const NewPassphraseField: React.FC<IProps> = (props) => {
</Field> </Field>
} }
export default NewPassphraseField; export default PassphraseField;

View File

@@ -10,11 +10,10 @@ import React from "react";
import { type MatrixClient } from "matrix-js-sdk/src/matrix"; import { type MatrixClient } from "matrix-js-sdk/src/matrix";
import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../MatrixClientPeg";
import AccessibleButton, { type AccessibleButtonKind } from "../elements/AccessibleButton";
import withValidation, { type IFieldState, type IValidationResult } from "../elements/Validation"; import withValidation, { type IFieldState, type IValidationResult } from "../elements/Validation";
import { UserFriendlyError, _t, _td } from "../../../languageHandler"; import { UserFriendlyError, _t, _td } from "../../../languageHandler";
import { PASSWORD_MIN_SCORE } from "../auth/RegistrationForm"; import { PASSWORD_MIN_SCORE } from "../auth/RegistrationForm";
import { Root, Field as CpdField, PasswordInput, Label, InlineSpinner, HelpMessage } from "@vector-im/compound-web"; import { Root, Field as CpdField, PasswordInput, Label, InlineSpinner, HelpMessage, Button } from "@vector-im/compound-web";
import PassphraseField from "../auth/PassphraseField"; import PassphraseField from "../auth/PassphraseField";
const FIELD_OLD_PASSWORD = "field_old_password"; const FIELD_OLD_PASSWORD = "field_old_password";
@@ -31,8 +30,6 @@ enum Phase {
interface IProps { interface IProps {
onFinished: (outcome: { didSetEmail?: boolean }) => void; onFinished: (outcome: { didSetEmail?: boolean }) => void;
onError: (error: Error) => void; onError: (error: Error) => void;
buttonClassName?: string;
buttonKind?: AccessibleButtonKind;
buttonLabel?: string; buttonLabel?: string;
} }
@@ -250,26 +247,25 @@ export default class ChangePassword extends React.Component<IProps, IState> {
activeElement.blur(); activeElement.blur();
} }
// Run all fields with stricter validation that no longer allows empty
// values for required fields.
await this.onOldPasswordValidate({
value: this[FIELD_OLD_PASSWORD]?.value ?? null,
allowEmpty: false,
focused: true,
});
await this.onNewPasswordConfirmValidate({
value: this[FIELD_NEW_PASSWORD_CONFIRM]?.value ?? null,
allowEmpty: false,
focused: true,
});
const fieldIDsInDisplayOrder: FieldType[] = [ const fieldIDsInDisplayOrder: FieldType[] = [
FIELD_OLD_PASSWORD, FIELD_OLD_PASSWORD,
FIELD_NEW_PASSWORD, FIELD_NEW_PASSWORD,
FIELD_NEW_PASSWORD_CONFIRM, FIELD_NEW_PASSWORD_CONFIRM,
]; ];
// Run all fields with stricter validation that no longer allows empty
// values for required fields.
for (const fieldID of fieldIDsInDisplayOrder) {
const field = this[fieldID];
if (!field) {
continue;
}
// We must wait for these validations to finish before queueing
// up the setState below so our setState goes in the queue after
// all the setStates from these validate calls (that's how we
// know they've finished).
await field.validate({ allowEmpty: false });
}
// Validation and state updates are async, so we need to wait for them to complete // Validation and state updates are async, so we need to wait for them to complete
// first. Queue a `setState` callback and wait for it to resolve. // first. Queue a `setState` callback and wait for it to resolve.
await new Promise<void>((resolve) => this.setState({}, resolve)); await new Promise<void>((resolve) => this.setState({}, resolve));
@@ -287,7 +283,8 @@ export default class ChangePassword extends React.Component<IProps, IState> {
// Focus the first invalid field and show feedback in the stricter mode // Focus the first invalid field and show feedback in the stricter mode
// that no longer allows empty values for required fields. // that no longer allows empty values for required fields.
invalidField.focus(); invalidField.focus();
invalidField.validate({ allowEmpty: false, focused: true }); // TODO: HMM
// invalidField.validate({ allowEmpty: false, focused: true });
return false; return false;
} }
@@ -305,8 +302,6 @@ export default class ChangePassword extends React.Component<IProps, IState> {
} }
public render(): React.ReactNode { public render(): React.ReactNode {
const buttonClassName = this.props.buttonClassName;
const { fieldValid, phase } = this.state; const { fieldValid, phase } = this.state;
switch (phase) { switch (phase) {
@@ -317,7 +312,7 @@ export default class ChangePassword extends React.Component<IProps, IState> {
<Label> <Label>
{_t("auth|change_password_current_label")} {_t("auth|change_password_current_label")}
</Label> </Label>
<PasswordInput ref={(field) => (this[FIELD_OLD_PASSWORD] = field)} data-invalid={fieldValid[FIELD_OLD_PASSWORD]?.valid} value={this.state.oldPassword} onChange={this.onChangeOldPassword} /> <PasswordInput ref={(field) => (this[FIELD_OLD_PASSWORD] = field)} data-invalid={fieldValid[FIELD_OLD_PASSWORD]?.valid === false ? true : undefined} value={this.state.oldPassword} onChange={this.onChangeOldPassword} />
{fieldValid[FIELD_OLD_PASSWORD]?.feedback && <HelpMessage> {fieldValid[FIELD_OLD_PASSWORD]?.feedback && <HelpMessage>
{fieldValid[FIELD_OLD_PASSWORD]?.feedback} {fieldValid[FIELD_OLD_PASSWORD]?.feedback}
</HelpMessage>} </HelpMessage>}
@@ -337,18 +332,20 @@ export default class ChangePassword extends React.Component<IProps, IState> {
<Label> <Label>
{_t("auth|change_password_confirm_label")} {_t("auth|change_password_confirm_label")}
</Label> </Label>
<PasswordInput autoComplete="new-password" ref={(field) => (this[FIELD_NEW_PASSWORD_CONFIRM] = field)} data-invalid={fieldValid[FIELD_NEW_PASSWORD_CONFIRM]} value={this.state.newPasswordConfirm} onChange={this.onChangeNewPasswordConfirm} /> <PasswordInput autoComplete="new-password" ref={(field) => (this[FIELD_NEW_PASSWORD_CONFIRM] = field)} data-invalid={fieldValid[FIELD_NEW_PASSWORD_CONFIRM]?.valid === false ? true : undefined} value={this.state.newPasswordConfirm} onChange={this.onChangeNewPasswordConfirm} />
{fieldValid[FIELD_NEW_PASSWORD_CONFIRM]?.feedback && <HelpMessage> {fieldValid[FIELD_NEW_PASSWORD_CONFIRM]?.feedback && <HelpMessage>
{fieldValid[FIELD_NEW_PASSWORD_CONFIRM]?.feedback} {fieldValid[FIELD_NEW_PASSWORD_CONFIRM]?.feedback}
</HelpMessage>} </HelpMessage>}
</CpdField> </CpdField>
<AccessibleButton <Button
className={buttonClassName} disabled={!this.allFieldsValid()}
kind={this.props.buttonKind} style={{width: "fit-content"}}
onClick={this.onClickChange} onClick={this.onClickChange}
kind="primary"
size="sm"
> >
{this.props.buttonLabel || _t("auth|change_password_action")} {this.props.buttonLabel || _t("auth|change_password_action")}
</AccessibleButton> </Button>
</Root> </Root>
); );
case Phase.Uploading: case Phase.Uploading: