diff --git a/src/components/views/auth/RegistrationForm.tsx b/src/components/views/auth/RegistrationForm.tsx index 0b41f064004..3a0e6ef0cec 100644 --- a/src/components/views/auth/RegistrationForm.tsx +++ b/src/components/views/auth/RegistrationForm.tsx @@ -44,6 +44,13 @@ enum RegistrationField { PasswordConfirm = "field_password_confirm", } +enum UsernameAvailableStatus { + Unknown, + Available, + Unavailable, + Error, +} + export const PASSWORD_MIN_SCORE = 3; // safely unguessable: moderate protection from offline slow-hash scenario. interface IProps { @@ -348,13 +355,25 @@ export default class RegistrationForm extends React.PureComponent({ description: (_, results) => { // omit the description if the only failing result is the `available` one as it makes no sense for it. if (results.every(({ key, valid }) => key === "available" || valid)) return; return _t("Use lowercase letters, numbers, dashes and underscores only"); }, hideDescriptionIfValid: true, + async deriveData(this: RegistrationForm, { value }) { + if (!value) { + return UsernameAvailableStatus.Unknown; + } + + try { + const available = await this.props.matrixClient.isUsernameAvailable(value); + return available ? UsernameAvailableStatus.Available : UsernameAvailableStatus.Unavailable; + } catch (err) { + return UsernameAvailableStatus.Error; + } + }, rules: [ { key: "required", @@ -369,19 +388,16 @@ export default class RegistrationForm extends React.PureComponent { + test: async ({ value }, usernameAvailable) => { if (!value) { return true; } - try { - await this.props.matrixClient.isUsernameAvailable(value); - return true; - } catch (err) { - return false; - } + return usernameAvailable === UsernameAvailableStatus.Available; }, - invalid: () => _t("Someone already has that username. Try another or if it is you, sign in below."), + invalid: (usernameAvailable) => usernameAvailable === UsernameAvailableStatus.Error + ? _t("Unable to check if username has been taken. Try again later.") + : _t("Someone already has that username. Try another or if it is you, sign in below."), }, ], }); diff --git a/src/components/views/elements/Validation.tsx b/src/components/views/elements/Validation.tsx index a7dbf66ac1e..66c197abc8c 100644 --- a/src/components/views/elements/Validation.tsx +++ b/src/components/views/elements/Validation.tsx @@ -92,7 +92,7 @@ export default function withValidation({ } const data = { value, allowEmpty }; - const derivedData = deriveData ? await deriveData(data) : undefined; + const derivedData: D | undefined = deriveData ? await deriveData.call(this, data) : undefined; const results: IResult[] = []; let valid = true; @@ -106,13 +106,13 @@ export default function withValidation({ continue; } - if (rule.skip && rule.skip.call(this, data, derivedData)) { + if (rule.skip?.call(this, data, derivedData)) { continue; } // We're setting `this` to whichever component holds the validation // function. That allows rules to access the state of the component. - const ruleValid = await rule.test.call(this, data, derivedData); + const ruleValid: boolean = await rule.test.call(this, data, derivedData); valid = valid && ruleValid; if (ruleValid && rule.valid) { // If the rule's result is valid and has text to show for diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index e5b31d898f3..b976a104ce2 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2939,6 +2939,7 @@ "Other users can invite you to rooms using your contact details": "Other users can invite you to rooms using your contact details", "Enter phone number (required on this homeserver)": "Enter phone number (required on this homeserver)", "Use lowercase letters, numbers, dashes and underscores only": "Use lowercase letters, numbers, dashes and underscores only", + "Unable to check if username has been taken. Try again later.": "Unable to check if username has been taken. Try again later.", "Someone already has that username. Try another or if it is you, sign in below.": "Someone already has that username. Try another or if it is you, sign in below.", "Phone (optional)": "Phone (optional)", "Register": "Register",