2019-01-18 20:22:36 -07:00
|
|
|
/*
|
|
|
|
|
Copyright 2019 New Vector Ltd
|
2019-08-06 13:03:15 +01:00
|
|
|
Copyright 2019 The Matrix.org Foundation C.I.C.
|
2019-08-11 03:43:34 +01:00
|
|
|
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
2019-01-18 20:22:36 -07:00
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
*/
|
|
|
|
|
|
2023-02-24 15:28:40 +00:00
|
|
|
import React, { ReactNode } from "react";
|
2023-08-22 14:25:34 +12:00
|
|
|
import { SERVICE_TYPES, HTTPError } from "matrix-js-sdk/src/matrix";
|
2023-05-24 14:37:10 +12:00
|
|
|
import { IThreepid, ThreepidMedium } from "matrix-js-sdk/src/@types/threepids";
|
2021-10-22 17:23:32 -05:00
|
|
|
import { logger } from "matrix-js-sdk/src/logger";
|
|
|
|
|
|
2023-05-24 14:37:10 +12:00
|
|
|
import { Icon as WarningIcon } from "../../../../../../res/img/feather-customised/warning-triangle.svg";
|
2023-04-24 03:41:09 -05:00
|
|
|
import { UserFriendlyError, _t } from "../../../../../languageHandler";
|
2019-02-22 10:47:18 -07:00
|
|
|
import ProfileSettings from "../../ProfileSettings";
|
|
|
|
|
import * as languageHandler from "../../../../../languageHandler";
|
|
|
|
|
import SettingsStore from "../../../../../settings/SettingsStore";
|
|
|
|
|
import LanguageDropdown from "../../../elements/LanguageDropdown";
|
2020-12-01 20:36:25 +01:00
|
|
|
import SpellCheckSettings from "../../SpellCheckSettings";
|
2019-02-22 10:47:18 -07:00
|
|
|
import AccessibleButton from "../../../elements/AccessibleButton";
|
|
|
|
|
import DeactivateAccountDialog from "../../../dialogs/DeactivateAccountDialog";
|
2019-08-09 18:07:58 +01:00
|
|
|
import PlatformPeg from "../../../../../PlatformPeg";
|
|
|
|
|
import Modal from "../../../../../Modal";
|
2020-05-13 20:41:41 -06:00
|
|
|
import dis from "../../../../../dispatcher/dispatcher";
|
2023-02-13 11:39:16 +00:00
|
|
|
import { Service, ServicePolicyPair, startTermsFlow } from "../../../../../Terms";
|
2019-08-19 23:00:05 -06:00
|
|
|
import IdentityAuthClient from "../../../../../IdentityAuthClient";
|
2021-06-29 13:11:58 +01:00
|
|
|
import { abbreviateUrl } from "../../../../../utils/UrlUtils";
|
2019-09-11 11:33:58 +01:00
|
|
|
import { getThreepidsWithBindStatus } from "../../../../../boundThreepids";
|
2021-06-29 13:11:58 +01:00
|
|
|
import { SettingLevel } from "../../../../../settings/SettingLevel";
|
|
|
|
|
import { UIFeature } from "../../../../../settings/UIFeature";
|
2021-09-21 13:45:38 +02:00
|
|
|
import { ActionPayload } from "../../../../../dispatcher/payloads";
|
2023-04-24 03:41:09 -05:00
|
|
|
import ErrorDialog, { extractErrorMessageFromError } from "../../../dialogs/ErrorDialog";
|
2021-09-21 13:45:38 +02:00
|
|
|
import AccountPhoneNumbers from "../../account/PhoneNumbers";
|
|
|
|
|
import AccountEmailAddresses from "../../account/EmailAddresses";
|
|
|
|
|
import DiscoveryEmailAddresses from "../../discovery/EmailAddresses";
|
|
|
|
|
import DiscoveryPhoneNumbers from "../../discovery/PhoneNumbers";
|
|
|
|
|
import ChangePassword from "../../ChangePassword";
|
|
|
|
|
import InlineTermsAgreement from "../../../terms/InlineTermsAgreement";
|
|
|
|
|
import SetIdServer from "../../SetIdServer";
|
|
|
|
|
import SetIntegrationManager from "../../SetIntegrationManager";
|
2022-07-28 10:10:04 +02:00
|
|
|
import ToggleSwitch from "../../../elements/ToggleSwitch";
|
|
|
|
|
import { IS_MAC } from "../../../../../Keyboard";
|
2023-05-17 19:52:44 +12:00
|
|
|
import SettingsTab from "../SettingsTab";
|
|
|
|
|
import { SettingsSection } from "../../shared/SettingsSection";
|
2023-05-24 14:37:10 +12:00
|
|
|
import SettingsSubsection, { SettingsSubsectionText } from "../../shared/SettingsSubsection";
|
2023-05-24 11:34:56 +12:00
|
|
|
import { SettingsSubsectionHeading } from "../../shared/SettingsSubsectionHeading";
|
2023-05-24 14:37:10 +12:00
|
|
|
import Heading from "../../../typography/Heading";
|
2023-05-26 10:42:01 +12:00
|
|
|
import InlineSpinner from "../../../elements/InlineSpinner";
|
2023-06-01 14:43:24 +01:00
|
|
|
import MatrixClientContext from "../../../../../contexts/MatrixClientContext";
|
2023-07-07 09:02:13 +01:00
|
|
|
import { ThirdPartyIdentifier } from "../../../../../AddThreepid";
|
2023-08-22 14:25:34 +12:00
|
|
|
import { getDelegatedAuthAccountUrl } from "../../../../../utils/oidc/getDelegatedAuthAccountUrl";
|
2021-09-21 13:45:38 +02:00
|
|
|
|
|
|
|
|
interface IProps {
|
|
|
|
|
closeSettingsFn: () => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface IState {
|
|
|
|
|
language: string;
|
2023-03-16 11:07:29 +00:00
|
|
|
spellCheckEnabled?: boolean;
|
2021-09-21 13:45:38 +02:00
|
|
|
spellCheckLanguages: string[];
|
|
|
|
|
haveIdServer: boolean;
|
|
|
|
|
idServerHasUnsignedTerms: boolean;
|
2023-04-21 11:50:42 +01:00
|
|
|
requiredPolicyInfo:
|
|
|
|
|
| {
|
|
|
|
|
// This object is passed along to a component for handling
|
|
|
|
|
hasTerms: false;
|
|
|
|
|
policiesAndServices: null; // From the startTermsFlow callback
|
|
|
|
|
agreedUrls: null; // From the startTermsFlow callback
|
|
|
|
|
resolve: null; // Promise resolve function for startTermsFlow callback
|
|
|
|
|
}
|
|
|
|
|
| {
|
|
|
|
|
hasTerms: boolean;
|
|
|
|
|
policiesAndServices: ServicePolicyPair[];
|
|
|
|
|
agreedUrls: string[];
|
|
|
|
|
resolve: (values: string[]) => void;
|
|
|
|
|
};
|
2023-07-07 09:02:13 +01:00
|
|
|
emails: ThirdPartyIdentifier[];
|
|
|
|
|
msisdns: ThirdPartyIdentifier[];
|
2021-09-21 13:45:38 +02:00
|
|
|
loading3pids: boolean; // whether or not the emails and msisdns have been loaded
|
|
|
|
|
canChangePassword: boolean;
|
2023-02-24 15:28:40 +00:00
|
|
|
idServerName?: string;
|
2023-01-27 11:06:10 +00:00
|
|
|
externalAccountManagementUrl?: string;
|
2023-08-22 18:07:10 +12:00
|
|
|
canMake3pidChanges: boolean;
|
2021-09-21 13:45:38 +02:00
|
|
|
}
|
2019-01-18 20:22:36 -07:00
|
|
|
|
2021-09-21 13:45:38 +02:00
|
|
|
export default class GeneralUserSettingsTab extends React.Component<IProps, IState> {
|
2023-06-01 14:43:24 +01:00
|
|
|
public static contextType = MatrixClientContext;
|
|
|
|
|
public context!: React.ContextType<typeof MatrixClientContext>;
|
|
|
|
|
|
2021-09-21 13:45:38 +02:00
|
|
|
private readonly dispatcherRef: string;
|
2019-07-11 14:54:49 -06:00
|
|
|
|
2023-06-14 13:42:07 +01:00
|
|
|
public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
|
2021-09-21 13:45:38 +02:00
|
|
|
super(props);
|
2023-06-14 13:42:07 +01:00
|
|
|
this.context = context;
|
2019-01-23 14:43:45 -07:00
|
|
|
|
|
|
|
|
this.state = {
|
|
|
|
|
language: languageHandler.getCurrentLanguage(),
|
2022-07-28 10:10:04 +02:00
|
|
|
spellCheckEnabled: false,
|
2021-02-18 20:12:48 +01:00
|
|
|
spellCheckLanguages: [],
|
2023-06-14 13:42:07 +01:00
|
|
|
haveIdServer: Boolean(this.context.getIdentityServerUrl()),
|
2019-08-19 23:00:05 -06:00
|
|
|
idServerHasUnsignedTerms: false,
|
|
|
|
|
requiredPolicyInfo: {
|
|
|
|
|
// This object is passed along to a component for handling
|
|
|
|
|
hasTerms: false,
|
2021-09-21 13:45:38 +02:00
|
|
|
policiesAndServices: null, // From the startTermsFlow callback
|
|
|
|
|
agreedUrls: null, // From the startTermsFlow callback
|
|
|
|
|
resolve: null, // Promise resolve function for startTermsFlow callback
|
2019-08-19 23:00:05 -06:00
|
|
|
},
|
2019-09-11 11:33:58 +01:00
|
|
|
emails: [],
|
|
|
|
|
msisdns: [],
|
2020-05-06 15:17:55 -06:00
|
|
|
loading3pids: true, // whether or not the emails and msisdns have been loaded
|
2021-09-21 13:45:38 +02:00
|
|
|
canChangePassword: false,
|
2023-08-22 18:07:10 +12:00
|
|
|
canMake3pidChanges: false,
|
2019-01-23 14:43:45 -07:00
|
|
|
};
|
2019-08-13 18:40:27 +01:00
|
|
|
|
2021-09-21 13:45:38 +02:00
|
|
|
this.dispatcherRef = dis.register(this.onAction);
|
2019-08-19 23:00:05 -06:00
|
|
|
|
2022-11-18 09:22:43 +00:00
|
|
|
this.getCapabilities();
|
2021-09-21 13:45:38 +02:00
|
|
|
this.getThreepidState();
|
2019-08-19 15:17:14 +01:00
|
|
|
}
|
|
|
|
|
|
2021-09-21 13:45:38 +02:00
|
|
|
public async componentDidMount(): Promise<void> {
|
2022-07-28 10:10:04 +02:00
|
|
|
const plat = PlatformPeg.get();
|
|
|
|
|
const [spellCheckEnabled, spellCheckLanguages] = await Promise.all([
|
2023-02-24 15:28:40 +00:00
|
|
|
plat?.getSpellCheckEnabled(),
|
|
|
|
|
plat?.getSpellCheckLanguages(),
|
2022-07-28 10:10:04 +02:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
if (spellCheckLanguages) {
|
2021-02-18 20:12:48 +01:00
|
|
|
this.setState({
|
2022-07-28 10:10:04 +02:00
|
|
|
spellCheckEnabled,
|
|
|
|
|
spellCheckLanguages,
|
2021-02-18 20:12:48 +01:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-21 13:45:38 +02:00
|
|
|
public componentWillUnmount(): void {
|
2019-08-13 18:40:27 +01:00
|
|
|
dis.unregister(this.dispatcherRef);
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-21 13:45:38 +02:00
|
|
|
private onAction = (payload: ActionPayload): void => {
|
2019-08-13 18:40:27 +01:00
|
|
|
if (payload.action === "id_server_changed") {
|
2023-06-14 13:42:07 +01:00
|
|
|
this.setState({ haveIdServer: Boolean(this.context.getIdentityServerUrl()) });
|
2021-09-21 13:45:38 +02:00
|
|
|
this.getThreepidState();
|
2019-08-13 18:40:27 +01:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2023-07-07 09:02:13 +01:00
|
|
|
private onEmailsChange = (emails: ThirdPartyIdentifier[]): void => {
|
2019-09-11 11:33:58 +01:00
|
|
|
this.setState({ emails });
|
2019-11-28 13:40:28 -07:00
|
|
|
};
|
2019-09-11 11:33:58 +01:00
|
|
|
|
2023-07-07 09:02:13 +01:00
|
|
|
private onMsisdnsChange = (msisdns: ThirdPartyIdentifier[]): void => {
|
2019-09-11 11:33:58 +01:00
|
|
|
this.setState({ msisdns });
|
2019-11-28 13:40:28 -07:00
|
|
|
};
|
2019-09-11 11:33:58 +01:00
|
|
|
|
2022-11-18 09:22:43 +00:00
|
|
|
private async getCapabilities(): Promise<void> {
|
2023-06-14 13:42:07 +01:00
|
|
|
const cli = this.context;
|
2022-11-18 09:22:43 +00:00
|
|
|
|
|
|
|
|
const capabilities = await cli.getCapabilities(); // this is cached
|
|
|
|
|
const changePasswordCap = capabilities["m.change_password"];
|
|
|
|
|
|
|
|
|
|
// You can change your password so long as the capability isn't explicitly disabled. The implicit
|
|
|
|
|
// behaviour is you can change your password when the capability is missing or has not-false as
|
|
|
|
|
// the enabled flag value.
|
|
|
|
|
const canChangePassword = !changePasswordCap || changePasswordCap["enabled"] !== false;
|
|
|
|
|
|
2023-08-22 14:25:34 +12:00
|
|
|
const externalAccountManagementUrl = getDelegatedAuthAccountUrl(cli.getClientWellKnown());
|
2023-08-22 18:07:10 +12:00
|
|
|
// https://spec.matrix.org/v1.7/client-server-api/#m3pid_changes-capability
|
|
|
|
|
// We support as far back as v1.1 which doesn't have m.3pid_changes
|
|
|
|
|
// so the behaviour for when it is missing has to be assume true
|
|
|
|
|
const canMake3pidChanges = !capabilities["m.3pid_changes"] || capabilities["m.3pid_changes"].enabled === true;
|
2023-01-27 11:06:10 +00:00
|
|
|
|
2023-08-22 18:07:10 +12:00
|
|
|
this.setState({ canChangePassword, externalAccountManagementUrl, canMake3pidChanges });
|
2022-11-18 09:22:43 +00:00
|
|
|
}
|
|
|
|
|
|
2021-09-21 13:45:38 +02:00
|
|
|
private async getThreepidState(): Promise<void> {
|
2023-06-14 13:42:07 +01:00
|
|
|
const cli = this.context;
|
2019-09-11 16:16:07 +01:00
|
|
|
|
|
|
|
|
// Check to see if terms need accepting
|
2021-09-21 13:45:38 +02:00
|
|
|
this.checkTerms();
|
2019-09-11 16:16:07 +01:00
|
|
|
|
|
|
|
|
// Need to get 3PIDs generally for Account section and possibly also for
|
|
|
|
|
// Discovery (assuming we have an IS and terms are agreed).
|
2022-11-18 09:22:43 +00:00
|
|
|
let threepids: IThreepid[] = [];
|
2019-10-11 15:52:15 +01:00
|
|
|
try {
|
|
|
|
|
threepids = await getThreepidsWithBindStatus(cli);
|
|
|
|
|
} catch (e) {
|
2023-06-14 13:42:07 +01:00
|
|
|
const idServerUrl = this.context.getIdentityServerUrl();
|
2021-10-15 16:31:29 +02:00
|
|
|
logger.warn(
|
2019-10-11 15:52:15 +01:00
|
|
|
`Unable to reach identity server at ${idServerUrl} to check ` + `for 3PIDs bindings in Settings`,
|
|
|
|
|
);
|
2021-10-15 16:31:29 +02:00
|
|
|
logger.warn(e);
|
2019-10-11 15:52:15 +01:00
|
|
|
}
|
2020-05-06 15:17:55 -06:00
|
|
|
this.setState({
|
2023-05-24 14:37:10 +12:00
|
|
|
emails: threepids.filter((a) => a.medium === ThreepidMedium.Email),
|
|
|
|
|
msisdns: threepids.filter((a) => a.medium === ThreepidMedium.Phone),
|
2020-05-06 15:17:55 -06:00
|
|
|
loading3pids: false,
|
|
|
|
|
});
|
2019-09-11 16:16:07 +01:00
|
|
|
}
|
|
|
|
|
|
2021-09-21 13:45:38 +02:00
|
|
|
private async checkTerms(): Promise<void> {
|
2023-04-21 11:50:42 +01:00
|
|
|
// By starting the terms flow we get the logic for checking which terms the user has signed
|
|
|
|
|
// for free. So we might as well use that for our own purposes.
|
2023-06-14 13:42:07 +01:00
|
|
|
const idServerUrl = this.context.getIdentityServerUrl();
|
2023-04-21 11:50:42 +01:00
|
|
|
if (!this.state.haveIdServer || !idServerUrl) {
|
2021-06-29 13:11:58 +01:00
|
|
|
this.setState({ idServerHasUnsignedTerms: false });
|
2019-08-19 23:00:05 -06:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const authClient = new IdentityAuthClient();
|
2019-10-11 15:52:15 +01:00
|
|
|
try {
|
2020-01-17 16:26:47 +00:00
|
|
|
const idAccessToken = await authClient.getAccessToken({ check: false });
|
2019-10-11 15:52:15 +01:00
|
|
|
await startTermsFlow(
|
2023-06-01 14:43:24 +01:00
|
|
|
this.context,
|
2023-04-21 11:50:42 +01:00
|
|
|
[new Service(SERVICE_TYPES.IS, idServerUrl, idAccessToken!)],
|
2019-10-11 15:52:15 +01:00
|
|
|
(policiesAndServices, agreedUrls, extraClassNames) => {
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
this.setState({
|
|
|
|
|
idServerName: abbreviateUrl(idServerUrl),
|
|
|
|
|
requiredPolicyInfo: {
|
|
|
|
|
hasTerms: true,
|
|
|
|
|
policiesAndServices,
|
|
|
|
|
agreedUrls,
|
|
|
|
|
resolve,
|
|
|
|
|
},
|
2022-12-12 12:24:14 +01:00
|
|
|
});
|
2019-10-11 15:52:15 +01:00
|
|
|
});
|
|
|
|
|
},
|
2019-08-19 23:00:05 -06:00
|
|
|
);
|
|
|
|
|
// User accepted all terms
|
|
|
|
|
this.setState({
|
|
|
|
|
requiredPolicyInfo: {
|
2022-05-17 00:20:16 -06:00
|
|
|
...this.state.requiredPolicyInfo, // set first so we can override
|
2019-08-19 23:00:05 -06:00
|
|
|
hasTerms: false,
|
|
|
|
|
},
|
|
|
|
|
});
|
2019-10-11 15:52:15 +01:00
|
|
|
} catch (e) {
|
|
|
|
|
logger.warn(`Unable to reach identity server at ${idServerUrl} to check ` + `for terms in Settings`);
|
2021-10-15 16:31:29 +02:00
|
|
|
logger.warn(e);
|
2019-10-11 15:52:15 +01:00
|
|
|
}
|
2019-08-19 23:00:05 -06:00
|
|
|
}
|
|
|
|
|
|
2021-09-21 13:45:38 +02:00
|
|
|
private onLanguageChange = (newLanguage: string): void => {
|
2019-01-23 14:43:45 -07:00
|
|
|
if (this.state.language === newLanguage) return;
|
|
|
|
|
|
|
|
|
|
SettingsStore.setValue("language", null, SettingLevel.DEVICE, newLanguage);
|
2021-06-29 13:11:58 +01:00
|
|
|
this.setState({ language: newLanguage });
|
2021-04-23 17:11:45 +01:00
|
|
|
const platform = PlatformPeg.get();
|
|
|
|
|
if (platform) {
|
2021-09-21 13:45:38 +02:00
|
|
|
platform.setLanguage([newLanguage]);
|
2021-04-26 13:55:14 +01:00
|
|
|
platform.reload();
|
2021-04-23 17:11:45 +01:00
|
|
|
}
|
2019-01-23 14:43:45 -07:00
|
|
|
};
|
|
|
|
|
|
2021-09-21 13:45:38 +02:00
|
|
|
private onSpellCheckLanguagesChange = (languages: string[]): void => {
|
2021-06-29 13:11:58 +01:00
|
|
|
this.setState({ spellCheckLanguages: languages });
|
2022-07-28 10:10:04 +02:00
|
|
|
PlatformPeg.get()?.setSpellCheckLanguages(languages);
|
|
|
|
|
};
|
2020-12-01 20:36:25 +01:00
|
|
|
|
2022-07-28 10:10:04 +02:00
|
|
|
private onSpellCheckEnabledChange = (spellCheckEnabled: boolean): void => {
|
|
|
|
|
this.setState({ spellCheckEnabled });
|
|
|
|
|
PlatformPeg.get()?.setSpellCheckEnabled(spellCheckEnabled);
|
2020-11-26 13:51:03 +01:00
|
|
|
};
|
|
|
|
|
|
2023-04-24 03:41:09 -05:00
|
|
|
private onPasswordChangeError = (err: Error): void => {
|
|
|
|
|
logger.error("Failed to change password: " + err);
|
|
|
|
|
|
|
|
|
|
let underlyingError = err;
|
|
|
|
|
if (err instanceof UserFriendlyError && err.cause instanceof Error) {
|
|
|
|
|
underlyingError = err.cause;
|
2019-01-22 13:09:40 -07:00
|
|
|
}
|
2023-04-24 03:41:09 -05:00
|
|
|
|
|
|
|
|
const errorMessage = extractErrorMessageFromError(
|
|
|
|
|
err,
|
|
|
|
|
_t("Unknown password change error (%(stringifiedError)s)", {
|
|
|
|
|
stringifiedError: String(err),
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let errorMessageToDisplay = errorMessage;
|
|
|
|
|
if (underlyingError instanceof HTTPError && underlyingError.httpStatus === 403) {
|
|
|
|
|
errorMessageToDisplay = _t("Failed to change password. Is your password correct?");
|
|
|
|
|
} else if (underlyingError instanceof HTTPError) {
|
|
|
|
|
errorMessageToDisplay = _t("%(errorMessage)s (HTTP status %(httpStatus)s)", {
|
|
|
|
|
errorMessage,
|
|
|
|
|
httpStatus: underlyingError.httpStatus,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Figure out a design that doesn't involve replacing the current dialog
|
2022-06-14 17:51:51 +01:00
|
|
|
Modal.createDialog(ErrorDialog, {
|
2023-04-24 03:41:09 -05:00
|
|
|
title: _t("Error changing password"),
|
|
|
|
|
description: errorMessageToDisplay,
|
2019-01-22 13:09:40 -07:00
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2023-08-14 02:25:13 -06:00
|
|
|
private onPasswordChanged = (): void => {
|
|
|
|
|
const description = _t("Your password was successfully changed.");
|
2019-01-22 13:09:40 -07:00
|
|
|
// TODO: Figure out a design that doesn't involve replacing the current dialog
|
2022-06-14 17:51:51 +01:00
|
|
|
Modal.createDialog(ErrorDialog, {
|
2019-01-22 13:09:40 -07:00
|
|
|
title: _t("Success"),
|
2022-04-22 18:15:38 +01:00
|
|
|
description,
|
2019-01-22 13:09:40 -07:00
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2021-09-21 13:45:38 +02:00
|
|
|
private onDeactivateClicked = (): void => {
|
2022-06-14 17:51:51 +01:00
|
|
|
Modal.createDialog(DeactivateAccountDialog, {
|
2019-07-11 14:54:49 -06:00
|
|
|
onFinished: (success) => {
|
|
|
|
|
if (success) this.props.closeSettingsFn();
|
|
|
|
|
},
|
|
|
|
|
});
|
2019-01-23 15:56:58 -07:00
|
|
|
};
|
|
|
|
|
|
2021-09-21 13:45:38 +02:00
|
|
|
private renderAccountSection(): JSX.Element {
|
2023-02-24 15:28:40 +00:00
|
|
|
let threepidSection: ReactNode = null;
|
2019-08-07 11:51:42 +01:00
|
|
|
|
2023-08-14 02:25:13 -06:00
|
|
|
if (SettingsStore.getValue(UIFeature.ThirdPartyID)) {
|
2020-05-14 09:43:34 -06:00
|
|
|
const emails = this.state.loading3pids ? (
|
2023-05-26 10:42:01 +12:00
|
|
|
<InlineSpinner />
|
2021-09-21 13:45:38 +02:00
|
|
|
) : (
|
2023-08-22 18:07:10 +12:00
|
|
|
<AccountEmailAddresses
|
|
|
|
|
emails={this.state.emails}
|
|
|
|
|
onEmailsChange={this.onEmailsChange}
|
|
|
|
|
disabled={!this.state.canMake3pidChanges}
|
|
|
|
|
/>
|
2020-05-06 15:17:55 -06:00
|
|
|
);
|
|
|
|
|
const msisdns = this.state.loading3pids ? (
|
2023-05-26 10:42:01 +12:00
|
|
|
<InlineSpinner />
|
2021-09-21 13:45:38 +02:00
|
|
|
) : (
|
2023-08-22 18:07:10 +12:00
|
|
|
<AccountPhoneNumbers
|
|
|
|
|
msisdns={this.state.msisdns}
|
|
|
|
|
onMsisdnsChange={this.onMsisdnsChange}
|
|
|
|
|
disabled={!this.state.canMake3pidChanges}
|
|
|
|
|
/>
|
2020-05-06 15:17:55 -06:00
|
|
|
);
|
|
|
|
|
threepidSection = (
|
2023-05-26 10:42:01 +12:00
|
|
|
<>
|
|
|
|
|
<SettingsSubsection
|
|
|
|
|
heading={_t("Email addresses")}
|
|
|
|
|
stretchContent
|
|
|
|
|
data-testid="mx_AccountEmailAddresses"
|
|
|
|
|
>
|
|
|
|
|
{emails}
|
|
|
|
|
</SettingsSubsection>
|
2020-05-06 15:17:55 -06:00
|
|
|
|
2023-05-26 10:42:01 +12:00
|
|
|
<SettingsSubsection
|
|
|
|
|
heading={_t("Phone numbers")}
|
|
|
|
|
stretchContent
|
|
|
|
|
data-testid="mx_AccountPhoneNumbers"
|
|
|
|
|
>
|
|
|
|
|
{msisdns}
|
|
|
|
|
</SettingsSubsection>
|
|
|
|
|
</>
|
2019-08-19 15:17:14 +01:00
|
|
|
);
|
|
|
|
|
}
|
2019-08-07 11:51:42 +01:00
|
|
|
|
2023-05-26 10:42:01 +12:00
|
|
|
let passwordChangeSection: ReactNode = null;
|
|
|
|
|
if (this.state.canChangePassword) {
|
|
|
|
|
passwordChangeSection = (
|
|
|
|
|
<>
|
|
|
|
|
<SettingsSubsectionText>{_t("Set a new account password…")}</SettingsSubsectionText>
|
|
|
|
|
<ChangePassword
|
|
|
|
|
className="mx_GeneralUserSettingsTab_section--account_changePassword"
|
|
|
|
|
rowClassName=""
|
|
|
|
|
buttonKind="primary"
|
|
|
|
|
onError={this.onPasswordChangeError}
|
|
|
|
|
onFinished={this.onPasswordChanged}
|
|
|
|
|
/>
|
|
|
|
|
</>
|
|
|
|
|
);
|
2020-01-23 16:29:55 -07:00
|
|
|
}
|
|
|
|
|
|
2023-01-27 11:06:10 +00:00
|
|
|
let externalAccountManagement: JSX.Element | undefined;
|
|
|
|
|
if (this.state.externalAccountManagementUrl) {
|
|
|
|
|
const { hostname } = new URL(this.state.externalAccountManagementUrl);
|
|
|
|
|
|
|
|
|
|
externalAccountManagement = (
|
|
|
|
|
<>
|
2023-05-26 10:42:01 +12:00
|
|
|
<SettingsSubsectionText data-testid="external-account-management-outer">
|
2023-01-27 11:06:10 +00:00
|
|
|
{_t(
|
|
|
|
|
"Your account details are managed separately at <code>%(hostname)s</code>.",
|
|
|
|
|
{ hostname },
|
|
|
|
|
{ code: (sub) => <code>{sub}</code> },
|
|
|
|
|
)}
|
2023-05-26 10:42:01 +12:00
|
|
|
</SettingsSubsectionText>
|
2023-01-27 11:06:10 +00:00
|
|
|
<AccessibleButton
|
|
|
|
|
onClick={null}
|
|
|
|
|
element="a"
|
|
|
|
|
kind="primary"
|
|
|
|
|
target="_blank"
|
|
|
|
|
rel="noreferrer noopener"
|
|
|
|
|
href={this.state.externalAccountManagementUrl}
|
|
|
|
|
data-testid="external-account-management-link"
|
|
|
|
|
>
|
|
|
|
|
{_t("Manage account")}
|
|
|
|
|
</AccessibleButton>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|
2019-01-22 10:36:47 -07:00
|
|
|
return (
|
2023-05-26 10:42:01 +12:00
|
|
|
<>
|
|
|
|
|
<SettingsSubsection heading={_t("Account")} stretchContent data-testid="accountSection">
|
|
|
|
|
{externalAccountManagement}
|
|
|
|
|
{passwordChangeSection}
|
|
|
|
|
</SettingsSubsection>
|
2021-07-19 22:43:11 +01:00
|
|
|
{threepidSection}
|
2023-05-26 10:42:01 +12:00
|
|
|
</>
|
2019-01-22 10:36:47 -07:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-21 13:45:38 +02:00
|
|
|
private renderLanguageSection(): JSX.Element {
|
2019-01-23 14:43:45 -07:00
|
|
|
// TODO: Convert to new-styled Field
|
2019-01-22 10:36:47 -07:00
|
|
|
return (
|
2023-05-24 11:34:56 +12:00
|
|
|
<SettingsSubsection heading={_t("Language and region")} stretchContent>
|
2021-04-27 17:23:27 +02:00
|
|
|
<LanguageDropdown
|
2023-05-12 10:33:01 +00:00
|
|
|
className="mx_GeneralUserSettingsTab_section_languageInput"
|
2021-09-21 13:45:38 +02:00
|
|
|
onOptionChange={this.onLanguageChange}
|
2021-04-27 17:23:27 +02:00
|
|
|
value={this.state.language}
|
|
|
|
|
/>
|
2023-05-24 11:34:56 +12:00
|
|
|
</SettingsSubsection>
|
2019-01-22 10:36:47 -07:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-21 13:45:38 +02:00
|
|
|
private renderSpellCheckSection(): JSX.Element {
|
2023-05-24 11:34:56 +12:00
|
|
|
const heading = (
|
|
|
|
|
<SettingsSubsectionHeading heading={_t("Spell check")}>
|
|
|
|
|
<ToggleSwitch checked={!!this.state.spellCheckEnabled} onChange={this.onSpellCheckEnabledChange} />
|
|
|
|
|
</SettingsSubsectionHeading>
|
|
|
|
|
);
|
2020-11-26 13:51:03 +01:00
|
|
|
return (
|
2023-05-24 11:34:56 +12:00
|
|
|
<SettingsSubsection heading={heading} stretchContent>
|
2022-07-28 10:10:04 +02:00
|
|
|
{this.state.spellCheckEnabled && !IS_MAC && (
|
|
|
|
|
<SpellCheckSettings
|
2021-04-27 17:23:27 +02:00
|
|
|
languages={this.state.spellCheckLanguages}
|
2021-09-21 13:45:38 +02:00
|
|
|
onLanguagesChange={this.onSpellCheckLanguagesChange}
|
2022-07-28 10:10:04 +02:00
|
|
|
/>
|
|
|
|
|
)}
|
2023-05-24 11:34:56 +12:00
|
|
|
</SettingsSubsection>
|
2020-11-26 13:51:03 +01:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-21 13:45:38 +02:00
|
|
|
private renderDiscoverySection(): JSX.Element {
|
2019-08-19 23:00:05 -06:00
|
|
|
if (this.state.requiredPolicyInfo.hasTerms) {
|
|
|
|
|
const intro = (
|
2023-05-24 14:37:10 +12:00
|
|
|
<SettingsSubsectionText>
|
2021-07-19 22:43:11 +01:00
|
|
|
{_t(
|
2019-08-19 23:00:05 -06:00
|
|
|
"Agree to the identity server (%(serverName)s) Terms of Service to " +
|
|
|
|
|
"allow yourself to be discoverable by email address or phone number.",
|
2021-06-29 13:11:58 +01:00
|
|
|
{ serverName: this.state.idServerName },
|
2021-07-19 22:43:11 +01:00
|
|
|
)}
|
2023-05-24 14:37:10 +12:00
|
|
|
</SettingsSubsectionText>
|
2019-08-19 23:00:05 -06:00
|
|
|
);
|
2019-08-26 11:25:50 -06:00
|
|
|
return (
|
2023-05-24 14:37:10 +12:00
|
|
|
<>
|
2019-08-26 11:25:50 -06:00
|
|
|
<InlineTermsAgreement
|
|
|
|
|
policiesAndServicePairs={this.state.requiredPolicyInfo.policiesAndServices}
|
|
|
|
|
agreedUrls={this.state.requiredPolicyInfo.agreedUrls}
|
|
|
|
|
onFinished={this.state.requiredPolicyInfo.resolve}
|
|
|
|
|
introElement={intro}
|
|
|
|
|
/>
|
2021-07-13 15:26:38 +01:00
|
|
|
{/* has its own heading as it includes the current identity server */}
|
2019-08-26 11:25:50 -06:00
|
|
|
<SetIdServer missingTerms={true} />
|
2023-05-24 14:37:10 +12:00
|
|
|
</>
|
2019-08-26 11:25:50 -06:00
|
|
|
);
|
2019-08-19 23:00:05 -06:00
|
|
|
}
|
|
|
|
|
|
2019-08-21 14:41:25 -06:00
|
|
|
const threepidSection = this.state.haveIdServer ? (
|
2023-05-12 10:33:01 +00:00
|
|
|
<>
|
2023-08-22 18:07:10 +12:00
|
|
|
<DiscoveryEmailAddresses
|
|
|
|
|
emails={this.state.emails}
|
|
|
|
|
isLoading={this.state.loading3pids}
|
|
|
|
|
disabled={!this.state.canMake3pidChanges}
|
|
|
|
|
/>
|
|
|
|
|
<DiscoveryPhoneNumbers
|
|
|
|
|
msisdns={this.state.msisdns}
|
|
|
|
|
isLoading={this.state.loading3pids}
|
|
|
|
|
disabled={!this.state.canMake3pidChanges}
|
|
|
|
|
/>
|
2023-05-12 10:33:01 +00:00
|
|
|
</>
|
2019-08-19 14:03:38 +01:00
|
|
|
) : null;
|
|
|
|
|
|
2019-08-08 11:35:35 +01:00
|
|
|
return (
|
2023-05-24 14:37:10 +12:00
|
|
|
<>
|
2021-07-19 22:43:11 +01:00
|
|
|
{threepidSection}
|
2021-07-13 15:26:38 +01:00
|
|
|
{/* has its own heading as it includes the current identity server */}
|
2021-09-21 13:45:38 +02:00
|
|
|
<SetIdServer missingTerms={false} />
|
2023-05-24 14:37:10 +12:00
|
|
|
</>
|
2019-08-08 11:35:35 +01:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-21 13:45:38 +02:00
|
|
|
private renderManagementSection(): JSX.Element {
|
2019-01-23 15:56:58 -07:00
|
|
|
// TODO: Improve warning text for account deactivation
|
2019-01-22 10:36:47 -07:00
|
|
|
return (
|
2023-05-17 19:52:44 +12:00
|
|
|
<SettingsSection heading={_t("Deactivate account")}>
|
|
|
|
|
<SettingsSubsection
|
|
|
|
|
heading={_t("Account management")}
|
|
|
|
|
data-testid="account-management-section"
|
|
|
|
|
description={_t("Deactivating your account is a permanent action — be careful!")}
|
|
|
|
|
>
|
|
|
|
|
<AccessibleButton onClick={this.onDeactivateClicked} kind="danger">
|
|
|
|
|
{_t("Deactivate Account")}
|
|
|
|
|
</AccessibleButton>
|
|
|
|
|
</SettingsSubsection>
|
|
|
|
|
</SettingsSection>
|
2019-01-22 10:36:47 -07:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-24 15:28:40 +00:00
|
|
|
private renderIntegrationManagerSection(): ReactNode {
|
2020-09-16 11:38:50 +01:00
|
|
|
if (!SettingsStore.getValue(UIFeature.Widgets)) return null;
|
|
|
|
|
|
2023-05-17 19:52:44 +12:00
|
|
|
return <SetIntegrationManager />;
|
2019-08-12 15:35:39 -06:00
|
|
|
}
|
|
|
|
|
|
2023-02-13 17:01:43 +00:00
|
|
|
public render(): React.ReactNode {
|
2020-12-02 20:14:58 +01:00
|
|
|
const plaf = PlatformPeg.get();
|
2023-02-24 15:28:40 +00:00
|
|
|
const supportsMultiLanguageSpellCheck = plaf?.supportsSpellCheckSettings();
|
2020-12-02 20:14:58 +01:00
|
|
|
|
2023-02-24 15:28:40 +00:00
|
|
|
let accountManagementSection: JSX.Element | undefined;
|
2023-08-22 23:51:16 +12:00
|
|
|
const isAccountManagedExternally = !!this.state.externalAccountManagementUrl;
|
|
|
|
|
if (SettingsStore.getValue(UIFeature.Deactivate) && !isAccountManagedExternally) {
|
2023-05-17 19:52:44 +12:00
|
|
|
accountManagementSection = this.renderManagementSection();
|
2020-09-17 11:55:10 +01:00
|
|
|
}
|
|
|
|
|
|
2020-09-16 14:45:34 +01:00
|
|
|
let discoverySection;
|
|
|
|
|
if (SettingsStore.getValue(UIFeature.IdentityServer)) {
|
2023-05-24 14:37:10 +12:00
|
|
|
const discoWarning = this.state.requiredPolicyInfo.hasTerms ? (
|
|
|
|
|
<WarningIcon
|
|
|
|
|
className="mx_GeneralUserSettingsTab_warningIcon"
|
|
|
|
|
width="18"
|
|
|
|
|
height="18"
|
|
|
|
|
// override icon default values
|
|
|
|
|
aria-hidden={false}
|
|
|
|
|
aria-label={_t("Warning")}
|
|
|
|
|
/>
|
|
|
|
|
) : null;
|
|
|
|
|
const heading = (
|
2023-06-29 11:30:25 +01:00
|
|
|
<Heading size="2">
|
2023-05-24 14:37:10 +12:00
|
|
|
{discoWarning}
|
|
|
|
|
{_t("Discovery")}
|
|
|
|
|
</Heading>
|
2020-09-16 14:45:34 +01:00
|
|
|
);
|
2023-05-26 10:42:01 +12:00
|
|
|
discoverySection = (
|
|
|
|
|
<SettingsSection heading={heading} data-testid="discoverySection">
|
|
|
|
|
{this.renderDiscoverySection()}
|
|
|
|
|
</SettingsSection>
|
|
|
|
|
);
|
2020-09-16 14:45:34 +01:00
|
|
|
}
|
|
|
|
|
|
2019-01-18 20:22:36 -07:00
|
|
|
return (
|
2023-05-17 19:52:44 +12:00
|
|
|
<SettingsTab data-testid="mx_GeneralUserSettingsTab">
|
2023-05-29 13:36:09 +12:00
|
|
|
<SettingsSection heading={_t("General")}>
|
|
|
|
|
<ProfileSettings />
|
|
|
|
|
{this.renderAccountSection()}
|
|
|
|
|
{this.renderLanguageSection()}
|
|
|
|
|
{supportsMultiLanguageSpellCheck ? this.renderSpellCheckSection() : null}
|
|
|
|
|
</SettingsSection>
|
2020-09-16 14:45:34 +01:00
|
|
|
{discoverySection}
|
2023-05-17 19:52:44 +12:00
|
|
|
{this.renderIntegrationManagerSection()}
|
2020-09-17 11:55:10 +01:00
|
|
|
{accountManagementSection}
|
2023-05-17 19:52:44 +12:00
|
|
|
</SettingsTab>
|
2019-01-18 20:22:36 -07:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|