- {hostingLink}
+ {topSection}
{primaryOptionList}
{secondarySection}
;
diff --git a/src/components/structures/auth/ForgotPassword.js b/src/components/structures/auth/ForgotPassword.js
index f9f5263f7e..5a39fe9fd9 100644
--- a/src/components/structures/auth/ForgotPassword.js
+++ b/src/components/structures/auth/ForgotPassword.js
@@ -21,16 +21,14 @@ import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
import * as sdk from '../../../index';
import Modal from "../../../Modal";
-import SdkConfig from "../../../SdkConfig";
import PasswordReset from "../../../PasswordReset";
import AutoDiscoveryUtils, {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
import classNames from 'classnames';
import AuthPage from "../../views/auth/AuthPage";
import CountlyAnalytics from "../../../CountlyAnalytics";
+import ServerPicker from "../../views/elements/ServerPicker";
// Phases
-// Show controls to configure server details
-const PHASE_SERVER_DETAILS = 0;
// Show the forgot password inputs
const PHASE_FORGOT = 1;
// Email is in the process of being sent
@@ -62,7 +60,6 @@ export default class ForgotPassword extends React.Component {
serverIsAlive: true,
serverErrorIsFatal: false,
serverDeadError: "",
- serverRequiresIdServer: null,
};
constructor(props) {
@@ -93,12 +90,8 @@ export default class ForgotPassword extends React.Component {
serverConfig.isUrl,
);
- const pwReset = new PasswordReset(serverConfig.hsUrl, serverConfig.isUrl);
- const serverRequiresIdServer = await pwReset.doesServerRequireIdServerParam();
-
this.setState({
serverIsAlive: true,
- serverRequiresIdServer,
});
} catch (e) {
this.setState(AutoDiscoveryUtils.authComponentStateForError(e, "forgot_password"));
@@ -177,20 +170,6 @@ export default class ForgotPassword extends React.Component {
});
};
- onServerDetailsNextPhaseClick = async () => {
- this.setState({
- phase: PHASE_FORGOT,
- });
- };
-
- onEditServerDetailsClick = ev => {
- ev.preventDefault();
- ev.stopPropagation();
- this.setState({
- phase: PHASE_SERVER_DETAILS,
- });
- };
-
onLoginClick = ev => {
ev.preventDefault();
ev.stopPropagation();
@@ -205,24 +184,6 @@ export default class ForgotPassword extends React.Component {
});
}
- renderServerDetails() {
- const ServerConfig = sdk.getComponent("auth.ServerConfig");
-
- if (SdkConfig.get()['disable_custom_urls']) {
- return null;
- }
-
- return ;
- }
-
renderForgot() {
const Field = sdk.getComponent('elements.Field');
@@ -246,57 +207,13 @@ export default class ForgotPassword extends React.Component {
);
}
- let yourMatrixAccountText = _t('Your Matrix account on %(serverName)s', {
- serverName: this.props.serverConfig.hsName,
- });
- if (this.props.serverConfig.hsNameIsDifferent) {
- const TextWithTooltip = sdk.getComponent("elements.TextWithTooltip");
-
- yourMatrixAccountText = _t('Your Matrix account on ', {}, {
- 'underlinedServerName': () => {
- return
- {this.props.serverConfig.hsName}
- ;
- },
- });
- }
-
- // If custom URLs are allowed, wire up the server details edit link.
- let editLink = null;
- if (!SdkConfig.get()['disable_custom_urls']) {
- editLink =
- {_t('Change')}
- ;
- }
-
- if (!this.props.serverConfig.isUrl && this.state.serverRequiresIdServer) {
- return
-
- {yourMatrixAccountText}
- {editLink}
-
- {_t(
- "No identity server is configured: " +
- "add one in server settings to reset your password.",
- )}
-
- {_t('Sign in instead')}
-
-
;
- }
-
return
{errorText}
{serverDeadSection}
-
- {yourMatrixAccountText}
- {editLink}
-
+
;
} else if (SettingsStore.getValue(UIFeature.Registration)) {
footer = (
-
- { _t('Create account') }
-
+
+ {_t("New? Create account", {}, {
+ a: sub => { sub },
+ })}
+
);
}
@@ -686,8 +596,11 @@ export default class LoginComponent extends React.Component {
{ errorTextSection }
{ serverDeadSection }
- { this.renderServerComponent() }
- { this.renderLoginComponentForStep() }
+
+ { this.renderLoginComponentForFlows() }
{ footer }
diff --git a/src/components/structures/auth/Registration.tsx b/src/components/structures/auth/Registration.tsx
index f97f20cf59..e1a2fc5590 100644
--- a/src/components/structures/auth/Registration.tsx
+++ b/src/components/structures/auth/Registration.tsx
@@ -15,29 +15,21 @@ limitations under the License.
*/
import Matrix from 'matrix-js-sdk';
-import React, {ComponentProps, ReactNode} from 'react';
+import React, {ReactNode} from 'react';
import {MatrixClient} from "matrix-js-sdk/src/client";
import * as sdk from '../../../index';
import { _t, _td } from '../../../languageHandler';
-import SdkConfig from '../../../SdkConfig';
import { messageForResourceLimitError } from '../../../utils/ErrorUtils';
-import * as ServerType from '../../views/auth/ServerTypeSelector';
import AutoDiscoveryUtils, {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
import classNames from "classnames";
import * as Lifecycle from '../../../Lifecycle';
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import AuthPage from "../../views/auth/AuthPage";
-import Login from "../../../Login";
+import Login, {ISSOFlow} from "../../../Login";
import dis from "../../../dispatcher/dispatcher";
-
-// Phases
-enum Phase {
- // Show controls to configure server details
- ServerDetails = 0,
- // Show the appropriate registration flow(s) for the server
- Registration = 1,
-}
+import SSOButtons from "../../views/elements/SSOButtons";
+import ServerPicker from '../../views/elements/ServerPicker';
interface IProps {
serverConfig: ValidatedServerConfig;
@@ -47,6 +39,7 @@ interface IProps {
clientSecret?: string;
sessionId?: string;
idSid?: string;
+ fragmentAfterLogin?: string;
// Called when the user has logged in. Params:
// - object with userId, deviceId, homeserverUrl, identityServerUrl, accessToken
@@ -92,9 +85,6 @@ interface IState {
// If set, we've registered but are not going to log
// the user in to their new account automatically.
completedNoSignin: boolean;
- serverType: ServerType.FREE | ServerType.PREMIUM | ServerType.ADVANCED;
- // Phase of the overall registration dialog.
- phase: Phase;
flows: {
stages: string[];
}[];
@@ -109,23 +99,22 @@ interface IState {
// Our matrix client - part of state because we can't render the UI auth
// component without it.
matrixClient?: MatrixClient;
- // whether the HS requires an ID server to register with a threepid
- serverRequiresIdServer?: boolean;
// The user ID we've just registered
registeredUsername?: string;
// if a different user ID to the one we just registered is logged in,
// this is the user ID that's logged in.
differentLoggedInUserId?: string;
+ // the SSO flow definition, this is fetched from /login as that's the only
+ // place it is exposed.
+ ssoFlow?: ISSOFlow;
}
-// Enable phases for registration
-const PHASES_ENABLED = true;
-
export default class Registration extends React.Component {
+ loginLogic: Login;
+
constructor(props) {
super(props);
- const serverType = ServerType.getTypeFromServerConfig(this.props.serverConfig);
this.state = {
busy: false,
errorText: null,
@@ -133,14 +122,17 @@ export default class Registration extends React.Component {
email: this.props.email,
},
doingUIAuth: Boolean(this.props.sessionId),
- serverType,
- phase: Phase.Registration,
flows: null,
completedNoSignin: false,
serverIsAlive: true,
serverErrorIsFatal: false,
serverDeadError: "",
};
+
+ const {hsUrl, isUrl} = this.props.serverConfig;
+ this.loginLogic = new Login(hsUrl, isUrl, null, {
+ defaultDeviceDisplayName: "Element login check", // We shouldn't ever be used
+ });
}
componentDidMount() {
@@ -154,61 +146,8 @@ export default class Registration extends React.Component {
newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return;
this.replaceClient(newProps.serverConfig);
-
- // Handle cases where the user enters "https://matrix.org" for their server
- // from the advanced option - we should default to FREE at that point.
- const serverType = ServerType.getTypeFromServerConfig(newProps.serverConfig);
- if (serverType !== this.state.serverType) {
- // Reset the phase to default phase for the server type.
- this.setState({
- serverType,
- phase: Registration.getDefaultPhaseForServerType(serverType),
- });
- }
}
- private static getDefaultPhaseForServerType(type: IState["serverType"]) {
- switch (type) {
- case ServerType.FREE: {
- // Move directly to the registration phase since the server
- // details are fixed.
- return Phase.Registration;
- }
- case ServerType.PREMIUM:
- case ServerType.ADVANCED:
- return Phase.ServerDetails;
- }
- }
-
- private onServerTypeChange = (type: IState["serverType"]) => {
- 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 { serverConfig } = ServerType.TYPES.FREE;
- this.props.onServerConfigChange(serverConfig);
- break;
- }
- case ServerType.PREMIUM:
- // We can accept whatever server config was the default here as this essentially
- // acts as a slightly different "custom server"/ADVANCED option.
- break;
- case ServerType.ADVANCED:
- // Use the default config from the config
- this.props.onServerConfigChange(SdkConfig.get()["validated_server_config"]);
- break;
- }
-
- // Reset the phase to default phase for the server type.
- this.setState({
- phase: Registration.getDefaultPhaseForServerType(type),
- });
- };
-
private async replaceClient(serverConfig: ValidatedServerConfig) {
this.setState({
errorText: null,
@@ -245,16 +184,20 @@ export default class Registration extends React.Component {
idBaseUrl: isUrl,
});
- let serverRequiresIdServer = true;
+ this.loginLogic.setHomeserverUrl(hsUrl);
+ this.loginLogic.setIdentityServerUrl(isUrl);
+
+ let ssoFlow: ISSOFlow;
try {
- serverRequiresIdServer = await cli.doesServerRequireIdServerParam();
+ const loginFlows = await this.loginLogic.getFlows();
+ ssoFlow = loginFlows.find(f => f.type === "m.login.sso" || f.type === "m.login.cas") as ISSOFlow;
} catch (e) {
- console.log("Unable to determine is server needs id_server param", e);
+ console.error("Failed to get login flows to check for SSO support", e);
}
this.setState({
matrixClient: cli,
- serverRequiresIdServer,
+ ssoFlow,
busy: false,
});
const showGenericError = (e) => {
@@ -282,26 +225,16 @@ export default class Registration extends React.Component {
// At this point registration is pretty much disabled, but before we do that let's
// quickly check to see if the server supports SSO instead. If it does, we'll send
// the user off to the login page to figure their account out.
- try {
- const loginLogic = new Login(hsUrl, isUrl, null, {
- defaultDeviceDisplayName: "Element login check", // We shouldn't ever be used
+ if (ssoFlow) {
+ // Redirect to login page - server probably expects SSO only
+ dis.dispatch({action: 'start_login'});
+ } else {
+ this.setState({
+ serverErrorIsFatal: true, // fatal because user cannot continue on this server
+ errorText: _t("Registration has been disabled on this homeserver."),
+ // add empty flows array to get rid of spinner
+ flows: [],
});
- const flows = await loginLogic.getFlows();
- const hasSsoFlow = flows.find(f => f.type === 'm.login.sso' || f.type === 'm.login.cas');
- if (hasSsoFlow) {
- // Redirect to login page - server probably expects SSO only
- dis.dispatch({action: 'start_login'});
- } else {
- this.setState({
- serverErrorIsFatal: true, // fatal because user cannot continue on this server
- errorText: _t("Registration has been disabled on this homeserver."),
- // add empty flows array to get rid of spinner
- flows: [],
- });
- }
- } catch (e) {
- console.error("Failed to get login flows to check for SSO support", e);
- showGenericError(e);
}
} else {
console.log("Unable to query for supported registration methods.", e);
@@ -365,6 +298,8 @@ export default class Registration extends React.Component {
if (!msisdnAvailable) {
msg = _t('This server does not support authentication with a phone number.');
}
+ } else if (response.errcode === "M_USER_IN_USE") {
+ msg = _t("That username already exists, please try another.");
}
this.setState({
busy: false,
@@ -453,21 +388,6 @@ export default class Registration extends React.Component {
this.setState({
busy: false,
doingUIAuth: false,
- phase: Phase.Registration,
- });
- };
-
- private onServerDetailsNextPhaseClick = async () => {
- this.setState({
- phase: Phase.Registration,
- });
- };
-
- private onEditServerDetailsClick = ev => {
- ev.preventDefault();
- ev.stopPropagation();
- this.setState({
- phase: Phase.ServerDetails,
});
};
@@ -516,77 +436,7 @@ export default class Registration extends React.Component {
}
};
- private renderServerComponent() {
- const ServerTypeSelector = sdk.getComponent("auth.ServerTypeSelector");
- const ServerConfig = sdk.getComponent("auth.ServerConfig");
- const ModularServerConfig = sdk.getComponent("auth.ModularServerConfig");
-
- if (SdkConfig.get()['disable_custom_urls']) {
- return null;
- }
-
- // Hide the server picker once the user is doing UI Auth unless encountered a fatal server error
- if (this.state.phase !== Phase.ServerDetails && this.state.doingUIAuth && !this.state.serverErrorIsFatal) {
- 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 there's a fatal server error, we need to show the full server
- // config as the user may need to change servers to resolve the error).
- if (PHASES_ENABLED && this.state.phase !== Phase.ServerDetails && !this.state.serverErrorIsFatal) {
- return
;
- }
-
private 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');
@@ -610,18 +460,48 @@ export default class Registration extends React.Component {
;
} else if (this.state.flows.length) {
- return ;
+ let ssoSection;
+ if (this.state.ssoFlow) {
+ let continueWithSection;
+ const providers = this.state.ssoFlow["org.matrix.msc2858.identity_providers"]
+ || this.state.ssoFlow["identity_providers"] || [];
+ // when there is only a single (or 0) providers we show a wide button with `Continue with X` text
+ if (providers.length > 1) {
+ // i18n: ssoButtons is a placeholder to help translators understand context
+ continueWithSection =
}
+
{ this.renderRegisterComponent() }
{ goBack }
{ signIn }
diff --git a/src/components/structures/auth/SoftLogout.js b/src/components/structures/auth/SoftLogout.js
index a539c8c9ee..fdc1aec96d 100644
--- a/src/components/structures/auth/SoftLogout.js
+++ b/src/components/structures/auth/SoftLogout.js
@@ -24,8 +24,8 @@ import Modal from '../../../Modal';
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import {sendLoginRequest} from "../../../Login";
import AuthPage from "../../views/auth/AuthPage";
-import SSOButton from "../../views/elements/SSOButton";
import {SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY} from "../../../BasePlatform";
+import SSOButtons from "../../views/elements/SSOButtons";
const LOGIN_VIEW = {
LOADING: 1,
@@ -101,10 +101,11 @@ export default class SoftLogout extends React.Component {
// Note: we don't use the existing Login class because it is heavily flow-based. We don't
// care about login flows here, unless it is the single flow we support.
const client = MatrixClientPeg.get();
- const loginViews = (await client.loginFlows()).flows.map(f => FLOWS_TO_VIEWS[f.type]);
+ const flows = (await client.loginFlows()).flows;
+ const loginViews = flows.map(f => FLOWS_TO_VIEWS[f.type]);
const chosenView = loginViews.filter(f => !!f)[0] || LOGIN_VIEW.UNSUPPORTED;
- this.setState({loginView: chosenView});
+ this.setState({ flows, loginView: chosenView });
}
onPasswordChange = (ev) => {
@@ -240,13 +241,18 @@ export default class SoftLogout extends React.Component {
introText = _t("Sign in and regain access to your account.");
} // else we already have a message and should use it (key backup warning)
+ const loginType = this.state.loginView === LOGIN_VIEW.CAS ? "cas" : "sso";
+ const flow = this.state.flows.find(flow => flow.type === "m.login." + loginType);
+
return (
{introText}
- flow.type === "m.login.password")}
/>
);
diff --git a/src/components/views/auth/CustomServerDialog.js b/src/components/views/auth/CustomServerDialog.js
deleted file mode 100644
index 138f8c4689..0000000000
--- a/src/components/views/auth/CustomServerDialog.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
-Copyright 2015, 2016 OpenMarket Ltd
-Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
-
-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.
-*/
-
-import React from 'react';
-import { _t } from '../../../languageHandler';
-import SdkConfig from '../../../SdkConfig';
-
-export default class CustomServerDialog extends React.Component {
- render() {
- const brand = SdkConfig.get().brand;
- return (
-
-
- { _t("Custom Server Options") }
-
-
-
{_t(
- "You can use the custom server options to sign into other " +
- "Matrix servers by specifying a different homeserver URL. This " +
- "allows you to use %(brand)s with an existing Matrix account on a " +
- "different homeserver.",
- { brand },
- )}
-
-
-
-
-
- );
- }
-}
diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.js b/src/components/views/auth/InteractiveAuthEntryComponents.js
index 6628ca7120..60e57afc98 100644
--- a/src/components/views/auth/InteractiveAuthEntryComponents.js
+++ b/src/components/views/auth/InteractiveAuthEntryComponents.js
@@ -18,7 +18,6 @@ limitations under the License.
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
-import url from 'url';
import classnames from 'classnames';
import * as sdk from '../../../index';
@@ -500,17 +499,11 @@ export class MsisdnAuthEntry extends React.Component {
});
try {
- const requiresIdServerParam =
- await this.props.matrixClient.doesServerRequireIdServerParam();
let result;
if (this._submitUrl) {
result = await this.props.matrixClient.submitMsisdnTokenOtherUrl(
this._submitUrl, this._sid, this.props.clientSecret, this.state.token,
);
- } else if (requiresIdServerParam) {
- result = await this.props.matrixClient.submitMsisdnToken(
- this._sid, this.props.clientSecret, this.state.token,
- );
} else {
throw new Error("The registration with MSISDN flow is misconfigured");
}
@@ -519,12 +512,6 @@ export class MsisdnAuthEntry extends React.Component {
sid: this._sid,
client_secret: this.props.clientSecret,
};
- if (requiresIdServerParam) {
- const idServerParsedUrl = url.parse(
- this.props.matrixClient.getIdentityServerUrl(),
- );
- creds.id_server = idServerParsedUrl.host;
- }
this.props.submitAuthDict({
type: MsisdnAuthEntry.LOGIN_TYPE,
// TODO: Remove `threepid_creds` once servers support proper UIA
diff --git a/src/components/views/auth/ModularServerConfig.js b/src/components/views/auth/ModularServerConfig.js
deleted file mode 100644
index 28fd16379d..0000000000
--- a/src/components/views/auth/ModularServerConfig.js
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
-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.
-*/
-
-import React from 'react';
-import * as sdk from '../../../index';
-import { _t } from '../../../languageHandler';
-import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
-import SdkConfig from "../../../SdkConfig";
-import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils";
-import * as ServerType from '../../views/auth/ServerTypeSelector';
-import ServerConfig from "./ServerConfig";
-
-const MODULAR_URL = 'https://element.io/matrix-services' +
- '?utm_source=element-web&utm_medium=web&utm_campaign=element-web-authentication';
-
-// TODO: TravisR - Can this extend ServerConfig for most things?
-
-/*
- * Configure the Modular server name.
- *
- * This is a variant of ServerConfig with only the HS field and different body
- * text that is specific to the Modular case.
- */
-export default class ModularServerConfig extends ServerConfig {
- static propTypes = ServerConfig.propTypes;
-
- async validateAndApplyServer(hsUrl, isUrl) {
- // Always try and use the defaults first
- const defaultConfig: ValidatedServerConfig = SdkConfig.get()["validated_server_config"];
- if (defaultConfig.hsUrl === hsUrl && defaultConfig.isUrl === isUrl) {
- this.setState({busy: false, errorText: ""});
- this.props.onServerConfigChange(defaultConfig);
- return defaultConfig;
- }
-
- this.setState({
- hsUrl,
- isUrl,
- busy: true,
- errorText: "",
- });
-
- try {
- const result = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl, isUrl);
- this.setState({busy: false, errorText: ""});
- this.props.onServerConfigChange(result);
- return result;
- } catch (e) {
- console.error(e);
- let message = _t("Unable to validate homeserver/identity server");
- if (e.translatedMessage) {
- message = e.translatedMessage;
- }
- this.setState({
- busy: false,
- errorText: message,
- });
-
- return null;
- }
- }
-
- async validateServer() {
- // TODO: Do we want to support .well-known lookups here?
- // If for some reason someone enters "matrix.org" for a URL, we could do a lookup to
- // find their homeserver without demanding they use "https://matrix.org"
- return this.validateAndApplyServer(this.state.hsUrl, ServerType.TYPES.PREMIUM.identityServerUrl);
- }
-
- render() {
- const Field = sdk.getComponent('elements.Field');
- const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
-
- const submitButton = this.props.submitText
- ? {this.props.submitText}
- : null;
-
- return (
-
-
{_t("Your server")}
- {_t(
- "Enter the location of your Element Matrix Services homeserver. It may use your own " +
- "domain name or be a subdomain of element.io.",
- {}, {
- a: sub =>
- {sub}
- ,
- },
- )}
-
-
- );
- }
-}
diff --git a/src/components/views/auth/PasswordLogin.tsx b/src/components/views/auth/PasswordLogin.tsx
index fced2e08d0..84e583c3a5 100644
--- a/src/components/views/auth/PasswordLogin.tsx
+++ b/src/components/views/auth/PasswordLogin.tsx
@@ -1,5 +1,5 @@
/*
-Copyright 2015, 2016, 2017, 2019 New Vector Ltd.
+Copyright 2015, 2016, 2017, 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -26,7 +26,6 @@ import withValidation from "../elements/Validation";
import * as Email from "../../../email";
import Field from "../elements/Field";
import CountryDropdown from "./CountryDropdown";
-import SignInToText from "./SignInToText";
// For validating phone numbers without country codes
const PHONE_NUMBER_REGEX = /^[0-9()\-\s]*$/;
@@ -47,7 +46,6 @@ interface IProps {
onUsernameBlur?(username: string): void;
onPhoneCountryChanged?(phoneCountry: string): void;
onPhoneNumberChanged?(phoneNumber: string): void;
- onEditServerDetailsClick?(): void;
onForgotPasswordClick?(): void;
}
@@ -70,7 +68,6 @@ enum LoginField {
*/
export default class PasswordLogin extends React.PureComponent {
static defaultProps = {
- onEditServerDetailsClick: null,
onUsernameChanged: function() {},
onUsernameBlur: function() {},
onPhoneCountryChanged: function() {},
@@ -296,7 +293,7 @@ export default class PasswordLogin extends React.PureComponent {
}, {
key: "number",
test: ({ value }) => !value || PHONE_NUMBER_REGEX.test(value),
- invalid: () => _t("Doesn't look like a valid phone number"),
+ invalid: () => _t("That phone number doesn't look quite right, please check and try again"),
},
],
});
@@ -357,6 +354,7 @@ export default class PasswordLogin extends React.PureComponent {
key="username_input"
type="text"
label={_t("Username")}
+ placeholder={_t("Username").toLocaleLowerCase()}
value={this.props.username}
onChange={this.onUsernameChanged}
onFocus={this.onUsernameFocus}
@@ -410,20 +408,14 @@ export default class PasswordLogin extends React.PureComponent {
let forgotPasswordJsx;
if (this.props.onForgotPasswordClick) {
- forgotPasswordJsx =
- {_t('Not sure of your password? Set a new one', {}, {
- a: sub => (
-
- {sub}
-
- ),
- })}
- ;
+ forgotPasswordJsx =
+ {_t("Forgot password?")}
+ ;
}
const pwFieldClass = classNames({
@@ -465,8 +457,6 @@ export default class PasswordLogin extends React.PureComponent {
return (
-
;
} else {
emailHelperText =
- {_t(
- "Set an email for account recovery. " +
- "Use email to optionally be discoverable by existing contacts.",
- )}
+ {
+ _t("Add an email to be able to reset your password.")
+ } {
+ _t("Use email to optionally be discoverable by existing contacts.")
+ }
diff --git a/src/components/views/auth/ServerConfig.js b/src/components/views/auth/ServerConfig.js
deleted file mode 100644
index e04bf9e25a..0000000000
--- a/src/components/views/auth/ServerConfig.js
+++ /dev/null
@@ -1,291 +0,0 @@
-/*
-Copyright 2015, 2016 OpenMarket Ltd
-Copyright 2019 New Vector Ltd
-Copyright 2019 The Matrix.org Foundation C.I.C.
-
-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.
-*/
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import Modal from '../../../Modal';
-import * as sdk from '../../../index';
-import { _t } from '../../../languageHandler';
-import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
-import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils";
-import SdkConfig from "../../../SdkConfig";
-import { createClient } from 'matrix-js-sdk/src/matrix';
-import classNames from 'classnames';
-import CountlyAnalytics from "../../../CountlyAnalytics";
-
-/*
- * A pure UI component which displays the HS and IS to use.
- */
-
-export default class ServerConfig extends React.PureComponent {
- static propTypes = {
- onServerConfigChange: PropTypes.func.isRequired,
-
- // The current configuration that the user is expecting to change.
- serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired,
-
- delayTimeMs: PropTypes.number, // time to wait before invoking onChanged
-
- // Called after the component calls onServerConfigChange
- onAfterSubmit: PropTypes.func,
-
- // Optional text for the submit button. If falsey, no button will be shown.
- submitText: PropTypes.string,
-
- // Optional class for the submit button. Only applies if the submit button
- // is to be rendered.
- submitClass: PropTypes.string,
-
- // Whether the flow this component is embedded in requires an identity
- // server when the homeserver says it will need one. Default false.
- showIdentityServerIfRequiredByHomeserver: PropTypes.bool,
- };
-
- static defaultProps = {
- onServerConfigChange: function() {},
- delayTimeMs: 0,
- };
-
- constructor(props) {
- super(props);
-
- this.state = {
- busy: false,
- errorText: "",
- hsUrl: props.serverConfig.hsUrl,
- isUrl: props.serverConfig.isUrl,
- showIdentityServer: false,
- };
-
- CountlyAnalytics.instance.track("onboarding_custom_server");
- }
-
- // TODO: [REACT-WARNING] Replace with appropriate lifecycle event
- UNSAFE_componentWillReceiveProps(newProps) { // eslint-disable-line camelcase
- if (newProps.serverConfig.hsUrl === this.state.hsUrl &&
- newProps.serverConfig.isUrl === this.state.isUrl) return;
-
- this.validateAndApplyServer(newProps.serverConfig.hsUrl, newProps.serverConfig.isUrl);
- }
-
- async validateServer() {
- // TODO: Do we want to support .well-known lookups here?
- // If for some reason someone enters "matrix.org" for a URL, we could do a lookup to
- // find their homeserver without demanding they use "https://matrix.org"
- const result = this.validateAndApplyServer(this.state.hsUrl, this.state.isUrl);
- if (!result) {
- return result;
- }
-
- // If the UI flow this component is embedded in requires an identity
- // server when the homeserver says it will need one, check first and
- // reveal this field if not already shown.
- // XXX: This a backward compatibility path for homeservers that require
- // an identity server to be passed during certain flows.
- // See also https://github.com/matrix-org/synapse/pull/5868.
- if (
- this.props.showIdentityServerIfRequiredByHomeserver &&
- !this.state.showIdentityServer &&
- await this.isIdentityServerRequiredByHomeserver()
- ) {
- this.setState({
- showIdentityServer: true,
- });
- return null;
- }
-
- return result;
- }
-
- async validateAndApplyServer(hsUrl, isUrl) {
- // Always try and use the defaults first
- const defaultConfig: ValidatedServerConfig = SdkConfig.get()["validated_server_config"];
- if (defaultConfig.hsUrl === hsUrl && defaultConfig.isUrl === isUrl) {
- this.setState({
- hsUrl: defaultConfig.hsUrl,
- isUrl: defaultConfig.isUrl,
- busy: false,
- errorText: "",
- });
- this.props.onServerConfigChange(defaultConfig);
- return defaultConfig;
- }
-
- this.setState({
- hsUrl,
- isUrl,
- busy: true,
- errorText: "",
- });
-
- try {
- const result = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl, isUrl);
- this.setState({busy: false, errorText: ""});
- this.props.onServerConfigChange(result);
- return result;
- } catch (e) {
- console.error(e);
-
- const stateForError = AutoDiscoveryUtils.authComponentStateForError(e);
- if (!stateForError.isFatalError) {
- this.setState({
- busy: false,
- });
- // carry on anyway
- const result = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl, isUrl, true);
- this.props.onServerConfigChange(result);
- return result;
- } else {
- let message = _t("Unable to validate homeserver/identity server");
- if (e.translatedMessage) {
- message = e.translatedMessage;
- }
- this.setState({
- busy: false,
- errorText: message,
- });
-
- return null;
- }
- }
- }
-
- async isIdentityServerRequiredByHomeserver() {
- // XXX: We shouldn't have to create a whole new MatrixClient just to
- // check if the homeserver requires an identity server... Should it be
- // extracted to a static utils function...?
- return createClient({
- baseUrl: this.state.hsUrl,
- }).doesServerRequireIdServerParam();
- }
-
- onHomeserverBlur = (ev) => {
- this._hsTimeoutId = this._waitThenInvoke(this._hsTimeoutId, () => {
- this.validateServer();
- });
- };
-
- onHomeserverChange = (ev) => {
- const hsUrl = ev.target.value;
- this.setState({ hsUrl });
- };
-
- onIdentityServerBlur = (ev) => {
- this._isTimeoutId = this._waitThenInvoke(this._isTimeoutId, () => {
- this.validateServer();
- });
- };
-
- onIdentityServerChange = (ev) => {
- const isUrl = ev.target.value;
- this.setState({ isUrl });
- };
-
- onSubmit = async (ev) => {
- ev.preventDefault();
- ev.stopPropagation();
- const result = await this.validateServer();
- if (!result) return; // Do not continue.
-
- if (this.props.onAfterSubmit) {
- this.props.onAfterSubmit();
- }
- };
-
- _waitThenInvoke(existingTimeoutId, fn) {
- if (existingTimeoutId) {
- clearTimeout(existingTimeoutId);
- }
- return setTimeout(fn.bind(this), this.props.delayTimeMs);
- }
-
- showHelpPopup = () => {
- const CustomServerDialog = sdk.getComponent('auth.CustomServerDialog');
- Modal.createTrackedDialog('Custom Server Dialog', '', CustomServerDialog);
- };
-
- _renderHomeserverSection() {
- const Field = sdk.getComponent('elements.Field');
- return
;
- }
-
- render() {
- const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
-
- const errorText = this.state.errorText
- ? {this.state.errorText}
- : null;
-
- const submitButton = this.props.submitText
- ? {this.props.submitText}
- : null;
-
- return (
-
- );
- }
-}
diff --git a/src/components/views/auth/ServerTypeSelector.js b/src/components/views/auth/ServerTypeSelector.js
deleted file mode 100644
index 71e7ac7f0e..0000000000
--- a/src/components/views/auth/ServerTypeSelector.js
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
-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.
-*/
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import { _t } from '../../../languageHandler';
-import * as sdk from '../../../index';
-import classnames from 'classnames';
-import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
-import {makeType} from "../../../utils/TypeUtils";
-
-const MODULAR_URL = 'https://element.io/matrix-services' +
- '?utm_source=element-web&utm_medium=web&utm_campaign=element-web-authentication';
-
-export const FREE = 'Free';
-export const PREMIUM = 'Premium';
-export const ADVANCED = 'Advanced';
-
-export const TYPES = {
- FREE: {
- id: FREE,
- label: () => _t('Free'),
- logo: () => ,
- description: () => _t('Join millions for free on the largest public server'),
- serverConfig: makeType(ValidatedServerConfig, {
- hsUrl: "https://matrix-client.matrix.org",
- hsName: "matrix.org",
- hsNameIsDifferent: false,
- isUrl: "https://vector.im",
- }),
- },
- PREMIUM: {
- id: PREMIUM,
- label: () => _t('Premium'),
- logo: () => ,
- description: () => _t('Premium hosting for organisations Learn more', {}, {
- a: sub =>
- {sub}
- ,
- }),
- identityServerUrl: "https://vector.im",
- },
- ADVANCED: {
- id: ADVANCED,
- label: () => _t('Advanced'),
- logo: () =>
-
- {_t('Other')}
-
,
- description: () => _t('Find other public servers or use a custom server'),
- },
-};
-
-export function getTypeFromServerConfig(config) {
- const {hsUrl} = config;
- if (!hsUrl) {
- return null;
- } else if (hsUrl === TYPES.FREE.serverConfig.hsUrl) {
- return FREE;
- } else if (new URL(hsUrl).hostname.endsWith('.modular.im')) {
- // This is an unlikely case to reach, as Modular defaults to hiding the
- // server type selector.
- return PREMIUM;
- } else {
- return ADVANCED;
- }
-}
-
-export default class ServerTypeSelector extends React.PureComponent {
- static propTypes = {
- // The default selected type.
- selected: PropTypes.string,
- // Handler called when the selected type changes.
- onChange: PropTypes.func.isRequired,
- };
-
- constructor(props) {
- super(props);
-
- const {
- selected,
- } = props;
-
- this.state = {
- selected,
- };
- }
-
- updateSelectedType(type) {
- if (this.state.selected === type) {
- return;
- }
- this.setState({
- selected: type,
- });
- if (this.props.onChange) {
- this.props.onChange(type);
- }
- }
-
- onClick = (e) => {
- e.stopPropagation();
- const type = e.currentTarget.dataset.id;
- this.updateSelectedType(type);
- };
-
- render() {
- const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
-
- const serverTypes = [];
- for (const type of Object.values(TYPES)) {
- const { id, label, logo, description } = type;
- const classes = classnames(
- "mx_ServerTypeSelector_type",
- `mx_ServerTypeSelector_type_${id}`,
- {
- "mx_ServerTypeSelector_type_selected": id === this.state.selected,
- },
- );
-
- serverTypes.push(
-
- {label()}
-
-
-
- {logo()}
-
-
- {description()}
-
-
-
);
- }
-
- return
- {serverTypes}
-
;
- }
-}
diff --git a/src/components/views/auth/SignInToText.js b/src/components/views/auth/SignInToText.js
deleted file mode 100644
index 7564096b7d..0000000000
--- a/src/components/views/auth/SignInToText.js
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
-Copyright 2019 The Matrix.org Foundation C.I.C.
-
-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.
-*/
-
-import React from 'react';
-import {_t} from "../../../languageHandler";
-import * as sdk from "../../../index";
-import PropTypes from "prop-types";
-import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
-
-export default class SignInToText extends React.PureComponent {
- static propTypes = {
- serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired,
- onEditServerDetailsClick: PropTypes.func,
- };
-
- render() {
- let signInToText = _t('Sign in to your Matrix account on %(serverName)s', {
- serverName: this.props.serverConfig.hsName,
- });
- if (this.props.serverConfig.hsNameIsDifferent) {
- const TextWithTooltip = sdk.getComponent("elements.TextWithTooltip");
-
- signInToText = _t('Sign in to your Matrix account on ', {}, {
- 'underlinedServerName': () => {
- return
- {this.props.serverConfig.hsName}
- ;
- },
- });
- }
-
- let editLink = null;
- if (this.props.onEditServerDetailsClick) {
- editLink =
- {_t('Change')}
- ;
- }
-
- return
{ this.props.description }
diff --git a/src/components/views/dialogs/ModalWidgetDialog.tsx b/src/components/views/dialogs/ModalWidgetDialog.tsx
index e722374555..484e8f0dcf 100644
--- a/src/components/views/dialogs/ModalWidgetDialog.tsx
+++ b/src/components/views/dialogs/ModalWidgetDialog.tsx
@@ -38,6 +38,7 @@ import {MatrixClientPeg} from "../../../MatrixClientPeg";
import RoomViewStore from "../../../stores/RoomViewStore";
import {OwnProfileStore} from "../../../stores/OwnProfileStore";
import { arrayFastClone } from "../../../utils/arrays";
+import { ElementWidget } from "../../../stores/widgets/StopGapWidget";
interface IProps {
widgetDefinition: IModalWidgetOpenRequestData;
@@ -64,7 +65,7 @@ export default class ModalWidgetDialog extends React.PureComponent
+ const isDisabled = this.state.disabledButtonIds.includes(def.id);
+
+ return
{ def.label }
;
});
diff --git a/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx b/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx
new file mode 100644
index 0000000000..b7cc81c113
--- /dev/null
+++ b/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx
@@ -0,0 +1,96 @@
+/*
+Copyright 2020 The Matrix.org Foundation C.I.C.
+
+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.
+*/
+
+import * as React from "react";
+
+import { _t } from '../../../languageHandler';
+import { IDialogProps } from "./IDialogProps";
+import {useRef, useState} from "react";
+import Field from "../elements/Field";
+import CountlyAnalytics from "../../../CountlyAnalytics";
+import withValidation from "../elements/Validation";
+import * as Email from "../../../email";
+import BaseDialog from "./BaseDialog";
+import DialogButtons from "../elements/DialogButtons";
+
+interface IProps extends IDialogProps {
+ onFinished(continued: boolean, email?: string): void;
+}
+
+const validation = withValidation({
+ rules: [
+ {
+ key: "email",
+ test: ({ value }) => !value || Email.looksValid(value),
+ invalid: () => _t("Doesn't look like a valid email address"),
+ },
+ ],
+});
+
+const RegistrationEmailPromptDialog: React.FC = ({onFinished}) => {
+ const [email, setEmail] = useState("");
+ const fieldRef = useRef();
+
+ const onSubmit = async () => {
+ if (email) {
+ const valid = await fieldRef.current.validate({ allowEmpty: false });
+
+ if (!valid) {
+ fieldRef.current.focus();
+ fieldRef.current.validate({ allowEmpty: false, focused: true });
+ return;
+ }
+ }
+
+ onFinished(true, email);
+ };
+
+ return onFinished(false)}
+ fixedWidth={false}
+ >
+
+
{_t("Just a heads up, if you don't add an email and forget your password, you could " +
+ "permanently lose access to your account.", {}, {
+ b: sub => {sub},
+ })}
+
+
+
+ ;
+};
+
+export default RegistrationEmailPromptDialog;
diff --git a/src/components/views/dialogs/RoomSettingsDialog.js b/src/components/views/dialogs/RoomSettingsDialog.js
index a43b284c42..9d9313f08f 100644
--- a/src/components/views/dialogs/RoomSettingsDialog.js
+++ b/src/components/views/dialogs/RoomSettingsDialog.js
@@ -53,9 +53,9 @@ export default class RoomSettingsDialog extends React.Component {
}
_onAction = (payload) => {
- // When room changes below us, close the room settings
+ // When view changes below us, close the room settings
// whilst the modal is open this can only be triggered when someone hits Leave Room
- if (payload.action === 'view_next_room') {
+ if (payload.action === 'view_home_page') {
this.props.onFinished();
}
};
diff --git a/src/components/views/dialogs/ServerPickerDialog.tsx b/src/components/views/dialogs/ServerPickerDialog.tsx
new file mode 100644
index 0000000000..65d53f0870
--- /dev/null
+++ b/src/components/views/dialogs/ServerPickerDialog.tsx
@@ -0,0 +1,235 @@
+/*
+Copyright 2020 The Matrix.org Foundation C.I.C.
+
+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.
+*/
+
+import React, {createRef} from "react";
+import {AutoDiscovery} from "matrix-js-sdk/src/autodiscovery";
+
+import AutoDiscoveryUtils, {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
+import BaseDialog from './BaseDialog';
+import { _t } from '../../../languageHandler';
+import AccessibleButton from "../elements/AccessibleButton";
+import SdkConfig from "../../../SdkConfig";
+import Field from "../elements/Field";
+import StyledRadioButton from "../elements/StyledRadioButton";
+import TextWithTooltip from "../elements/TextWithTooltip";
+import withValidation, {IFieldState} from "../elements/Validation";
+
+interface IProps {
+ title?: string;
+ serverConfig: ValidatedServerConfig;
+ onFinished(config?: ValidatedServerConfig): void;
+}
+
+interface IState {
+ defaultChosen: boolean;
+ otherHomeserver: string;
+}
+
+export default class ServerPickerDialog extends React.PureComponent {
+ private readonly defaultServer: ValidatedServerConfig;
+ private readonly fieldRef = createRef();
+ private validatedConf: ValidatedServerConfig;
+
+ constructor(props) {
+ super(props);
+
+ const config = SdkConfig.get();
+ this.defaultServer = config["validated_server_config"] as ValidatedServerConfig;
+ const { serverConfig } = this.props;
+
+ let otherHomeserver = "";
+ if (!serverConfig.isDefault) {
+ if (serverConfig.isNameResolvable && serverConfig.hsName) {
+ otherHomeserver = serverConfig.hsName;
+ } else {
+ otherHomeserver = serverConfig.hsUrl;
+ }
+ }
+
+ this.state = {
+ defaultChosen: serverConfig.isDefault,
+ otherHomeserver,
+ };
+ }
+
+ private onDefaultChosen = () => {
+ this.setState({ defaultChosen: true });
+ };
+
+ private onOtherChosen = () => {
+ this.setState({ defaultChosen: false });
+ };
+
+ private onHomeserverChange = (ev) => {
+ this.setState({ otherHomeserver: ev.target.value });
+ };
+
+ // TODO: Do we want to support .well-known lookups here?
+ // If for some reason someone enters "matrix.org" for a URL, we could do a lookup to
+ // find their homeserver without demanding they use "https://matrix.org"
+ private validate = withValidation({
+ deriveData: async ({ value }) => {
+ let hsUrl = value.trim(); // trim to account for random whitespace
+
+ // if the URL has no protocol, try validate it as a serverName via well-known
+ if (!hsUrl.includes("://")) {
+ try {
+ const discoveryResult = await AutoDiscovery.findClientConfig(hsUrl);
+ this.validatedConf = AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(hsUrl, discoveryResult);
+ return {}; // we have a validated config, we don't need to try the other paths
+ } catch (e) {
+ console.error(`Attempted ${hsUrl} as a server_name but it failed`, e);
+ }
+ }
+
+ // if we got to this stage then either the well-known failed or the URL had a protocol specified,
+ // so validate statically only. If the URL has no protocol, default to https.
+ if (!hsUrl.includes("://")) {
+ hsUrl = "https://" + hsUrl;
+ }
+
+ try {
+ this.validatedConf = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl);
+ return {};
+ } catch (e) {
+ console.error(e);
+
+ const stateForError = AutoDiscoveryUtils.authComponentStateForError(e);
+ if (stateForError.isFatalError) {
+ let error = _t("Unable to validate homeserver");
+ if (e.translatedMessage) {
+ error = e.translatedMessage;
+ }
+ return { error };
+ }
+
+ // try to carry on anyway
+ try {
+ this.validatedConf = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl, null, true);
+ return {};
+ } catch (e) {
+ console.error(e);
+ return { error: _t("Invalid URL") };
+ }
+ }
+ },
+ rules: [
+ {
+ key: "required",
+ test: ({ value, allowEmpty }) => allowEmpty || !!value,
+ invalid: () => _t("Specify a homeserver"),
+ }, {
+ key: "valid",
+ test: async function({ value }, { error }) {
+ if (!value) return true;
+ return !error;
+ },
+ invalid: function({ error }) {
+ return error;
+ },
+ },
+ ],
+ });
+
+ private onHomeserverValidate = (fieldState: IFieldState) => this.validate(fieldState);
+
+ private onSubmit = async (ev) => {
+ ev.preventDefault();
+
+ const valid = await this.fieldRef.current.validate({ allowEmpty: false });
+
+ if (!valid) {
+ this.fieldRef.current.focus();
+ this.fieldRef.current.validate({ allowEmpty: false, focused: true });
+ return;
+ }
+
+ this.props.onFinished(this.validatedConf);
+ };
+
+ public render() {
+ let text;
+ if (this.defaultServer.hsName === "matrix.org") {
+ text = _t("Matrix.org is the biggest public homeserver in the world, so it’s a good place for many.");
+ }
+
+ let defaultServerName = this.defaultServer.hsName;
+ if (this.defaultServer.hsNameIsDifferent) {
+ defaultServerName = (
+
+ {this.defaultServer.hsName}
+
+ );
+ }
+
+ return
+
+ ;
+ }
+}
diff --git a/src/components/views/dialogs/ShareDialog.tsx b/src/components/views/dialogs/ShareDialog.tsx
index 1569977d58..5264031cc6 100644
--- a/src/components/views/dialogs/ShareDialog.tsx
+++ b/src/components/views/dialogs/ShareDialog.tsx
@@ -146,7 +146,7 @@ export default class ShareDialog extends React.PureComponent {
const events = this.props.target.getLiveTimeline().getEvents();
matrixToUrl = this.state.permalinkCreator.forEvent(events[events.length - 1].getId());
} else {
- matrixToUrl = this.state.permalinkCreator.forRoom();
+ matrixToUrl = this.state.permalinkCreator.forShareableRoom();
}
} else if (this.props.target instanceof User || this.props.target instanceof RoomMember) {
matrixToUrl = makeUserPermalink(this.props.target.userId);
diff --git a/src/components/views/dialogs/WidgetOpenIDPermissionsDialog.js b/src/components/views/dialogs/WidgetOpenIDPermissionsDialog.js
index e793b85079..7ed3d04318 100644
--- a/src/components/views/dialogs/WidgetOpenIDPermissionsDialog.js
+++ b/src/components/views/dialogs/WidgetOpenIDPermissionsDialog.js
@@ -17,18 +17,17 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import {_t} from "../../../languageHandler";
-import SettingsStore from "../../../settings/SettingsStore";
import * as sdk from "../../../index";
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
-import WidgetUtils from "../../../utils/WidgetUtils";
-import {SettingLevel} from "../../../settings/SettingLevel";
+import {Widget} from "matrix-widget-api";
+import {OIDCState, WidgetPermissionStore} from "../../../stores/widgets/WidgetPermissionStore";
export default class WidgetOpenIDPermissionsDialog extends React.Component {
static propTypes = {
onFinished: PropTypes.func.isRequired,
- widgetUrl: PropTypes.string.isRequired,
- widgetId: PropTypes.string.isRequired,
- isUserWidget: PropTypes.bool.isRequired,
+ widget: PropTypes.objectOf(Widget).isRequired,
+ widgetKind: PropTypes.string.isRequired, // WidgetKind from widget-api
+ inRoomId: PropTypes.string,
};
constructor() {
@@ -51,16 +50,10 @@ export default class WidgetOpenIDPermissionsDialog extends React.Component {
if (this.state.rememberSelection) {
console.log(`Remembering ${this.props.widgetId} as allowed=${allowed} for OpenID`);
- const currentValues = SettingsStore.getValue("widgetOpenIDPermissions");
- if (!currentValues.allow) currentValues.allow = [];
- if (!currentValues.deny) currentValues.deny = [];
-
- const securityKey = WidgetUtils.getWidgetSecurityKey(
- this.props.widgetId,
- this.props.widgetUrl,
- this.props.isUserWidget);
- (allowed ? currentValues.allow : currentValues.deny).push(securityKey);
- SettingsStore.setValue("widgetOpenIDPermissions", null, SettingLevel.DEVICE, currentValues);
+ WidgetPermissionStore.instance.setOIDCState(
+ this.props.widget, this.props.widgetKind, this.props.inRoomId,
+ allowed ? OIDCState.Allowed : OIDCState.Denied,
+ );
}
this.props.onFinished(allowed);
@@ -84,7 +77,7 @@ export default class WidgetOpenIDPermissionsDialog extends React.Component {
"A widget located at %(widgetUrl)s would like to verify your identity. " +
"By allowing this, the widget will be able to verify your user ID, but not " +
"perform actions as you.", {
- widgetUrl: this.props.widgetUrl.split("?")[0],
+ widgetUrl: this.props.widget.templateUrl.split("?")[0],
},
)}
diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js
index b862a1e912..7e0ae965bb 100644
--- a/src/components/views/elements/AppTile.js
+++ b/src/components/views/elements/AppTile.js
@@ -375,17 +375,20 @@ export default class AppTile extends React.Component {
);
- // all widgets can theoretically be allowed to remain on screen, so we wrap
- // them all in a PersistedElement from the get-go. If we wait, the iframe will
- // be re-mounted later, which means the widget has to start over, which is bad.
+ if (!this.props.userWidget) {
+ // All room widgets can theoretically be allowed to remain on screen, so we
+ // wrap them all in a PersistedElement from the get-go. If we wait, the iframe
+ // will be re-mounted later, which means the widget has to start over, which is
+ // bad.
- // Also wrap the PersistedElement in a div to fix the height, otherwise
- // AppTile's border is in the wrong place
- appTileBody =
-
- {appTileBody}
-
-
;
+ // Also wrap the PersistedElement in a div to fix the height, otherwise
+ // AppTile's border is in the wrong place
+ appTileBody =
+
+ {appTileBody}
+
+
;
+ }
}
}
diff --git a/src/components/views/elements/EffectsOverlay.tsx b/src/components/views/elements/EffectsOverlay.tsx
new file mode 100644
index 0000000000..38be8da9a8
--- /dev/null
+++ b/src/components/views/elements/EffectsOverlay.tsx
@@ -0,0 +1,94 @@
+/*
+ Copyright 2020 Nurjin Jafar
+ Copyright 2020 Nordeck IT + Consulting GmbH.
+
+ 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.
+ */
+import React, { FunctionComponent, useEffect, useRef } from 'react';
+import dis from '../../../dispatcher/dispatcher';
+import ICanvasEffect from '../../../effects/ICanvasEffect';
+import {CHAT_EFFECTS} from '../../../effects'
+
+interface IProps {
+ roomWidth: number;
+}
+
+const EffectsOverlay: FunctionComponent = ({ roomWidth }) => {
+ const canvasRef = useRef(null);
+ const effectsRef = useRef
;
- const memberCount = useMemberCount(room);
+ const memberCount = useRoomMemberCount(room);
return
diff --git a/src/components/views/right_panel/WidgetCard.tsx b/src/components/views/right_panel/WidgetCard.tsx
index c1753e90e3..593bd0dde7 100644
--- a/src/components/views/right_panel/WidgetCard.tsx
+++ b/src/components/views/right_panel/WidgetCard.tsx
@@ -42,7 +42,7 @@ const WidgetCard: React.FC = ({ room, widgetId, onClose }) => {
const apps = useWidgets(room);
const app = apps.find(a => a.id === widgetId);
- const isPinned = app && WidgetStore.instance.isPinned(app.id);
+ const isPinned = app && WidgetStore.instance.isPinned(room.roomId, app.id);
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu();
diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js
index c358ef610d..11277daa57 100644
--- a/src/components/views/rooms/EventTile.js
+++ b/src/components/views/rooms/EventTile.js
@@ -745,13 +745,22 @@ export default class EventTile extends React.Component {
}
if (this.props.mxEvent.sender && avatarSize) {
+ let member;
+ // set member to receiver (target) if it is a 3PID invite
+ // so that the correct avatar is shown as the text is
+ // `$target accepted the invitation for $email`
+ if (this.props.mxEvent.getContent().third_party_invite) {
+ member = this.props.mxEvent.target;
+ } else {
+ member = this.props.mxEvent.sender;
+ }
avatar = (
-