diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx index 40affa35b3..be096c3797 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { useCallback, useContext, useEffect } from "react"; import { HTTPError } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; @@ -34,70 +34,106 @@ import { SettingsSection } from "../../shared/SettingsSection"; import SettingsSubsection, { SettingsSubsectionText } from "../../shared/SettingsSubsection"; import { SDKContext } from "../../../../../contexts/SDKContext"; import UserPersonalInfoSettings from "../../UserPersonalInfoSettings"; +import { useMatrixClientContext } from "../../../../../contexts/MatrixClientContext"; interface IProps { closeSettingsFn: () => void; } -interface IState { +interface AccountSectionProps { canChangePassword: boolean; - idServerName?: string; - externalAccountManagementUrl?: string; - canMake3pidChanges: boolean; - canSetDisplayName: boolean; - canSetAvatar: boolean; + onPasswordChangeError: (e: Error) => void; + onPasswordChanged: () => void; } -export default class GeneralUserSettingsTab extends React.Component { - public static contextType = SDKContext; - public context!: React.ContextType; +const AccountSection: React.FC = ({ + canChangePassword, + onPasswordChangeError, + onPasswordChanged, +}) => { + if (!canChangePassword) return <>; - public constructor(props: IProps, context: React.ContextType) { - super(props, context); - this.context = context; + return ( + <> + + {_t("settings|general|password_change_section")} + + + + ); +}; - this.state = { - canChangePassword: false, - canMake3pidChanges: false, - canSetDisplayName: false, - canSetAvatar: false, - }; +interface ManagementSectionProps { + onDeactivateClicked: () => void; +} - this.getCapabilities(); - } +const ManagementSection: React.FC = ({ onDeactivateClicked }) => { + return ( + + + + {_t("settings|general|deactivate_section")} + + + + ); +}; - private async getCapabilities(): Promise { - const cli = this.context.client!; +const GeneralUserSettingsTab: React.FC = ({ closeSettingsFn }) => { + const [externalAccountManagementUrl, setExternalAccountManagementUrl] = React.useState(); + const [canMake3pidChanges, setCanMake3pidChanges] = React.useState(false); + const [canSetDisplayName, setCanSetDisplayName] = React.useState(false); + const [canSetAvatar, setCanSetAvatar] = React.useState(false); + const [canChangePassword, setCanChangePassword] = React.useState(false); - const capabilities = (await cli.getCapabilities()) ?? {}; - const changePasswordCap = capabilities["m.change_password"]; + const cli = useMatrixClientContext(); + const sdkContext = useContext(SDKContext); - // 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; + useEffect(() => { + (async () => { + const capabilities = (await cli.getCapabilities()) ?? {}; + const changePasswordCap = capabilities["m.change_password"]; - await this.context.oidcClientStore.readyPromise; // wait for the store to be ready - const externalAccountManagementUrl = this.context.oidcClientStore.accountManagementEndpoint; - // 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; + // 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; - const canSetDisplayName = - !capabilities["m.set_displayname"] || capabilities["m.set_displayname"].enabled === true; - const canSetAvatar = !capabilities["m.set_avatar_url"] || capabilities["m.set_avatar_url"].enabled === true; + await sdkContext.oidcClientStore.readyPromise; // wait for the store to be ready + const externalAccountManagementUrl = sdkContext.oidcClientStore.accountManagementEndpoint; + // 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; - this.setState({ - canChangePassword, - externalAccountManagementUrl, - canMake3pidChanges, - canSetDisplayName, - canSetAvatar, - }); - } + const canSetDisplayName = + !capabilities["m.set_displayname"] || capabilities["m.set_displayname"].enabled === true; + const canSetAvatar = !capabilities["m.set_avatar_url"] || capabilities["m.set_avatar_url"].enabled === true; - private onPasswordChangeError = (err: Error): void => { + setCanMake3pidChanges(canMake3pidChanges); + setCanSetDisplayName(canSetDisplayName); + setCanSetAvatar(canSetAvatar); + setExternalAccountManagementUrl(externalAccountManagementUrl); + setCanChangePassword(canChangePassword); + })(); + }, [cli, sdkContext.oidcClientStore]); + + const onPasswordChangeError = useCallback((err: Error): void => { logger.error("Failed to change password: " + err); let underlyingError = err; @@ -127,85 +163,49 @@ export default class GeneralUserSettingsTab extends React.Component { + const onPasswordChanged = useCallback((): void => { const description = _t("settings|general|password_change_success"); // TODO: Figure out a design that doesn't involve replacing the current dialog Modal.createDialog(ErrorDialog, { title: _t("common|success"), description, }); - }; + }, []); - private onDeactivateClicked = (): void => { + const onDeactivateClicked = useCallback((): void => { Modal.createDialog(DeactivateAccountDialog, { onFinished: (success) => { - if (success) this.props.closeSettingsFn(); + if (success) closeSettingsFn(); }, }); - }; + }, [closeSettingsFn]); - private renderAccountSection(): JSX.Element | undefined { - if (!this.state.canChangePassword) return undefined; - - return ( - <> - - {_t("settings|general|password_change_section")} - - - - ); + let accountManagementSection: JSX.Element | undefined; + const isAccountManagedExternally = Boolean(externalAccountManagementUrl); + if (SettingsStore.getValue(UIFeature.Deactivate) && !isAccountManagedExternally) { + accountManagementSection = ; } - private renderManagementSection(): JSX.Element { - // TODO: Improve warning text for account deactivation - return ( - - - - {_t("settings|general|deactivate_section")} - - + return ( + + + + + - ); - } + {accountManagementSection} + + ); +}; - public render(): React.ReactNode { - let accountManagementSection: JSX.Element | undefined; - const isAccountManagedExternally = !!this.state.externalAccountManagementUrl; - if (SettingsStore.getValue(UIFeature.Deactivate) && !isAccountManagedExternally) { - accountManagementSection = this.renderManagementSection(); - } - - return ( - - - - - {this.renderAccountSection()} - - {accountManagementSection} - - ); - } -} +export default GeneralUserSettingsTab;