Finishing off the first iteration on login UI
This makes the following changes: - Improve CountryDropdown by allowing all countries to be displayed at once and using PNGs for performance (trading of quality - the pngs are scaled down from 32px to 25px) - "I want to sign in with" dropdown to select login method - MXID login field that suffixes HS domain (whether custom or matrix.org) and prefixes "@" - Email field which is secretly the same as the username field but with a different placeholder - No more login flickering when changing ServerConfig (!) fixes https://github.com/vector-im/riot-web/issues/1517 This implements most of the design in https://github.com/vector-im/riot-web/issues/3524 but neglects the phone number login: ![login_with_msisdn](https://cloud.githubusercontent.com/assets/1922197/24864469/30a921fc-1dfc-11e7-95d1-76f619da1402.png) This will be updated in another PR to implement desired things: - Country code visible once a country has been selected (propbably but as a prefix to the phone number input box. - Use square flags - Move CountryDropdown above phone input and make it show the full country name when not expanded - Auto-select country based on IP
This commit is contained in:
parent
566a315242
commit
9cd7914ea5
6 changed files with 207 additions and 151 deletions
|
@ -25,6 +25,9 @@ import emojione from 'emojione';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
emojione.imagePathSVG = 'emojione/svg/';
|
emojione.imagePathSVG = 'emojione/svg/';
|
||||||
|
// Store PNG path for displaying many flags at once (for increased performance over SVG)
|
||||||
|
emojione.imagePathPNG = 'emojione/png/';
|
||||||
|
// Use SVGs for emojis
|
||||||
emojione.imageType = 'svg';
|
emojione.imageType = 'svg';
|
||||||
|
|
||||||
const EMOJI_REGEX = new RegExp(emojione.unicodeRegexp+"+", "gi");
|
const EMOJI_REGEX = new RegExp(emojione.unicodeRegexp+"+", "gi");
|
||||||
|
@ -64,16 +67,23 @@ export function unicodeToImage(str) {
|
||||||
* emoji.
|
* emoji.
|
||||||
*
|
*
|
||||||
* @param alt {string} String to use for the image alt text
|
* @param alt {string} String to use for the image alt text
|
||||||
|
* @param useSvg {boolean} Whether to use SVG image src. If False, PNG will be used.
|
||||||
* @param unicode {integer} One or more integers representing unicode characters
|
* @param unicode {integer} One or more integers representing unicode characters
|
||||||
* @returns A img node with the corresponding emoji
|
* @returns A img node with the corresponding emoji
|
||||||
*/
|
*/
|
||||||
export function charactersToImageNode(alt, ...unicode) {
|
export function charactersToImageNode(alt, useSvg, ...unicode) {
|
||||||
const fileName = unicode.map((u) => {
|
const fileName = unicode.map((u) => {
|
||||||
return u.toString(16);
|
return u.toString(16);
|
||||||
}).join('-');
|
}).join('-');
|
||||||
return <img alt={alt} src={`${emojione.imagePathSVG}${fileName}.svg${emojione.cacheBustParam}`}/>;
|
const path = useSvg ? emojione.imagePathSVG : emojione.imagePathPNG;
|
||||||
|
const fileType = useSvg ? 'svg' : 'png';
|
||||||
|
return <img
|
||||||
|
alt={alt}
|
||||||
|
src={`${path}${fileName}.${fileType}${emojione.cacheBustParam}`}
|
||||||
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function stripParagraphs(html: string): string {
|
export function stripParagraphs(html: string): string {
|
||||||
const contentDiv = document.createElement('div');
|
const contentDiv = document.createElement('div');
|
||||||
contentDiv.innerHTML = html;
|
contentDiv.innerHTML = html;
|
||||||
|
|
|
@ -17,13 +17,11 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
import React from 'react';
|
||||||
var ReactDOM = require('react-dom');
|
import ReactDOM from 'react-dom';
|
||||||
var sdk = require('../../../index');
|
import url from 'url';
|
||||||
var Login = require("../../../Login");
|
import sdk from '../../../index';
|
||||||
var PasswordLogin = require("../../views/login/PasswordLogin");
|
import Login from '../../../Login';
|
||||||
var CasLogin = require("../../views/login/CasLogin");
|
|
||||||
var ServerConfig = require("../../views/login/ServerConfig");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wire component which glues together login UI components and Login logic
|
* A wire component which glues together login UI components and Login logic
|
||||||
|
@ -67,6 +65,7 @@ module.exports = React.createClass({
|
||||||
username: "",
|
username: "",
|
||||||
phoneCountry: null,
|
phoneCountry: null,
|
||||||
phoneNumber: "",
|
phoneNumber: "",
|
||||||
|
currentFlow: "m.login.password",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -129,23 +128,19 @@ module.exports = React.createClass({
|
||||||
this.setState({ phoneNumber: phoneNumber });
|
this.setState({ phoneNumber: phoneNumber });
|
||||||
},
|
},
|
||||||
|
|
||||||
onHsUrlChanged: function(newHsUrl) {
|
onServerConfigChange: function(config) {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.setState({
|
let newState = {
|
||||||
enteredHomeserverUrl: newHsUrl,
|
|
||||||
errorText: null, // reset err messages
|
errorText: null, // reset err messages
|
||||||
}, function() {
|
};
|
||||||
self._initLoginLogic(newHsUrl);
|
if (config.hsUrl !== undefined) {
|
||||||
});
|
newState.enteredHomeserverUrl = config.hsUrl;
|
||||||
},
|
}
|
||||||
|
if (config.isUrl !== undefined) {
|
||||||
onIsUrlChanged: function(newIsUrl) {
|
newState.enteredIdentityServerUrl = config.isUrl;
|
||||||
var self = this;
|
}
|
||||||
this.setState({
|
this.setState(newState, function() {
|
||||||
enteredIdentityServerUrl: newIsUrl,
|
self._initLoginLogic(config.hsUrl || null, config.isUrl);
|
||||||
errorText: null, // reset err messages
|
|
||||||
}, function() {
|
|
||||||
self._initLoginLogic(null, newIsUrl);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -161,25 +156,28 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
this._loginLogic = loginLogic;
|
this._loginLogic = loginLogic;
|
||||||
|
|
||||||
loginLogic.getFlows().then(function(flows) {
|
|
||||||
// old behaviour was to always use the first flow without presenting
|
|
||||||
// options. This works in most cases (we don't have a UI for multiple
|
|
||||||
// logins so let's skip that for now).
|
|
||||||
loginLogic.chooseFlow(0);
|
|
||||||
}, function(err) {
|
|
||||||
self._setStateFromError(err, false);
|
|
||||||
}).finally(function() {
|
|
||||||
self.setState({
|
|
||||||
busy: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
enteredHomeserverUrl: hsUrl,
|
enteredHomeserverUrl: hsUrl,
|
||||||
enteredIdentityServerUrl: isUrl,
|
enteredIdentityServerUrl: isUrl,
|
||||||
busy: true,
|
busy: true,
|
||||||
loginIncorrect: false,
|
loginIncorrect: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
loginLogic.getFlows().then(function(flows) {
|
||||||
|
// old behaviour was to always use the first flow without presenting
|
||||||
|
// options. This works in most cases (we don't have a UI for multiple
|
||||||
|
// logins so let's skip that for now).
|
||||||
|
loginLogic.chooseFlow(0);
|
||||||
|
self.setState({
|
||||||
|
currentFlow: self._getCurrentFlowStep(),
|
||||||
|
});
|
||||||
|
}, function(err) {
|
||||||
|
self._setStateFromError(err, false);
|
||||||
|
}).finally(function() {
|
||||||
|
self.setState({
|
||||||
|
busy: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
_getCurrentFlowStep: function() {
|
_getCurrentFlowStep: function() {
|
||||||
|
@ -231,6 +229,7 @@ module.exports = React.createClass({
|
||||||
componentForStep: function(step) {
|
componentForStep: function(step) {
|
||||||
switch (step) {
|
switch (step) {
|
||||||
case 'm.login.password':
|
case 'm.login.password':
|
||||||
|
const PasswordLogin = sdk.getComponent('login.PasswordLogin');
|
||||||
return (
|
return (
|
||||||
<PasswordLogin
|
<PasswordLogin
|
||||||
onSubmit={this.onPasswordLogin}
|
onSubmit={this.onPasswordLogin}
|
||||||
|
@ -242,9 +241,11 @@ module.exports = React.createClass({
|
||||||
onPhoneNumberChanged={this.onPhoneNumberChanged}
|
onPhoneNumberChanged={this.onPhoneNumberChanged}
|
||||||
onForgotPasswordClick={this.props.onForgotPasswordClick}
|
onForgotPasswordClick={this.props.onForgotPasswordClick}
|
||||||
loginIncorrect={this.state.loginIncorrect}
|
loginIncorrect={this.state.loginIncorrect}
|
||||||
|
hsDomain={url.parse(this.state.enteredHomeserverUrl).hostname}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case 'm.login.cas':
|
case 'm.login.cas':
|
||||||
|
const CasLogin = sdk.getComponent('login.CasLogin');
|
||||||
return (
|
return (
|
||||||
<CasLogin onSubmit={this.onCasLogin} />
|
<CasLogin onSubmit={this.onCasLogin} />
|
||||||
);
|
);
|
||||||
|
@ -262,10 +263,11 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var Loader = sdk.getComponent("elements.Spinner");
|
const Loader = sdk.getComponent("elements.Spinner");
|
||||||
var LoginHeader = sdk.getComponent("login.LoginHeader");
|
const LoginHeader = sdk.getComponent("login.LoginHeader");
|
||||||
var LoginFooter = sdk.getComponent("login.LoginFooter");
|
const LoginFooter = sdk.getComponent("login.LoginFooter");
|
||||||
var loader = this.state.busy ? <div className="mx_Login_loader"><Loader /></div> : null;
|
const ServerConfig = sdk.getComponent("login.ServerConfig");
|
||||||
|
const loader = this.state.busy ? <div className="mx_Login_loader"><Loader /></div> : null;
|
||||||
|
|
||||||
var loginAsGuestJsx;
|
var loginAsGuestJsx;
|
||||||
if (this.props.enableGuest) {
|
if (this.props.enableGuest) {
|
||||||
|
@ -291,15 +293,14 @@ module.exports = React.createClass({
|
||||||
<h2>Sign in
|
<h2>Sign in
|
||||||
{ loader }
|
{ loader }
|
||||||
</h2>
|
</h2>
|
||||||
{ this.componentForStep(this._getCurrentFlowStep()) }
|
{ this.componentForStep(this.state.currentFlow) }
|
||||||
<ServerConfig ref="serverConfig"
|
<ServerConfig ref="serverConfig"
|
||||||
withToggleButton={true}
|
withToggleButton={true}
|
||||||
customHsUrl={this.props.customHsUrl}
|
customHsUrl={this.props.customHsUrl}
|
||||||
customIsUrl={this.props.customIsUrl}
|
customIsUrl={this.props.customIsUrl}
|
||||||
defaultHsUrl={this.props.defaultHsUrl}
|
defaultHsUrl={this.props.defaultHsUrl}
|
||||||
defaultIsUrl={this.props.defaultIsUrl}
|
defaultIsUrl={this.props.defaultIsUrl}
|
||||||
onHsUrlChanged={this.onHsUrlChanged}
|
onServerConfigChange={this.onServerConfigChange}
|
||||||
onIsUrlChanged={this.onIsUrlChanged}
|
|
||||||
delayTimeMs={1000}/>
|
delayTimeMs={1000}/>
|
||||||
<div className="mx_Login_error">
|
<div className="mx_Login_error">
|
||||||
{ this.state.errorText }
|
{ this.state.errorText }
|
||||||
|
|
|
@ -248,13 +248,10 @@ export default class Dropdown extends React.Component {
|
||||||
</MenuOption>
|
</MenuOption>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
if (options.length === 0) {
|
||||||
if (!this.state.searchQuery && this.props.searchEnabled) {
|
return [<div className="mx_Dropdown_option">
|
||||||
options.push(
|
No results
|
||||||
<div key="_searchprompt" className="mx_Dropdown_searchPrompt">
|
</div>];
|
||||||
Type to search...
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
@ -317,7 +314,7 @@ Dropdown.propTypes = {
|
||||||
onOptionChange: React.PropTypes.func.isRequired,
|
onOptionChange: React.PropTypes.func.isRequired,
|
||||||
// Called when the value of the search field changes
|
// Called when the value of the search field changes
|
||||||
onSearchChange: React.PropTypes.func,
|
onSearchChange: React.PropTypes.func,
|
||||||
searchEnabled: React.PropTypes.boolean,
|
searchEnabled: React.PropTypes.bool,
|
||||||
// Function that, given the key of an option, returns
|
// Function that, given the key of an option, returns
|
||||||
// a node representing that option to be displayed in the
|
// a node representing that option to be displayed in the
|
||||||
// box itself as the currently-selected option (ie. as
|
// box itself as the currently-selected option (ie. as
|
||||||
|
|
|
@ -33,8 +33,6 @@ function countryMatchesSearchQuery(query, country) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_DISPLAYED_ROWS = 2;
|
|
||||||
|
|
||||||
export default class CountryDropdown extends React.Component {
|
export default class CountryDropdown extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -64,7 +62,7 @@ export default class CountryDropdown extends React.Component {
|
||||||
// Unicode Regional Indicator Symbol letter 'A'
|
// Unicode Regional Indicator Symbol letter 'A'
|
||||||
const RIS_A = 0x1F1E6;
|
const RIS_A = 0x1F1E6;
|
||||||
const ASCII_A = 65;
|
const ASCII_A = 65;
|
||||||
return charactersToImageNode(iso2,
|
return charactersToImageNode(iso2, true,
|
||||||
RIS_A + (iso2.charCodeAt(0) - ASCII_A),
|
RIS_A + (iso2.charCodeAt(0) - ASCII_A),
|
||||||
RIS_A + (iso2.charCodeAt(1) - ASCII_A),
|
RIS_A + (iso2.charCodeAt(1) - ASCII_A),
|
||||||
);
|
);
|
||||||
|
@ -93,10 +91,6 @@ export default class CountryDropdown extends React.Component {
|
||||||
displayedCountries = COUNTRIES;
|
displayedCountries = COUNTRIES;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (displayedCountries.length > MAX_DISPLAYED_ROWS) {
|
|
||||||
displayedCountries = displayedCountries.slice(0, MAX_DISPLAYED_ROWS);
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = displayedCountries.map((country) => {
|
const options = displayedCountries.map((country) => {
|
||||||
return <div key={country.iso2}>
|
return <div key={country.iso2}>
|
||||||
{this._flagImgForIso2(country.iso2)}
|
{this._flagImgForIso2(country.iso2)}
|
||||||
|
|
|
@ -25,56 +25,49 @@ import {field_input_incorrect} from '../../../UiEffects';
|
||||||
/**
|
/**
|
||||||
* A pure UI component which displays a username/password form.
|
* A pure UI component which displays a username/password form.
|
||||||
*/
|
*/
|
||||||
module.exports = React.createClass({displayName: 'PasswordLogin',
|
class PasswordLogin extends React.Component {
|
||||||
propTypes: {
|
static defaultProps = {
|
||||||
onSubmit: React.PropTypes.func.isRequired, // fn(username, password)
|
onUsernameChanged: function() {},
|
||||||
onForgotPasswordClick: React.PropTypes.func, // fn()
|
onPasswordChanged: function() {},
|
||||||
initialUsername: React.PropTypes.string,
|
onPhoneCountryChanged: function() {},
|
||||||
initialPhoneCountry: React.PropTypes.string,
|
onPhoneNumberChanged: function() {},
|
||||||
initialPhoneNumber: React.PropTypes.string,
|
initialUsername: "",
|
||||||
initialPassword: React.PropTypes.string,
|
initialPhoneCountry: "",
|
||||||
onUsernameChanged: React.PropTypes.func,
|
initialPhoneNumber: "",
|
||||||
onPhoneCountryChanged: React.PropTypes.func,
|
initialPassword: "",
|
||||||
onPhoneNumberChanged: React.PropTypes.func,
|
loginIncorrect: false,
|
||||||
onPasswordChanged: React.PropTypes.func,
|
hsDomain: "",
|
||||||
loginIncorrect: React.PropTypes.bool,
|
}
|
||||||
},
|
|
||||||
|
|
||||||
getDefaultProps: function() {
|
constructor(props) {
|
||||||
return {
|
super(props);
|
||||||
onUsernameChanged: function() {},
|
this.state = {
|
||||||
onPasswordChanged: function() {},
|
|
||||||
onPhoneCountryChanged: function() {},
|
|
||||||
onPhoneNumberChanged: function() {},
|
|
||||||
initialUsername: "",
|
|
||||||
initialPhoneCountry: "",
|
|
||||||
initialPhoneNumber: "",
|
|
||||||
initialPassword: "",
|
|
||||||
loginIncorrect: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
username: this.props.initialUsername,
|
username: this.props.initialUsername,
|
||||||
password: this.props.initialPassword,
|
password: this.props.initialPassword,
|
||||||
phoneCountry: this.props.initialPhoneCountry,
|
phoneCountry: this.props.initialPhoneCountry,
|
||||||
phoneNumber: this.props.initialPhoneNumber,
|
phoneNumber: this.props.initialPhoneNumber,
|
||||||
loginType: "mxid",
|
loginType: PasswordLogin.LOGIN_FIELD_MXID,
|
||||||
};
|
};
|
||||||
},
|
|
||||||
|
|
||||||
componentWillMount: function() {
|
this.onSubmitForm = this.onSubmitForm.bind(this);
|
||||||
|
this.onUsernameChanged = this.onUsernameChanged.bind(this);
|
||||||
|
this.onLoginTypeChange = this.onLoginTypeChange.bind(this);
|
||||||
|
this.onPhoneCountryChanged = this.onPhoneCountryChanged.bind(this);
|
||||||
|
this.onPhoneNumberChanged = this.onPhoneNumberChanged.bind(this);
|
||||||
|
this.onPasswordChanged = this.onPasswordChanged.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
this._passwordField = null;
|
this._passwordField = null;
|
||||||
},
|
}
|
||||||
|
|
||||||
componentWillReceiveProps: function(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if (!this.props.loginIncorrect && nextProps.loginIncorrect) {
|
if (!this.props.loginIncorrect && nextProps.loginIncorrect) {
|
||||||
field_input_incorrect(this._passwordField);
|
field_input_incorrect(this._passwordField);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
onSubmitForm: function(ev) {
|
onSubmitForm(ev) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
this.props.onSubmit(
|
this.props.onSubmit(
|
||||||
this.state.username,
|
this.state.username,
|
||||||
|
@ -82,33 +75,87 @@ module.exports = React.createClass({displayName: 'PasswordLogin',
|
||||||
this.state.phoneNumber,
|
this.state.phoneNumber,
|
||||||
this.state.password,
|
this.state.password,
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
onUsernameChanged: function(ev) {
|
onUsernameChanged(ev) {
|
||||||
this.setState({username: ev.target.value});
|
this.setState({username: ev.target.value});
|
||||||
this.props.onUsernameChanged(ev.target.value);
|
this.props.onUsernameChanged(ev.target.value);
|
||||||
},
|
}
|
||||||
|
|
||||||
onLoginTypeChange: function(loginType) {
|
onLoginTypeChange(loginType) {
|
||||||
this.setState({loginType: loginType});
|
this.setState({
|
||||||
},
|
loginType: loginType,
|
||||||
|
username: "" // Reset because email and username use the same state
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onPhoneCountryChanged: function(country) {
|
onPhoneCountryChanged(country) {
|
||||||
this.setState({phoneCountry: country});
|
this.setState({phoneCountry: country});
|
||||||
this.props.onPhoneCountryChanged(country);
|
this.props.onPhoneCountryChanged(country);
|
||||||
},
|
}
|
||||||
|
|
||||||
onPhoneNumberChanged: function(ev) {
|
onPhoneNumberChanged(ev) {
|
||||||
this.setState({phoneNumber: ev.target.value});
|
this.setState({phoneNumber: ev.target.value});
|
||||||
this.props.onPhoneNumberChanged(ev.target.value);
|
this.props.onPhoneNumberChanged(ev.target.value);
|
||||||
},
|
}
|
||||||
|
|
||||||
onPasswordChanged: function(ev) {
|
onPasswordChanged(ev) {
|
||||||
this.setState({password: ev.target.value});
|
this.setState({password: ev.target.value});
|
||||||
this.props.onPasswordChanged(ev.target.value);
|
this.props.onPasswordChanged(ev.target.value);
|
||||||
},
|
}
|
||||||
|
|
||||||
render: function() {
|
renderLoginField(loginType) {
|
||||||
|
switch(loginType) {
|
||||||
|
case PasswordLogin.LOGIN_FIELD_EMAIL:
|
||||||
|
return <input
|
||||||
|
className="mx_Login_field mx_Login_email"
|
||||||
|
key="email_input"
|
||||||
|
type="text"
|
||||||
|
name="username" // make it a little easier for browser's remember-password
|
||||||
|
onChange={this.onUsernameChanged}
|
||||||
|
placeholder="joe@example.com"
|
||||||
|
value={this.state.username}
|
||||||
|
autoFocus
|
||||||
|
/>;
|
||||||
|
case PasswordLogin.LOGIN_FIELD_MXID:
|
||||||
|
return <div className="mx_Login_username_group">
|
||||||
|
<div className="mx_Login_username_prefix">@</div>
|
||||||
|
<input
|
||||||
|
className="mx_Login_field mx_Login_username"
|
||||||
|
key="username_input"
|
||||||
|
type="text"
|
||||||
|
name="username" // make it a little easier for browser's remember-password
|
||||||
|
onChange={this.onUsernameChanged}
|
||||||
|
placeholder="username"
|
||||||
|
value={this.state.username}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
<div className="mx_Login_username_suffix">:{this.props.hsDomain}</div>
|
||||||
|
</div>;
|
||||||
|
case PasswordLogin.LOGIN_FIELD_PHONE:
|
||||||
|
const CountryDropdown = sdk.getComponent('views.login.CountryDropdown');
|
||||||
|
return <div className="mx_Login_phoneSection">
|
||||||
|
<CountryDropdown
|
||||||
|
className="mx_Login_phoneCountry"
|
||||||
|
ref="phone_country"
|
||||||
|
onOptionChange={this.onPhoneCountryChanged}
|
||||||
|
value={this.state.phoneCountry}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
className="mx_Login_phoneNumberField mx_Login_field"
|
||||||
|
ref="phoneNumber"
|
||||||
|
key="phone_input"
|
||||||
|
type="text"
|
||||||
|
name="phoneNumber"
|
||||||
|
onChange={this.onPhoneNumberChanged}
|
||||||
|
placeholder="Mobile phone number"
|
||||||
|
value={this.state.phoneNumber}
|
||||||
|
/>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
var forgotPasswordJsx;
|
var forgotPasswordJsx;
|
||||||
|
|
||||||
if (this.props.onForgotPasswordClick) {
|
if (this.props.onForgotPasswordClick) {
|
||||||
|
@ -124,47 +171,25 @@ module.exports = React.createClass({displayName: 'PasswordLogin',
|
||||||
error: this.props.loginIncorrect,
|
error: this.props.loginIncorrect,
|
||||||
});
|
});
|
||||||
|
|
||||||
const CountryDropdown = sdk.getComponent('views.login.CountryDropdown');
|
|
||||||
const Dropdown = sdk.getComponent('elements.Dropdown');
|
const Dropdown = sdk.getComponent('elements.Dropdown');
|
||||||
|
|
||||||
const loginType = {
|
const loginField = this.renderLoginField(this.state.loginType);
|
||||||
'email':
|
|
||||||
<input className="mx_Login_field mx_Login_username" type="text"
|
|
||||||
name="username" // make it a little easier for browser's remember-password
|
|
||||||
value={this.state.username} onChange={this.onUsernameChanged}
|
|
||||||
placeholder="Email or user name" autoFocus />,
|
|
||||||
'mxid':
|
|
||||||
<input className="mx_Login_field mx_Login_username" type="text"
|
|
||||||
name="username" // make it a little easier for browser's remember-password
|
|
||||||
value={this.state.username} onChange={this.onUsernameChanged}
|
|
||||||
placeholder="Email or user name" autoFocus />,
|
|
||||||
'phone': <div className="mx_Login_phoneSection">
|
|
||||||
<CountryDropdown ref="phone_country" onOptionChange={this.onPhoneCountryChanged}
|
|
||||||
className="mx_Login_phoneCountry"
|
|
||||||
value={this.state.phoneCountry}
|
|
||||||
/>
|
|
||||||
<input type="text" ref="phoneNumber"
|
|
||||||
onChange={this.onPhoneNumberChanged}
|
|
||||||
placeholder="Mobile phone number"
|
|
||||||
className="mx_Login_phoneNumberField mx_Login_field"
|
|
||||||
value={this.state.phoneNumber}
|
|
||||||
name="phoneNumber"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}[this.state.loginType];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<form onSubmit={this.onSubmitForm}>
|
<form onSubmit={this.onSubmitForm}>
|
||||||
<div className="mx_Login_type_container">
|
<div className="mx_Login_type_container">
|
||||||
<label className="mx_Login_type_label">I want to sign in with my</label>
|
<label className="mx_Login_type_label">I want to sign in with my</label>
|
||||||
<Dropdown className="mx_Login_type_dropdown" value={this.state.loginType} onOptionChange={this.onLoginTypeChange}>
|
<Dropdown
|
||||||
<span key="mxid">Matrix ID</span>
|
className="mx_Login_type_dropdown"
|
||||||
<span key="email">Email</span>
|
value={this.state.loginType}
|
||||||
<span key="phone">Phone</span>
|
onOptionChange={this.onLoginTypeChange}>
|
||||||
|
<span key={PasswordLogin.LOGIN_FIELD_MXID}>Matrix ID</span>
|
||||||
|
<span key={PasswordLogin.LOGIN_FIELD_EMAIL}>Email Address</span>
|
||||||
|
<span key={PasswordLogin.LOGIN_FIELD_PHONE}>Phone</span>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
{loginType}
|
{loginField}
|
||||||
<input className={pwFieldClass} ref={(e) => {this._passwordField = e;}} type="password"
|
<input className={pwFieldClass} ref={(e) => {this._passwordField = e;}} type="password"
|
||||||
name="password"
|
name="password"
|
||||||
value={this.state.password} onChange={this.onPasswordChanged}
|
value={this.state.password} onChange={this.onPasswordChanged}
|
||||||
|
@ -176,4 +201,25 @@ module.exports = React.createClass({displayName: 'PasswordLogin',
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
PasswordLogin.LOGIN_FIELD_EMAIL = "login_field_email";
|
||||||
|
PasswordLogin.LOGIN_FIELD_MXID = "login_field_mxid";
|
||||||
|
PasswordLogin.LOGIN_FIELD_PHONE = "login_field_phone";
|
||||||
|
|
||||||
|
PasswordLogin.propTypes = {
|
||||||
|
onSubmit: React.PropTypes.func.isRequired, // fn(username, password)
|
||||||
|
onForgotPasswordClick: React.PropTypes.func, // fn()
|
||||||
|
initialUsername: React.PropTypes.string,
|
||||||
|
initialPhoneCountry: React.PropTypes.string,
|
||||||
|
initialPhoneNumber: React.PropTypes.string,
|
||||||
|
initialPassword: React.PropTypes.string,
|
||||||
|
onUsernameChanged: React.PropTypes.func,
|
||||||
|
onPhoneCountryChanged: React.PropTypes.func,
|
||||||
|
onPhoneNumberChanged: React.PropTypes.func,
|
||||||
|
onPasswordChanged: React.PropTypes.func,
|
||||||
|
loginIncorrect: React.PropTypes.bool,
|
||||||
|
hsDomain: React.PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = PasswordLogin;
|
||||||
|
|
|
@ -27,8 +27,7 @@ module.exports = React.createClass({
|
||||||
displayName: 'ServerConfig',
|
displayName: 'ServerConfig',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
onHsUrlChanged: React.PropTypes.func,
|
onServerConfigChange: React.PropTypes.func,
|
||||||
onIsUrlChanged: React.PropTypes.func,
|
|
||||||
|
|
||||||
// default URLs are defined in config.json (or the hardcoded defaults)
|
// default URLs are defined in config.json (or the hardcoded defaults)
|
||||||
// they are used if the user has not overridden them with a custom URL.
|
// they are used if the user has not overridden them with a custom URL.
|
||||||
|
@ -50,8 +49,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
onHsUrlChanged: function() {},
|
onServerConfigChange: function() {},
|
||||||
onIsUrlChanged: function() {},
|
|
||||||
customHsUrl: "",
|
customHsUrl: "",
|
||||||
customIsUrl: "",
|
customIsUrl: "",
|
||||||
withToggleButton: false,
|
withToggleButton: false,
|
||||||
|
@ -75,7 +73,10 @@ module.exports = React.createClass({
|
||||||
this._hsTimeoutId = this._waitThenInvoke(this._hsTimeoutId, function() {
|
this._hsTimeoutId = this._waitThenInvoke(this._hsTimeoutId, function() {
|
||||||
var hsUrl = this.state.hs_url.trim().replace(/\/$/, "");
|
var hsUrl = this.state.hs_url.trim().replace(/\/$/, "");
|
||||||
if (hsUrl === "") hsUrl = this.props.defaultHsUrl;
|
if (hsUrl === "") hsUrl = this.props.defaultHsUrl;
|
||||||
this.props.onHsUrlChanged(hsUrl);
|
this.props.onServerConfigChange({
|
||||||
|
hsUrl : this.state.hs_url,
|
||||||
|
isUrl : this.state.is_url,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -85,7 +86,10 @@ module.exports = React.createClass({
|
||||||
this._isTimeoutId = this._waitThenInvoke(this._isTimeoutId, function() {
|
this._isTimeoutId = this._waitThenInvoke(this._isTimeoutId, function() {
|
||||||
var isUrl = this.state.is_url.trim().replace(/\/$/, "");
|
var isUrl = this.state.is_url.trim().replace(/\/$/, "");
|
||||||
if (isUrl === "") isUrl = this.props.defaultIsUrl;
|
if (isUrl === "") isUrl = this.props.defaultIsUrl;
|
||||||
this.props.onIsUrlChanged(isUrl);
|
this.props.onServerConfigChange({
|
||||||
|
hsUrl : this.state.hs_url,
|
||||||
|
isUrl : this.state.is_url,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -102,12 +106,16 @@ module.exports = React.createClass({
|
||||||
configVisible: visible
|
configVisible: visible
|
||||||
});
|
});
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
this.props.onHsUrlChanged(this.props.defaultHsUrl);
|
this.props.onServerConfigChange({
|
||||||
this.props.onIsUrlChanged(this.props.defaultIsUrl);
|
hsUrl : this.props.defaultHsUrl,
|
||||||
|
isUrl : this.props.defaultIsUrl,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.props.onHsUrlChanged(this.state.hs_url);
|
this.props.onServerConfigChange({
|
||||||
this.props.onIsUrlChanged(this.state.is_url);
|
hsUrl : this.state.hs_url,
|
||||||
|
isUrl : this.state.is_url,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue