diff --git a/res/css/_components.scss b/res/css/_components.scss index 97da563347..e017d4b95a 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -30,6 +30,7 @@ @import "./views/auth/_AuthHeader.scss"; @import "./views/auth/_AuthHeaderLogo.scss"; @import "./views/auth/_AuthPage.scss"; +@import "./views/auth/_CountryDropdown.scss"; @import "./views/auth/_InteractiveAuthEntryComponents.scss"; @import "./views/auth/_LanguageSelector.scss"; @import "./views/auth/_ServerConfig.scss"; diff --git a/res/css/structures/auth/_Login.scss b/res/css/structures/auth/_Login.scss index 5ba4a38c95..b48021c27a 100644 --- a/res/css/structures/auth/_Login.scss +++ b/res/css/structures/auth/_Login.scss @@ -15,13 +15,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_Login_support { - text-align: center; - font-size: 13px; - margin-top: 0px; - opacity: 0.7; -} - .mx_Login_field { width: 100%; box-sizing: border-box; @@ -33,14 +26,6 @@ limitations under the License. margin-bottom: 14px; } -.mx_Login_fieldLabel { - margin-top: -10px; - margin-left: 8px; - margin-bottom: 14px; - font-size: 13px; - opacity: 0.8; -} - .mx_Login_submit { @mixin mx_DialogButton; width: 100%; @@ -58,16 +43,6 @@ limitations under the License. opacity: 0.3; } -.mx_Login_label { - font-size: 13px; - opacity: 0.8; -} - -.mx_Login_checkbox, -.mx_Login_radio { - margin-right: 10px; -} - .mx_AuthBody a.mx_Login_sso_link:link, .mx_AuthBody a.mx_Login_sso_link:hover, .mx_AuthBody a.mx_Login_sso_link:visited { @@ -119,10 +94,6 @@ limitations under the License. flex: 1 1 auto; } -.mx_Login_field_group { - display: flex; -} - .mx_Login_field_prefix { height: 38px; padding: 0px 5px; @@ -147,7 +118,6 @@ limitations under the License. .mx_Login_phoneCountry { margin-bottom: 14px; - width: 150px; /* To override mx_Login_field_prefix */ text-align: left; diff --git a/res/css/views/auth/_AuthBody.scss b/res/css/views/auth/_AuthBody.scss index 845aea3fd9..08a8b4ff4a 100644 --- a/res/css/views/auth/_AuthBody.scss +++ b/res/css/views/auth/_AuthBody.scss @@ -47,6 +47,24 @@ limitations under the License. box-sizing: border-box; } +.mx_Auth_fieldRow { + display: flex; + margin-bottom: 10px; +} + +.mx_Auth_fieldRow > * { + margin: 0 5px; + flex: 1; +} + +.mx_Auth_fieldRow > *:first-child { + margin-left: 0; +} + +.mx_Auth_fieldRow > *:last-child { + margin-right: 0; +} + .mx_AuthBody a:link, .mx_AuthBody a:hover, .mx_AuthBody a:visited { diff --git a/res/css/views/auth/_CountryDropdown.scss b/res/css/views/auth/_CountryDropdown.scss new file mode 100644 index 0000000000..6fd3c0182c --- /dev/null +++ b/res/css/views/auth/_CountryDropdown.scss @@ -0,0 +1,34 @@ +/* +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_CountryDropdown .mx_Dropdown_input .mx_Dropdown_option { + padding: 0 3px; +} + +.mx_CountryDropdown .mx_Dropdown_arrow { + padding-right: 3px; +} + +.mx_CountryDropdown_shortOption { + display: inline-flex; + align-items: center; + height: 100%; +} + +.mx_CountryDropdown_option { + display: flex; + align-items: center; +} diff --git a/res/css/views/auth/_LanguageSelector.scss b/res/css/views/auth/_LanguageSelector.scss index e73dd4ac64..be40c9e6cd 100644 --- a/res/css/views/auth/_LanguageSelector.scss +++ b/res/css/views/auth/_LanguageSelector.scss @@ -25,22 +25,6 @@ limitations under the License. color: $authpage-lang-color; } -/* TODO: Consider using this new arrow for all dropdowns */ -.mx_Auth_language .mx_Dropdown_arrow { - width: 10px; - height: 6px; - border: none; - right: 6px; -} - .mx_Auth_language .mx_Dropdown_arrow::before { - content: ""; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - mask: url('$(res)/img/feather-icons/dropdown-arrow.svg'); - mask-repeat: no-repeat; background: $authpage-lang-color; } diff --git a/res/css/views/elements/_Dropdown.scss b/res/css/views/elements/_Dropdown.scss index 69dd1703ee..9d4fb5c4a9 100644 --- a/res/css/views/elements/_Dropdown.scss +++ b/res/css/views/elements/_Dropdown.scss @@ -23,11 +23,12 @@ limitations under the License. } .mx_Dropdown_input { + display: flex; + align-items: center; position: relative; border-radius: 3px; border: 1px solid $strong-input-border-color; - font-weight: 300; - font-size: 13px; + font-size: 12px; user-select: none; } @@ -41,19 +42,23 @@ limitations under the License. } .mx_Dropdown_arrow { - border-color: $primary-fg-color transparent transparent; - border-style: solid; - border-width: 5px 5px 0; display: block; - height: 0; - position: absolute; - right: 10px; - top: 14px; - width: 0 + position: relative; + width: 10px; + height: 6px; + padding-right: 8px; } -.mx_Dropdown.left_aligned .mx_Dropdown_arrow { - left: 10px; +.mx_Dropdown_arrow::before { + content: ""; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + mask: url('$(res)/img/feather-icons/dropdown-arrow.svg'); + mask-repeat: no-repeat; + background: $primary-fg-color; } .mx_Dropdown_input > .mx_Dropdown_option { @@ -62,10 +67,6 @@ limitations under the License. white-space: nowrap; } -.mx_Dropdown.left_aligned .mx_Dropdown_input > .mx_Dropdown_option { - padding-left: 25px; -} - .mx_Dropdown_option { height: 35px; line-height: 35px; @@ -81,7 +82,7 @@ limitations under the License. .mx_Dropdown_option img { margin: 5px; - width: 27px; + width: 16px; vertical-align: middle; } diff --git a/src/components/structures/auth/Login.js b/src/components/structures/auth/Login.js index 85ccecdac3..8d846112b6 100644 --- a/src/components/structures/auth/Login.js +++ b/src/components/structures/auth/Login.js @@ -539,7 +539,7 @@ module.exports = React.createClass({ return errorText; }, - serverComponentForStep() { + renderServerComponentForStep() { const ServerTypeSelector = sdk.getComponent("auth.ServerTypeSelector"); const ServerConfig = sdk.getComponent("auth.ServerConfig"); const ModularServerConfig = sdk.getComponent("auth.ModularServerConfig"); @@ -605,7 +605,7 @@ module.exports = React.createClass({ ; }, - loginComponentForStep() { + renderLoginComponentForStep() { if (PHASES_ENABLED && this.state.phase !== PHASE_LOGIN) { return null; } @@ -707,8 +707,8 @@ module.exports = React.createClass({ {loader} { errorTextSection } - { this.serverComponentForStep() } - { this.loginComponentForStep() } + { this.renderServerComponentForStep() } + { this.renderLoginComponentForStep() } { _t('Create account') } diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js index 3a63115a43..ca2d7716a9 100644 --- a/src/components/structures/auth/Registration.js +++ b/src/components/structures/auth/Registration.js @@ -23,13 +23,22 @@ import React from 'react'; import PropTypes from 'prop-types'; import sdk from '../../../index'; -import RegistrationForm from '../../views/auth/RegistrationForm'; import { _t, _td } from '../../../languageHandler'; import SdkConfig from '../../../SdkConfig'; import { messageForResourceLimitError } from '../../../utils/ErrorUtils'; +import * as ServerType from '../../views/auth/ServerTypeSelector'; const MIN_PASSWORD_LENGTH = 6; +// Phases +// Show controls to configure server details +const PHASE_SERVER_DETAILS = 0; +// Show the appropriate registration flow(s) for the server +const PHASE_REGISTRATION = 1; + +// Enable phases for registration +const PHASES_ENABLED = true; + module.exports = React.createClass({ displayName: 'Registration', @@ -82,6 +91,7 @@ module.exports = React.createClass({ // If we've been given a session ID, we're resuming // straight back into UI auth doingUIAuth: Boolean(this.props.sessionId), + serverType: null, hsUrl: this.props.customHsUrl, isUrl: this.props.customIsUrl, flows: null, @@ -107,6 +117,39 @@ module.exports = React.createClass({ }); }, + onServerTypeChange(type) { + this.setState({ + serverType: type, + }); + + // When changing server types, set the HS / IS URLs to reasonable defaults for the + // the new type. + switch (type) { + case ServerType.FREE: { + const { hsUrl, isUrl } = ServerType.TYPES.FREE; + this.onServerConfigChange({ + hsUrl, + isUrl, + }); + // Move directly to the registration phase since the server details are fixed. + this.setState({ + phase: PHASE_REGISTRATION, + }); + break; + } + case ServerType.PREMIUM: + case ServerType.ADVANCED: + this.onServerConfigChange({ + hsUrl: this.props.defaultHsUrl, + isUrl: this.props.defaultIsUrl, + }); + this.setState({ + phase: PHASE_SERVER_DETAILS, + }); + break; + } + }, + _replaceClient: async function() { this._matrixClient = Matrix.createClient({ baseUrl: this.state.hsUrl, @@ -273,6 +316,21 @@ module.exports = React.createClass({ this.props.onLoginClick(); }, + onServerDetailsNextPhaseClick(ev) { + ev.stopPropagation(); + this.setState({ + phase: PHASE_REGISTRATION, + }); + }, + + onEditServerDetailsClick(ev) { + ev.preventDefault(); + ev.stopPropagation(); + this.setState({ + phase: PHASE_SERVER_DETAILS, + }); + }, + _makeRegisterRequest: function(auth) { // Only send the bind params if we're sending username / pw params // (Since we need to send no params at all to use the ones saved in the @@ -300,62 +358,127 @@ module.exports = React.createClass({ }; }, + renderServerComponent() { + const ServerTypeSelector = sdk.getComponent("auth.ServerTypeSelector"); + const ServerConfig = sdk.getComponent("auth.ServerConfig"); + const ModularServerConfig = sdk.getComponent("auth.ModularServerConfig"); + const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); + + // TODO: May need to adjust the behavior of this config option + if (SdkConfig.get()['disable_custom_urls']) { + return null; + } + + // If we're on a different phase, we only show the server type selector, + // which is always shown if we allow custom URLs at all. + if (PHASES_ENABLED && this.state.phase !== PHASE_SERVER_DETAILS) { + return
+ +
; + } + + let serverDetails = null; + switch (this.state.serverType) { + case ServerType.FREE: + break; + case ServerType.PREMIUM: + serverDetails = ; + break; + case ServerType.ADVANCED: + serverDetails = ; + break; + } + + let nextButton = null; + if (PHASES_ENABLED) { + nextButton = + {_t("Next")} + ; + } + + return
+ + {serverDetails} + {nextButton} +
; + }, + + renderRegisterComponent() { + if (PHASES_ENABLED && this.state.phase !== PHASE_REGISTRATION) { + return null; + } + + const InteractiveAuth = sdk.getComponent('structures.InteractiveAuth'); + const Spinner = sdk.getComponent('elements.Spinner'); + const RegistrationForm = sdk.getComponent('auth.RegistrationForm'); + + if (this.state.doingUIAuth) { + return ; + } else if (this.state.busy || !this.state.flows) { + return ; + } else { + let onEditServerDetailsClick = null; + // If custom URLs are allowed and we haven't selected the Free server type, wire + // up the server details edit link. + if ( + PHASES_ENABLED && + !SdkConfig.get()['disable_custom_urls'] && + this.state.serverType !== ServerType.FREE + ) { + onEditServerDetailsClick = this.onEditServerDetailsClick; + } + return ; + } + }, + render: function() { const AuthHeader = sdk.getComponent('auth.AuthHeader'); const AuthBody = sdk.getComponent("auth.AuthBody"); const AuthPage = sdk.getComponent('auth.AuthPage'); - const InteractiveAuth = sdk.getComponent('structures.InteractiveAuth'); - const Spinner = sdk.getComponent("elements.Spinner"); - const ServerConfig = sdk.getComponent('views.auth.ServerConfig'); - - let registerBody; - if (this.state.doingUIAuth) { - registerBody = ( - - ); - } else if (this.state.busy || !this.state.flows) { - registerBody = ; - } else { - let serverConfigSection; - if (!SdkConfig.get()['disable_custom_urls']) { - serverConfigSection = ( - - ); - } - registerBody = ( -
- - { serverConfigSection } -
- ); - } let errorText; const err = this.state.errorText || this.props.defaultServerDiscoveryError; @@ -377,9 +500,10 @@ module.exports = React.createClass({

{ _t('Create your account') }

- { registerBody } - { signIn } { errorText } + { this.renderServerComponent() } + { this.renderRegisterComponent() } + { signIn }
); diff --git a/src/components/views/auth/CountryDropdown.js b/src/components/views/auth/CountryDropdown.js index 3a71d5d1c8..3cbdfe818e 100644 --- a/src/components/views/auth/CountryDropdown.js +++ b/src/components/views/auth/CountryDropdown.js @@ -81,7 +81,7 @@ export default class CountryDropdown extends React.Component { if (this.props.showPrefix) { countryPrefix = '+' + COUNTRIES_BY_ISO2[iso2].prefix; } - return + return { this._flagImgForIso2(iso2) } { countryPrefix } ; @@ -111,7 +111,7 @@ export default class CountryDropdown extends React.Component { } const options = displayedCountries.map((country) => { - return
+ return
{ this._flagImgForIso2(country.iso2) } { country.name } (+{ country.prefix })
; @@ -121,7 +121,7 @@ export default class CountryDropdown extends React.Component { // values between mounting and the initial value propgating const value = this.props.value || COUNTRIES[0].iso2; - return ?Promise + onEditServerDetailsClick: PropTypes.func, flows: PropTypes.arrayOf(PropTypes.object).isRequired, }, @@ -256,9 +257,34 @@ module.exports = React.createClass({ render: function() { const self = this; + let yourMatrixAccountText = _t('Create your account'); + if (this.props.hsName) { + yourMatrixAccountText = _t('Create your %(serverName)s account', { + serverName: this.props.hsName, + }); + } else { + try { + const parsedHsUrl = new URL(this.props.hsUrl); + yourMatrixAccountText = _t('Create your %(serverName)s account', { + serverName: parsedHsUrl.hostname, + }); + } catch (e) { + // ignore + } + } + + let editLink = null; + if (this.props.onEditServerDetailsClick) { + editLink = + {_t('Edit')} + ; + } + const emailPlaceholder = this._authStepIsRequired('m.login.email.identity') ? - _t("Email address") : - _t("Email address (optional)"); + _t("Email") : + _t("Email (optional)"); const emailSection = (
@@ -275,8 +301,8 @@ module.exports = React.createClass({ let phoneSection; if (!SdkConfig.get().disable_3pid_login) { const phonePlaceholder = this._authStepIsRequired('m.login.msisdn') ? - _t("Mobile phone number") : - _t("Mobile phone number (optional)"); + _t("Phone") : + _t("Phone (optional)"); phoneSection = (
+

+ {yourMatrixAccountText} + {editLink} +

- { emailSection } - { phoneSection } - -
- -
- -
+
+ +
+
+ + +
+
+ { emailSection } + { phoneSection } +
+ {_t( + "Use an email address to receover your account. Other users " + + "can invite you to rooms using your contact details.", + )} { registerButton }
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 341b9754a5..db8f499608 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1210,8 +1210,13 @@ "Sign in with": "Sign in with", "Sign in": "Sign in", "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?", - "Email address (optional)": "Email address (optional)", - "Mobile phone number (optional)": "Mobile phone number (optional)", + "Create your account": "Create your account", + "Create your %(serverName)s account": "Create your %(serverName)s account", + "Email": "Email", + "Email (optional)": "Email (optional)", + "Phone (optional)": "Phone (optional)", + "Confirm": "Confirm", + "Use an email address to receover your account. Other users can invite you to rooms using your contact details.": "Use an email address to receover your account. Other users can invite you to rooms using your contact details.", "Other servers": "Other servers", "Enter custom server URLs What does this mean?": "Enter custom server URLs What does this mean?", "Homeserver URL": "Homeserver URL", @@ -1372,7 +1377,6 @@ "Desktop specific": "Desktop specific", "Missing Media Permissions, click here to request.": "Missing Media Permissions, click here to request.", "VoIP": "VoIP", - "Email": "Email", "Add email address": "Add email address", "Display name": "Display name", "To return to your account in future you need to set a password": "To return to your account in future you need to set a password", @@ -1426,7 +1430,6 @@ "A phone number is required to register on this homeserver.": "A phone number is required to register on this homeserver.", "You need to enter a username.": "You need to enter a username.", "An unknown error occurred.": "An unknown error occurred.", - "Create your account": "Create your account", "Commands": "Commands", "Results from DuckDuckGo": "Results from DuckDuckGo", "Emoji": "Emoji",