mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-17 05:55:00 +08:00
Use field validation for PasswordLogin instead of global errors
This commit is contained in:
parent
dea4fd661a
commit
0b74d3a0ef
@ -32,9 +32,6 @@ import SettingsStore from "../../../settings/SettingsStore";
|
|||||||
import {UIFeature} from "../../../settings/UIFeature";
|
import {UIFeature} from "../../../settings/UIFeature";
|
||||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
import CountlyAnalytics from "../../../CountlyAnalytics";
|
||||||
|
|
||||||
// For validating phone numbers without country codes
|
|
||||||
const PHONE_NUMBER_REGEX = /^[0-9()\-\s]*$/;
|
|
||||||
|
|
||||||
// Phases
|
// Phases
|
||||||
// Show controls to configure server details
|
// Show controls to configure server details
|
||||||
const PHASE_SERVER_DETAILS = 0;
|
const PHASE_SERVER_DETAILS = 0;
|
||||||
@ -151,13 +148,6 @@ export default class LoginComponent extends React.Component {
|
|||||||
this._initLoginLogic(newProps.serverConfig.hsUrl, newProps.serverConfig.isUrl);
|
this._initLoginLogic(newProps.serverConfig.hsUrl, newProps.serverConfig.isUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
onPasswordLoginError = errorText => {
|
|
||||||
this.setState({
|
|
||||||
errorText,
|
|
||||||
loginIncorrect: Boolean(errorText),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
isBusy = () => this.state.busy || this.props.busy;
|
isBusy = () => this.state.busy || this.props.busy;
|
||||||
|
|
||||||
onPasswordLogin = async (username, phoneCountry, phoneNumber, password) => {
|
onPasswordLogin = async (username, phoneCountry, phoneNumber, password) => {
|
||||||
@ -330,21 +320,6 @@ export default class LoginComponent extends React.Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onPhoneNumberBlur = phoneNumber => {
|
|
||||||
// Validate the phone number entered
|
|
||||||
if (!PHONE_NUMBER_REGEX.test(phoneNumber)) {
|
|
||||||
this.setState({
|
|
||||||
errorText: _t('The phone number entered looks invalid'),
|
|
||||||
canTryLogin: false,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
errorText: null,
|
|
||||||
canTryLogin: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onRegisterClick = ev => {
|
onRegisterClick = ev => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
@ -590,7 +565,6 @@ export default class LoginComponent extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<PasswordLogin
|
<PasswordLogin
|
||||||
onSubmit={this.onPasswordLogin}
|
onSubmit={this.onPasswordLogin}
|
||||||
onError={this.onPasswordLoginError}
|
|
||||||
onEditServerDetailsClick={onEditServerDetailsClick}
|
onEditServerDetailsClick={onEditServerDetailsClick}
|
||||||
initialUsername={this.state.username}
|
initialUsername={this.state.username}
|
||||||
initialPhoneCountry={this.state.phoneCountry}
|
initialPhoneCountry={this.state.phoneCountry}
|
||||||
@ -599,7 +573,6 @@ export default class LoginComponent extends React.Component {
|
|||||||
onUsernameBlur={this.onUsernameBlur}
|
onUsernameBlur={this.onUsernameBlur}
|
||||||
onPhoneCountryChanged={this.onPhoneCountryChanged}
|
onPhoneCountryChanged={this.onPhoneCountryChanged}
|
||||||
onPhoneNumberChanged={this.onPhoneNumberChanged}
|
onPhoneNumberChanged={this.onPhoneNumberChanged}
|
||||||
onPhoneNumberBlur={this.onPhoneNumberBlur}
|
|
||||||
onForgotPasswordClick={this.props.onForgotPasswordClick}
|
onForgotPasswordClick={this.props.onForgotPasswordClick}
|
||||||
loginIncorrect={this.state.loginIncorrect}
|
loginIncorrect={this.state.loginIncorrect}
|
||||||
serverConfig={this.props.serverConfig}
|
serverConfig={this.props.serverConfig}
|
||||||
|
@ -25,6 +25,11 @@ import SdkConfig from '../../../SdkConfig';
|
|||||||
import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
|
import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
import CountlyAnalytics from "../../../CountlyAnalytics";
|
||||||
|
import withValidation from "../elements/Validation";
|
||||||
|
import * as Email from "../../../email";
|
||||||
|
|
||||||
|
// For validating phone numbers without country codes
|
||||||
|
const PHONE_NUMBER_REGEX = /^[0-9()\-\s]*$/;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A pure UI component which displays a username/password form.
|
* A pure UI component which displays a username/password form.
|
||||||
@ -32,7 +37,6 @@ import CountlyAnalytics from "../../../CountlyAnalytics";
|
|||||||
export default class PasswordLogin extends React.Component {
|
export default class PasswordLogin extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onSubmit: PropTypes.func.isRequired, // fn(username, password)
|
onSubmit: PropTypes.func.isRequired, // fn(username, password)
|
||||||
onError: PropTypes.func,
|
|
||||||
onEditServerDetailsClick: PropTypes.func,
|
onEditServerDetailsClick: PropTypes.func,
|
||||||
onForgotPasswordClick: PropTypes.func, // fn()
|
onForgotPasswordClick: PropTypes.func, // fn()
|
||||||
initialUsername: PropTypes.string,
|
initialUsername: PropTypes.string,
|
||||||
@ -50,14 +54,12 @@ export default class PasswordLogin extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
onError: function() {},
|
|
||||||
onEditServerDetailsClick: null,
|
onEditServerDetailsClick: null,
|
||||||
onUsernameChanged: function() {},
|
onUsernameChanged: function() {},
|
||||||
onUsernameBlur: function() {},
|
onUsernameBlur: function() {},
|
||||||
onPasswordChanged: function() {},
|
onPasswordChanged: function() {},
|
||||||
onPhoneCountryChanged: function() {},
|
onPhoneCountryChanged: function() {},
|
||||||
onPhoneNumberChanged: function() {},
|
onPhoneNumberChanged: function() {},
|
||||||
onPhoneNumberBlur: function() {},
|
|
||||||
initialUsername: "",
|
initialUsername: "",
|
||||||
initialPhoneCountry: "",
|
initialPhoneCountry: "",
|
||||||
initialPhoneNumber: "",
|
initialPhoneNumber: "",
|
||||||
@ -69,10 +71,13 @@ export default class PasswordLogin extends React.Component {
|
|||||||
static LOGIN_FIELD_EMAIL = "login_field_email";
|
static LOGIN_FIELD_EMAIL = "login_field_email";
|
||||||
static LOGIN_FIELD_MXID = "login_field_mxid";
|
static LOGIN_FIELD_MXID = "login_field_mxid";
|
||||||
static LOGIN_FIELD_PHONE = "login_field_phone";
|
static LOGIN_FIELD_PHONE = "login_field_phone";
|
||||||
|
static LOGIN_FIELD_PASSWORD = "login_field_password";
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
// Field error codes by field ID
|
||||||
|
fieldValid: {},
|
||||||
username: this.props.initialUsername,
|
username: this.props.initialUsername,
|
||||||
password: this.props.initialPassword,
|
password: this.props.initialPassword,
|
||||||
phoneCountry: this.props.initialPhoneCountry,
|
phoneCountry: this.props.initialPhoneCountry,
|
||||||
@ -82,6 +87,7 @@ export default class PasswordLogin extends React.Component {
|
|||||||
|
|
||||||
this.onForgotPasswordClick = this.onForgotPasswordClick.bind(this);
|
this.onForgotPasswordClick = this.onForgotPasswordClick.bind(this);
|
||||||
this.onSubmitForm = this.onSubmitForm.bind(this);
|
this.onSubmitForm = this.onSubmitForm.bind(this);
|
||||||
|
this.onUsernameFocus = this.onUsernameFocus.bind(this);
|
||||||
this.onUsernameChanged = this.onUsernameChanged.bind(this);
|
this.onUsernameChanged = this.onUsernameChanged.bind(this);
|
||||||
this.onUsernameBlur = this.onUsernameBlur.bind(this);
|
this.onUsernameBlur = this.onUsernameBlur.bind(this);
|
||||||
this.onLoginTypeChange = this.onLoginTypeChange.bind(this);
|
this.onLoginTypeChange = this.onLoginTypeChange.bind(this);
|
||||||
@ -98,46 +104,30 @@ export default class PasswordLogin extends React.Component {
|
|||||||
this.props.onForgotPasswordClick();
|
this.props.onForgotPasswordClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
onSubmitForm(ev) {
|
async onSubmitForm(ev) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
|
const allFieldsValid = await this.verifyFieldsBeforeSubmit();
|
||||||
|
if (!allFieldsValid) {
|
||||||
|
CountlyAnalytics.instance.track("onboarding_registration_submit_failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let username = ''; // XXX: Synapse breaks if you send null here:
|
let username = ''; // XXX: Synapse breaks if you send null here:
|
||||||
let phoneCountry = null;
|
let phoneCountry = null;
|
||||||
let phoneNumber = null;
|
let phoneNumber = null;
|
||||||
let error;
|
|
||||||
|
|
||||||
switch (this.state.loginType) {
|
switch (this.state.loginType) {
|
||||||
case PasswordLogin.LOGIN_FIELD_EMAIL:
|
case PasswordLogin.LOGIN_FIELD_EMAIL:
|
||||||
username = this.state.username;
|
|
||||||
if (!username) {
|
|
||||||
error = _t('The email field must not be blank.');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PasswordLogin.LOGIN_FIELD_MXID:
|
case PasswordLogin.LOGIN_FIELD_MXID:
|
||||||
username = this.state.username;
|
username = this.state.username;
|
||||||
if (!username) {
|
|
||||||
error = _t('The username field must not be blank.');
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case PasswordLogin.LOGIN_FIELD_PHONE:
|
case PasswordLogin.LOGIN_FIELD_PHONE:
|
||||||
phoneCountry = this.state.phoneCountry;
|
phoneCountry = this.state.phoneCountry;
|
||||||
phoneNumber = this.state.phoneNumber;
|
phoneNumber = this.state.phoneNumber;
|
||||||
if (!phoneNumber) {
|
|
||||||
error = _t('The phone number field must not be blank.');
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
|
||||||
this.props.onError(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.state.password) {
|
|
||||||
this.props.onError(_t('The password field must not be blank.'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.onSubmit(
|
this.props.onSubmit(
|
||||||
username,
|
username,
|
||||||
phoneCountry,
|
phoneCountry,
|
||||||
@ -170,7 +160,6 @@ export default class PasswordLogin extends React.Component {
|
|||||||
|
|
||||||
onLoginTypeChange(ev) {
|
onLoginTypeChange(ev) {
|
||||||
const loginType = ev.target.value;
|
const loginType = ev.target.value;
|
||||||
this.props.onError(null); // send a null error to clear any error messages
|
|
||||||
this.setState({
|
this.setState({
|
||||||
loginType: loginType,
|
loginType: loginType,
|
||||||
username: "", // Reset because email and username use the same state
|
username: "", // Reset because email and username use the same state
|
||||||
@ -196,7 +185,6 @@ export default class PasswordLogin extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onPhoneNumberBlur(ev) {
|
onPhoneNumberBlur(ev) {
|
||||||
this.props.onPhoneNumberBlur(ev.target.value);
|
|
||||||
CountlyAnalytics.instance.track("onboarding_login_phone_number_blur");
|
CountlyAnalytics.instance.track("onboarding_login_phone_number_blur");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,6 +193,161 @@ export default class PasswordLogin extends React.Component {
|
|||||||
this.props.onPasswordChanged(ev.target.value);
|
this.props.onPasswordChanged(ev.target.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async verifyFieldsBeforeSubmit() {
|
||||||
|
// Blur the active element if any, so we first run its blur validation,
|
||||||
|
// which is less strict than the pass we're about to do below for all fields.
|
||||||
|
const activeElement = document.activeElement;
|
||||||
|
if (activeElement) {
|
||||||
|
activeElement.blur();
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldIDsInDisplayOrder = [
|
||||||
|
this.state.loginType,
|
||||||
|
PasswordLogin.LOGIN_FIELD_PASSWORD,
|
||||||
|
];
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// first. Queue a `setState` callback and wait for it to resolve.
|
||||||
|
await new Promise(resolve => this.setState({}, resolve));
|
||||||
|
|
||||||
|
if (this.allFieldsValid()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const invalidField = this.findFirstInvalidField(fieldIDsInDisplayOrder);
|
||||||
|
|
||||||
|
if (!invalidField) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Focus the first invalid field and show feedback in the stricter mode
|
||||||
|
// that no longer allows empty values for required fields.
|
||||||
|
invalidField.focus();
|
||||||
|
invalidField.validate({ allowEmpty: false, focused: true });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
allFieldsValid() {
|
||||||
|
const keys = Object.keys(this.state.fieldValid);
|
||||||
|
for (let i = 0; i < keys.length; ++i) {
|
||||||
|
if (!this.state.fieldValid[keys[i]]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
findFirstInvalidField(fieldIDs) {
|
||||||
|
for (const fieldID of fieldIDs) {
|
||||||
|
if (!this.state.fieldValid[fieldID] && this[fieldID]) {
|
||||||
|
return this[fieldID];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
markFieldValid(fieldID, valid) {
|
||||||
|
const { fieldValid } = this.state;
|
||||||
|
fieldValid[fieldID] = valid;
|
||||||
|
this.setState({
|
||||||
|
fieldValid,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
validateUsernameRules = withValidation({
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
key: "required",
|
||||||
|
test({ value, allowEmpty }) {
|
||||||
|
return allowEmpty || !!value;
|
||||||
|
},
|
||||||
|
invalid: () => _t("Enter username"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
onUsernameValidate = async (fieldState) => {
|
||||||
|
const result = await this.validateUsernameRules(fieldState);
|
||||||
|
this.markFieldValid(PasswordLogin.LOGIN_FIELD_MXID, result.valid);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
validateEmailRules = withValidation({
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
key: "required",
|
||||||
|
test({ value, allowEmpty }) {
|
||||||
|
return allowEmpty || !!value;
|
||||||
|
},
|
||||||
|
invalid: () => _t("Enter email address"),
|
||||||
|
}, {
|
||||||
|
key: "email",
|
||||||
|
test: ({ value }) => !value || Email.looksValid(value),
|
||||||
|
invalid: () => _t("Doesn't look like a valid email address"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
onEmailValidate = async (fieldState) => {
|
||||||
|
const result = await this.validateEmailRules(fieldState);
|
||||||
|
this.markFieldValid(PasswordLogin.LOGIN_FIELD_EMAIL, result.valid);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
validatePhoneNumberRules = withValidation({
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
key: "required",
|
||||||
|
test({ value, allowEmpty }) {
|
||||||
|
return allowEmpty || !!value;
|
||||||
|
},
|
||||||
|
invalid: () => _t("Enter phone number"),
|
||||||
|
}, {
|
||||||
|
key: "number",
|
||||||
|
test: ({ value }) => !value || PHONE_NUMBER_REGEX.test(value),
|
||||||
|
invalid: () => _t("Doesn't look like a valid phone number"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
onPhoneNumberValidate = async (fieldState) => {
|
||||||
|
const result = await this.validatePhoneNumberRules(fieldState);
|
||||||
|
this.markFieldValid(PasswordLogin.LOGIN_FIELD_PHONE, result.valid);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
validatePasswordRules = withValidation({
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
key: "required",
|
||||||
|
test({ value, allowEmpty }) {
|
||||||
|
return allowEmpty || !!value;
|
||||||
|
},
|
||||||
|
invalid: () => _t("Enter password"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
onPasswordValidate = async (fieldState) => {
|
||||||
|
const result = await this.validatePasswordRules(fieldState);
|
||||||
|
this.markFieldValid(PasswordLogin.LOGIN_FIELD_PASSWORD, result.valid);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
renderLoginField(loginType, autoFocus) {
|
renderLoginField(loginType, autoFocus) {
|
||||||
const Field = sdk.getComponent('elements.Field');
|
const Field = sdk.getComponent('elements.Field');
|
||||||
|
|
||||||
@ -226,6 +369,8 @@ export default class PasswordLogin extends React.Component {
|
|||||||
onBlur={this.onUsernameBlur}
|
onBlur={this.onUsernameBlur}
|
||||||
disabled={this.props.disableSubmit}
|
disabled={this.props.disableSubmit}
|
||||||
autoFocus={autoFocus}
|
autoFocus={autoFocus}
|
||||||
|
onValidate={this.onEmailValidate}
|
||||||
|
ref={field => this[PasswordLogin.LOGIN_FIELD_EMAIL] = field}
|
||||||
/>;
|
/>;
|
||||||
case PasswordLogin.LOGIN_FIELD_MXID:
|
case PasswordLogin.LOGIN_FIELD_MXID:
|
||||||
classes.error = this.props.loginIncorrect && !this.state.username;
|
classes.error = this.props.loginIncorrect && !this.state.username;
|
||||||
@ -241,6 +386,8 @@ export default class PasswordLogin extends React.Component {
|
|||||||
onBlur={this.onUsernameBlur}
|
onBlur={this.onUsernameBlur}
|
||||||
disabled={this.props.disableSubmit}
|
disabled={this.props.disableSubmit}
|
||||||
autoFocus={autoFocus}
|
autoFocus={autoFocus}
|
||||||
|
onValidate={this.onUsernameValidate}
|
||||||
|
ref={field => this[PasswordLogin.LOGIN_FIELD_MXID] = field}
|
||||||
/>;
|
/>;
|
||||||
case PasswordLogin.LOGIN_FIELD_PHONE: {
|
case PasswordLogin.LOGIN_FIELD_PHONE: {
|
||||||
const CountryDropdown = sdk.getComponent('views.auth.CountryDropdown');
|
const CountryDropdown = sdk.getComponent('views.auth.CountryDropdown');
|
||||||
@ -266,6 +413,8 @@ export default class PasswordLogin extends React.Component {
|
|||||||
onBlur={this.onPhoneNumberBlur}
|
onBlur={this.onPhoneNumberBlur}
|
||||||
disabled={this.props.disableSubmit}
|
disabled={this.props.disableSubmit}
|
||||||
autoFocus={autoFocus}
|
autoFocus={autoFocus}
|
||||||
|
onValidate={this.onPhoneNumberValidate}
|
||||||
|
ref={field => this[PasswordLogin.LOGIN_FIELD_PHONE] = field}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -363,6 +512,8 @@ export default class PasswordLogin extends React.Component {
|
|||||||
onChange={this.onPasswordChanged}
|
onChange={this.onPasswordChanged}
|
||||||
disabled={this.props.disableSubmit}
|
disabled={this.props.disableSubmit}
|
||||||
autoFocus={autoFocusPassword}
|
autoFocus={autoFocusPassword}
|
||||||
|
onValidate={this.onPasswordValidate}
|
||||||
|
ref={field => this[PasswordLogin.LOGIN_FIELD_PASSWORD] = field}
|
||||||
/>
|
/>
|
||||||
{forgotPasswordJsx}
|
{forgotPasswordJsx}
|
||||||
{ !this.props.busy && <input className="mx_Login_submit"
|
{ !this.props.busy && <input className="mx_Login_submit"
|
||||||
|
@ -2224,10 +2224,11 @@
|
|||||||
"Nice, strong password!": "Nice, strong password!",
|
"Nice, strong password!": "Nice, strong password!",
|
||||||
"Password is allowed, but unsafe": "Password is allowed, but unsafe",
|
"Password is allowed, but unsafe": "Password is allowed, but unsafe",
|
||||||
"Keep going...": "Keep going...",
|
"Keep going...": "Keep going...",
|
||||||
"The email field must not be blank.": "The email field must not be blank.",
|
"Enter username": "Enter username",
|
||||||
"The username field must not be blank.": "The username field must not be blank.",
|
"Enter email address": "Enter email address",
|
||||||
"The phone number field must not be blank.": "The phone number field must not be blank.",
|
"Doesn't look like a valid email address": "Doesn't look like a valid email address",
|
||||||
"The password field must not be blank.": "The password field must not be blank.",
|
"Enter phone number": "Enter phone number",
|
||||||
|
"Doesn't look like a valid phone number": "Doesn't look like a valid phone number",
|
||||||
"Email": "Email",
|
"Email": "Email",
|
||||||
"Username": "Username",
|
"Username": "Username",
|
||||||
"Phone": "Phone",
|
"Phone": "Phone",
|
||||||
@ -2238,13 +2239,10 @@
|
|||||||
"If you don't specify an email address, you won't be able to reset your password. Are you sure?": "If you don't specify an email address, you won't be able to reset your password. Are you sure?",
|
"If you don't specify an email address, you won't be able to reset your password. Are you sure?": "If you don't specify an email address, you won't be able to reset your password. Are you sure?",
|
||||||
"Use an email address to recover your account": "Use an email address to recover your account",
|
"Use an email address to recover your account": "Use an email address to recover your account",
|
||||||
"Enter email address (required on this homeserver)": "Enter email address (required on this homeserver)",
|
"Enter email address (required on this homeserver)": "Enter email address (required on this homeserver)",
|
||||||
"Doesn't look like a valid email address": "Doesn't look like a valid email address",
|
|
||||||
"Passwords don't match": "Passwords don't match",
|
"Passwords don't match": "Passwords don't match",
|
||||||
"Other users can invite you to rooms using your contact details": "Other users can invite you to rooms using your contact details",
|
"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)",
|
"Enter phone number (required on this homeserver)": "Enter phone number (required on this homeserver)",
|
||||||
"Doesn't look like a valid phone number": "Doesn't look like a valid phone number",
|
|
||||||
"Use lowercase letters, numbers, dashes and underscores only": "Use lowercase letters, numbers, dashes and underscores only",
|
"Use lowercase letters, numbers, dashes and underscores only": "Use lowercase letters, numbers, dashes and underscores only",
|
||||||
"Enter username": "Enter username",
|
|
||||||
"Email (optional)": "Email (optional)",
|
"Email (optional)": "Email (optional)",
|
||||||
"Phone (optional)": "Phone (optional)",
|
"Phone (optional)": "Phone (optional)",
|
||||||
"Register": "Register",
|
"Register": "Register",
|
||||||
@ -2450,7 +2448,6 @@
|
|||||||
"Incorrect username and/or password.": "Incorrect username and/or password.",
|
"Incorrect username and/or password.": "Incorrect username and/or password.",
|
||||||
"Please note you are logging into the %(hs)s server, not matrix.org.": "Please note you are logging into the %(hs)s server, not matrix.org.",
|
"Please note you are logging into the %(hs)s server, not matrix.org.": "Please note you are logging into the %(hs)s server, not matrix.org.",
|
||||||
"Failed to perform homeserver discovery": "Failed to perform homeserver discovery",
|
"Failed to perform homeserver discovery": "Failed to perform homeserver discovery",
|
||||||
"The phone number entered looks invalid": "The phone number entered looks invalid",
|
|
||||||
"This homeserver doesn't offer any login flows which are supported by this client.": "This homeserver doesn't offer any login flows which are supported by this client.",
|
"This homeserver doesn't offer any login flows which are supported by this client.": "This homeserver doesn't offer any login flows which are supported by this client.",
|
||||||
"Error: Problem communicating with the given homeserver.": "Error: Problem communicating with the given homeserver.",
|
"Error: Problem communicating with the given homeserver.": "Error: Problem communicating with the given homeserver.",
|
||||||
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.": "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.",
|
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.": "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.",
|
||||||
|
Loading…
Reference in New Issue
Block a user