diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx similarity index 76% rename from src/components/views/settings/tabs/user/GeneralUserSettingsTab.js rename to src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx index 238d6cca21..77b3ace22b 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx @@ -25,13 +25,11 @@ import LanguageDropdown from "../../../elements/LanguageDropdown"; import SpellCheckSettings from "../../SpellCheckSettings"; import AccessibleButton from "../../../elements/AccessibleButton"; import DeactivateAccountDialog from "../../../dialogs/DeactivateAccountDialog"; -import PropTypes from "prop-types"; import PlatformPeg from "../../../../../PlatformPeg"; import { MatrixClientPeg } from "../../../../../MatrixClientPeg"; -import * as sdk from "../../../../.."; import Modal from "../../../../../Modal"; import dis from "../../../../../dispatcher/dispatcher"; -import { Service, startTermsFlow } from "../../../../../Terms"; +import { Policies, Service, startTermsFlow } from "../../../../../Terms"; import { SERVICE_TYPES } from "matrix-js-sdk/src/service-types"; import IdentityAuthClient from "../../../../../IdentityAuthClient"; import { abbreviateUrl } from "../../../../../utils/UrlUtils"; @@ -40,15 +38,50 @@ import Spinner from "../../../elements/Spinner"; import { SettingLevel } from "../../../../../settings/SettingLevel"; import { UIFeature } from "../../../../../settings/UIFeature"; import { replaceableComponent } from "../../../../../utils/replaceableComponent"; +import { IThreepid } from "matrix-js-sdk/src/@types/threepids"; +import { ActionPayload } from "../../../../../dispatcher/payloads"; +import ErrorDialog from "../../../dialogs/ErrorDialog"; +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"; + +interface IProps { + closeSettingsFn: () => void; +} + +interface IState { + language: string; + spellCheckLanguages: string[]; + haveIdServer: boolean; + serverSupportsSeparateAddAndBind: boolean; + idServerHasUnsignedTerms: boolean; + requiredPolicyInfo: { // This object is passed along to a component for handling + hasTerms: boolean; + policiesAndServices: { + service: Service; + policies: Policies; + }[]; // From the startTermsFlow callback + agreedUrls: string[]; // From the startTermsFlow callback + resolve: (values: string[]) => void; // Promise resolve function for startTermsFlow callback + }; + emails: IThreepid[]; + msisdns: IThreepid[]; + loading3pids: boolean; // whether or not the emails and msisdns have been loaded + canChangePassword: boolean; + idServerName: string; +} @replaceableComponent("views.settings.tabs.user.GeneralUserSettingsTab") -export default class GeneralUserSettingsTab extends React.Component { - static propTypes = { - closeSettingsFn: PropTypes.func.isRequired, - }; +export default class GeneralUserSettingsTab extends React.Component { + private readonly dispatcherRef: string; - constructor() { - super(); + constructor(props: IProps) { + super(props); this.state = { language: languageHandler.getCurrentLanguage(), @@ -58,20 +91,23 @@ export default class GeneralUserSettingsTab extends React.Component { idServerHasUnsignedTerms: false, requiredPolicyInfo: { // This object is passed along to a component for handling hasTerms: false, - // policiesAndServices, // From the startTermsFlow callback - // agreedUrls, // From the startTermsFlow callback - // resolve, // Promise resolve function for startTermsFlow callback + policiesAndServices: null, // From the startTermsFlow callback + agreedUrls: null, // From the startTermsFlow callback + resolve: null, // Promise resolve function for startTermsFlow callback }, emails: [], msisdns: [], loading3pids: true, // whether or not the emails and msisdns have been loaded + canChangePassword: false, + idServerName: null, }; - this.dispatcherRef = dis.register(this._onAction); + this.dispatcherRef = dis.register(this.onAction); } // TODO: [REACT-WARNING] Move this to constructor - async UNSAFE_componentWillMount() { // eslint-disable-line camelcase + // eslint-disable-next-line @typescript-eslint/naming-convention, camelcase + public async UNSAFE_componentWillMount(): Promise { const cli = MatrixClientPeg.get(); const serverSupportsSeparateAddAndBind = await cli.doesServerSupportSeparateAddAndBind(); @@ -86,10 +122,10 @@ export default class GeneralUserSettingsTab extends React.Component { this.setState({ serverSupportsSeparateAddAndBind, canChangePassword }); - this._getThreepidState(); + this.getThreepidState(); } - async componentDidMount() { + public async componentDidMount(): Promise { const plaf = PlatformPeg.get(); if (plaf) { this.setState({ @@ -98,30 +134,30 @@ export default class GeneralUserSettingsTab extends React.Component { } } - componentWillUnmount() { + public componentWillUnmount(): void { dis.unregister(this.dispatcherRef); } - _onAction = (payload) => { + private onAction = (payload: ActionPayload): void => { if (payload.action === 'id_server_changed') { this.setState({ haveIdServer: Boolean(MatrixClientPeg.get().getIdentityServerUrl()) }); - this._getThreepidState(); + this.getThreepidState(); } }; - _onEmailsChange = (emails) => { + private onEmailsChange = (emails: IThreepid[]): void => { this.setState({ emails }); }; - _onMsisdnsChange = (msisdns) => { + private onMsisdnsChange = (msisdns: IThreepid[]): void => { this.setState({ msisdns }); }; - async _getThreepidState() { + private async getThreepidState(): Promise { const cli = MatrixClientPeg.get(); // Check to see if terms need accepting - this._checkTerms(); + this.checkTerms(); // Need to get 3PIDs generally for Account section and possibly also for // Discovery (assuming we have an IS and terms are agreed). @@ -143,7 +179,7 @@ export default class GeneralUserSettingsTab extends React.Component { }); } - async _checkTerms() { + private async checkTerms(): Promise { if (!this.state.haveIdServer) { this.setState({ idServerHasUnsignedTerms: false }); return; @@ -176,6 +212,7 @@ export default class GeneralUserSettingsTab extends React.Component { this.setState({ requiredPolicyInfo: { hasTerms: false, + ...this.state.requiredPolicyInfo, }, }); } catch (e) { @@ -187,19 +224,19 @@ export default class GeneralUserSettingsTab extends React.Component { } } - _onLanguageChange = (newLanguage) => { + private onLanguageChange = (newLanguage: string): void => { if (this.state.language === newLanguage) return; SettingsStore.setValue("language", null, SettingLevel.DEVICE, newLanguage); this.setState({ language: newLanguage }); const platform = PlatformPeg.get(); if (platform) { - platform.setLanguage(newLanguage); + platform.setLanguage([newLanguage]); platform.reload(); } }; - _onSpellCheckLanguagesChange = (languages) => { + private onSpellCheckLanguagesChange = (languages: string[]): void => { this.setState({ spellCheckLanguages: languages }); const plaf = PlatformPeg.get(); @@ -208,7 +245,7 @@ export default class GeneralUserSettingsTab extends React.Component { } }; - _onPasswordChangeError = (err) => { + private onPasswordChangeError = (err): void => { // TODO: Figure out a design that doesn't involve replacing the current dialog let errMsg = err.error || err.message || ""; if (err.httpStatus === 403) { @@ -216,7 +253,6 @@ export default class GeneralUserSettingsTab extends React.Component { } else if (!errMsg) { errMsg += ` (HTTP status ${err.httpStatus})`; } - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Failed to change password: " + errMsg); Modal.createTrackedDialog('Failed to change password', '', ErrorDialog, { title: _t("Error"), @@ -224,9 +260,8 @@ export default class GeneralUserSettingsTab extends React.Component { }); }; - _onPasswordChanged = () => { + private onPasswordChanged = (): void => { // TODO: Figure out a design that doesn't involve replacing the current dialog - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createTrackedDialog('Password changed', '', ErrorDialog, { title: _t("Success"), description: _t( @@ -236,7 +271,7 @@ export default class GeneralUserSettingsTab extends React.Component { }); }; - _onDeactivateClicked = () => { + private onDeactivateClicked = (): void => { Modal.createTrackedDialog('Deactivate Account', '', DeactivateAccountDialog, { onFinished: (success) => { if (success) this.props.closeSettingsFn(); @@ -244,7 +279,7 @@ export default class GeneralUserSettingsTab extends React.Component { }); }; - _renderProfileSection() { + private renderProfileSection(): JSX.Element { return (
@@ -252,18 +287,14 @@ export default class GeneralUserSettingsTab extends React.Component { ); } - _renderAccountSection() { - const ChangePassword = sdk.getComponent("views.settings.ChangePassword"); - const EmailAddresses = sdk.getComponent("views.settings.account.EmailAddresses"); - const PhoneNumbers = sdk.getComponent("views.settings.account.PhoneNumbers"); - + private renderAccountSection(): JSX.Element { let passwordChangeForm = ( + onError={this.onPasswordChangeError} + onFinished={this.onPasswordChanged} /> ); let threepidSection = null; @@ -278,15 +309,15 @@ export default class GeneralUserSettingsTab extends React.Component { ) { const emails = this.state.loading3pids ? - : ; const msisdns = this.state.loading3pids ? - : ; threepidSection =
{ _t("Email addresses") } @@ -318,37 +349,34 @@ export default class GeneralUserSettingsTab extends React.Component { ); } - _renderLanguageSection() { + private renderLanguageSection(): JSX.Element { // TODO: Convert to new-styled Field return (
{ _t("Language and region") }
); } - _renderSpellCheckSection() { + private renderSpellCheckSection(): JSX.Element { return (
{ _t("Spell check dictionaries") }
); } - _renderDiscoverySection() { - const SetIdServer = sdk.getComponent("views.settings.SetIdServer"); - + private renderDiscoverySection(): JSX.Element { if (this.state.requiredPolicyInfo.hasTerms) { - const InlineTermsAgreement = sdk.getComponent("views.terms.InlineTermsAgreement"); const intro = { _t( "Agree to the identity server (%(serverName)s) Terms of Service to " + @@ -370,11 +398,8 @@ export default class GeneralUserSettingsTab extends React.Component { ); } - const EmailAddresses = sdk.getComponent("views.settings.discovery.EmailAddresses"); - const PhoneNumbers = sdk.getComponent("views.settings.discovery.PhoneNumbers"); - - const emails = this.state.loading3pids ? : ; - const msisdns = this.state.loading3pids ? : ; + const emails = this.state.loading3pids ? : ; + const msisdns = this.state.loading3pids ? : ; const threepidSection = this.state.haveIdServer ?
{ _t("Email addresses") } @@ -388,12 +413,12 @@ export default class GeneralUserSettingsTab extends React.Component {
{ threepidSection } { /* has its own heading as it includes the current identity server */ } - +
); } - _renderManagementSection() { + private renderManagementSection(): JSX.Element { // TODO: Improve warning text for account deactivation return (
@@ -401,18 +426,16 @@ export default class GeneralUserSettingsTab extends React.Component { { _t("Deactivating your account is a permanent action - be careful!") } - + { _t("Deactivate Account") }
); } - _renderIntegrationManagerSection() { + private renderIntegrationManagerSection(): JSX.Element { if (!SettingsStore.getValue(UIFeature.Widgets)) return null; - const SetIntegrationManager = sdk.getComponent("views.settings.SetIntegrationManager"); - return (
{ /* has its own heading as it includes the current integration manager */ } @@ -421,7 +444,7 @@ export default class GeneralUserSettingsTab extends React.Component { ); } - render() { + public render(): JSX.Element { const plaf = PlatformPeg.get(); const supportsMultiLanguageSpellCheck = plaf.supportsMultiLanguageSpellCheck(); @@ -439,7 +462,7 @@ export default class GeneralUserSettingsTab extends React.Component { if (SettingsStore.getValue(UIFeature.Deactivate)) { accountManagementSection = <>
{ _t("Deactivate account") }
- { this._renderManagementSection() } + { this.renderManagementSection() } ; } @@ -447,19 +470,19 @@ export default class GeneralUserSettingsTab extends React.Component { if (SettingsStore.getValue(UIFeature.IdentityServer)) { discoverySection = <>
{ discoWarning } { _t("Discovery") }
- { this._renderDiscoverySection() } + { this.renderDiscoverySection() } ; } return (
{ _t("General") }
- { this._renderProfileSection() } - { this._renderAccountSection() } - { this._renderLanguageSection() } - { supportsMultiLanguageSpellCheck ? this._renderSpellCheckSection() : null } + { this.renderProfileSection() } + { this.renderAccountSection() } + { this.renderLanguageSection() } + { supportsMultiLanguageSpellCheck ? this.renderSpellCheckSection() : null } { discoverySection } - { this._renderIntegrationManagerSection() /* Has its own title */ } + { this.renderIntegrationManagerSection() /* Has its own title */ } { accountManagementSection }
); diff --git a/src/components/views/terms/InlineTermsAgreement.tsx b/src/components/views/terms/InlineTermsAgreement.tsx index 54c0258d37..80ccf49ee9 100644 --- a/src/components/views/terms/InlineTermsAgreement.tsx +++ b/src/components/views/terms/InlineTermsAgreement.tsx @@ -25,7 +25,7 @@ interface IProps { policiesAndServicePairs: any[]; onFinished: (string) => void; agreedUrls: string[]; // array of URLs the user has accepted - introElement: Node; + introElement: React.ReactNode; } interface IState {