Convert GeneralUserSettingsTab to TS

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
Šimon Brandner 2021-09-21 13:45:38 +02:00
parent e88227dac8
commit 222427dae8
No known key found for this signature in database
GPG key ID: 55C211A1226CB17D
2 changed files with 95 additions and 72 deletions

View file

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

View file

@ -25,7 +25,7 @@ interface IProps {
policiesAndServicePairs: any[]; policiesAndServicePairs: any[];
onFinished: (string) => void; onFinished: (string) => void;
agreedUrls: string[]; // array of URLs the user has accepted agreedUrls: string[]; // array of URLs the user has accepted
introElement: Node; introElement: React.ReactNode;
} }
interface IState { interface IState {