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.
|
|
|
|
|
*/
|
|
|
|
|
|
2024-07-18 14:04:40 +01:00
|
|
|
import React from "react";
|
2024-06-26 14:04:19 +01:00
|
|
|
import { HTTPError } from "matrix-js-sdk/src/matrix";
|
2021-10-22 17:23:32 -05:00
|
|
|
import { logger } from "matrix-js-sdk/src/logger";
|
|
|
|
|
|
2023-04-24 03:41:09 -05:00
|
|
|
import { UserFriendlyError, _t } from "../../../../../languageHandler";
|
2024-06-06 14:56:38 +01:00
|
|
|
import UserProfileSettings from "../../UserProfileSettings";
|
2019-02-22 10:47:18 -07:00
|
|
|
import SettingsStore from "../../../../../settings/SettingsStore";
|
|
|
|
|
import AccessibleButton from "../../../elements/AccessibleButton";
|
|
|
|
|
import DeactivateAccountDialog from "../../../dialogs/DeactivateAccountDialog";
|
2019-08-09 18:07:58 +01:00
|
|
|
import Modal from "../../../../../Modal";
|
2021-06-29 13:11:58 +01:00
|
|
|
import { UIFeature } from "../../../../../settings/UIFeature";
|
2023-04-24 03:41:09 -05:00
|
|
|
import ErrorDialog, { extractErrorMessageFromError } from "../../../dialogs/ErrorDialog";
|
2021-09-21 13:45:38 +02:00
|
|
|
import ChangePassword from "../../ChangePassword";
|
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-10-16 12:03:25 +13:00
|
|
|
import { SDKContext } from "../../../../../contexts/SDKContext";
|
2024-06-26 14:04:19 +01:00
|
|
|
import UserPersonalInfoSettings from "../../UserPersonalInfoSettings";
|
2021-09-21 13:45:38 +02:00
|
|
|
|
|
|
|
|
interface IProps {
|
|
|
|
|
closeSettingsFn: () => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface IState {
|
|
|
|
|
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;
|
2024-07-02 11:04:07 +01:00
|
|
|
canSetDisplayName: boolean;
|
|
|
|
|
canSetAvatar: 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-10-16 12:03:25 +13:00
|
|
|
public static contextType = SDKContext;
|
|
|
|
|
public context!: React.ContextType<typeof SDKContext>;
|
2023-06-01 14:43:24 +01:00
|
|
|
|
2023-10-16 12:03:25 +13:00
|
|
|
public constructor(props: IProps, context: React.ContextType<typeof SDKContext>) {
|
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 = {
|
2021-09-21 13:45:38 +02:00
|
|
|
canChangePassword: false,
|
2023-08-22 18:07:10 +12:00
|
|
|
canMake3pidChanges: false,
|
2024-07-02 11:04:07 +01:00
|
|
|
canSetDisplayName: false,
|
|
|
|
|
canSetAvatar: false,
|
2019-01-23 14:43:45 -07:00
|
|
|
};
|
2019-08-13 18:40:27 +01:00
|
|
|
|
2022-11-18 09:22:43 +00:00
|
|
|
this.getCapabilities();
|
2019-08-19 15:17:14 +01:00
|
|
|
}
|
|
|
|
|
|
2022-11-18 09:22:43 +00:00
|
|
|
private async getCapabilities(): Promise<void> {
|
2023-10-16 12:03:25 +13:00
|
|
|
const cli = this.context.client!;
|
2022-11-18 09:22:43 +00:00
|
|
|
|
2024-07-02 11:04:07 +01:00
|
|
|
const capabilities = (await cli.getCapabilities()) ?? {};
|
2022-11-18 09:22:43 +00:00
|
|
|
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;
|
|
|
|
|
|
2024-02-21 10:43:47 +00:00
|
|
|
await this.context.oidcClientStore.readyPromise; // wait for the store to be ready
|
2023-10-16 12:03:25 +13:00
|
|
|
const externalAccountManagementUrl = this.context.oidcClientStore.accountManagementEndpoint;
|
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
|
|
|
|
2024-07-02 11:04:07 +01:00
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
|
canChangePassword,
|
|
|
|
|
externalAccountManagementUrl,
|
|
|
|
|
canMake3pidChanges,
|
|
|
|
|
canSetDisplayName,
|
|
|
|
|
canSetAvatar,
|
|
|
|
|
});
|
2022-11-18 09:22:43 +00: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,
|
2023-09-26 18:35:55 +01:00
|
|
|
_t("settings|general|error_password_change_unknown", {
|
2023-04-24 03:41:09 -05:00
|
|
|
stringifiedError: String(err),
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let errorMessageToDisplay = errorMessage;
|
|
|
|
|
if (underlyingError instanceof HTTPError && underlyingError.httpStatus === 403) {
|
2023-09-26 18:35:55 +01:00
|
|
|
errorMessageToDisplay = _t("settings|general|error_password_change_403");
|
2023-04-24 03:41:09 -05:00
|
|
|
} else if (underlyingError instanceof HTTPError) {
|
2023-09-26 18:35:55 +01:00
|
|
|
errorMessageToDisplay = _t("settings|general|error_password_change_http", {
|
2023-04-24 03:41:09 -05:00
|
|
|
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-09-26 18:35:55 +01:00
|
|
|
title: _t("settings|general|error_password_change_title"),
|
2023-04-24 03:41:09 -05:00
|
|
|
description: errorMessageToDisplay,
|
2019-01-22 13:09:40 -07:00
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2023-08-14 02:25:13 -06:00
|
|
|
private onPasswordChanged = (): void => {
|
2023-09-26 18:35:55 +01:00
|
|
|
const description = _t("settings|general|password_change_success");
|
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, {
|
2023-08-22 18:47:33 +01:00
|
|
|
title: _t("common|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
|
|
|
};
|
|
|
|
|
|
2024-07-18 14:04:40 +01:00
|
|
|
private renderAccountSection(): JSX.Element | undefined {
|
|
|
|
|
if (!this.state.canChangePassword) return undefined;
|
2020-01-23 16:29:55 -07:00
|
|
|
|
2019-01-22 10:36:47 -07:00
|
|
|
return (
|
2023-05-26 10:42:01 +12:00
|
|
|
<>
|
2023-09-21 18:16:04 +01:00
|
|
|
<SettingsSubsection
|
|
|
|
|
heading={_t("settings|general|account_section")}
|
|
|
|
|
stretchContent
|
|
|
|
|
data-testid="accountSection"
|
|
|
|
|
>
|
2024-07-18 14:04:40 +01:00
|
|
|
<SettingsSubsectionText>{_t("settings|general|password_change_section")}</SettingsSubsectionText>
|
|
|
|
|
<ChangePassword
|
|
|
|
|
className="mx_GeneralUserSettingsTab_section--account_changePassword"
|
|
|
|
|
rowClassName=""
|
|
|
|
|
buttonKind="primary"
|
|
|
|
|
onError={this.onPasswordChangeError}
|
|
|
|
|
onFinished={this.onPasswordChanged}
|
|
|
|
|
/>
|
2023-05-26 10:42:01 +12:00
|
|
|
</SettingsSubsection>
|
|
|
|
|
</>
|
2019-01-22 10:36:47 -07: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-09-26 18:35:55 +01:00
|
|
|
<SettingsSection heading={_t("settings|general|deactivate_section")}>
|
2023-05-17 19:52:44 +12:00
|
|
|
<SettingsSubsection
|
2023-09-26 18:35:55 +01:00
|
|
|
heading={_t("settings|general|account_management_section")}
|
2023-05-17 19:52:44 +12:00
|
|
|
data-testid="account-management-section"
|
2023-09-26 18:35:55 +01:00
|
|
|
description={_t("settings|general|deactivate_warning")}
|
2023-05-17 19:52:44 +12:00
|
|
|
>
|
|
|
|
|
<AccessibleButton onClick={this.onDeactivateClicked} kind="danger">
|
2023-09-26 18:35:55 +01:00
|
|
|
{_t("settings|general|deactivate_section")}
|
2023-05-17 19:52:44 +12:00
|
|
|
</AccessibleButton>
|
|
|
|
|
</SettingsSubsection>
|
|
|
|
|
</SettingsSection>
|
2019-01-22 10:36:47 -07:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-13 17:01:43 +00:00
|
|
|
public render(): React.ReactNode {
|
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
|
|
|
}
|
|
|
|
|
|
2019-01-18 20:22:36 -07:00
|
|
|
return (
|
2023-05-17 19:52:44 +12:00
|
|
|
<SettingsTab data-testid="mx_GeneralUserSettingsTab">
|
2024-05-07 10:32:24 +01:00
|
|
|
<SettingsSection>
|
2024-07-02 11:04:07 +01:00
|
|
|
<UserProfileSettings
|
2024-07-04 10:46:26 +01:00
|
|
|
externalAccountManagementUrl={this.state.externalAccountManagementUrl}
|
2024-07-02 11:04:07 +01:00
|
|
|
canSetDisplayName={this.state.canSetDisplayName}
|
|
|
|
|
canSetAvatar={this.state.canSetAvatar}
|
|
|
|
|
/>
|
2024-06-26 14:04:19 +01:00
|
|
|
<UserPersonalInfoSettings canMake3pidChanges={this.state.canMake3pidChanges} />
|
2023-05-29 13:36:09 +12:00
|
|
|
{this.renderAccountSection()}
|
|
|
|
|
</SettingsSection>
|
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
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|