Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Move the account management button #12663

Merged
merged 12 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions res/css/views/settings/_UserProfileSettings.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ limitations under the License.
font-size: 15px;
font-weight: 500;
}

.mx_UserProfileSettings_profile_buttons {
margin-top: var(--cpd-space-8x);
margin-bottom: var(--cpd-space-8x);
}

.mx_UserProfileSettings_accountmanageIcon {
margin-right: var(--cpd-space-2x);
}
}

@media (max-width: 768px) {
Expand Down
34 changes: 33 additions & 1 deletion src/components/views/settings/UserProfileSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
import React, { ChangeEvent, useCallback, useEffect, useMemo, useState } from "react";
import { logger } from "matrix-js-sdk/src/logger";
import { EditInPlace, Alert, ErrorMessage } from "@vector-im/compound-web";
import { Icon as PopOutIcon } from "@vector-im/compound-design-tokens/icons/pop-out.svg";

import { _t } from "../../../languageHandler";
import { OwnProfileStore } from "../../../stores/OwnProfileStore";
Expand All @@ -29,6 +30,7 @@ import UserIdentifierCustomisations from "../../../customisations/UserIdentifier
import { useId } from "../../../utils/useId";
import CopyableText from "../elements/CopyableText";
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
import AccessibleButton from "../elements/AccessibleButton";

const SpinnerToast: React.FC = ({ children }) => (
<>
Expand All @@ -55,7 +57,28 @@ const UsernameBox: React.FC<UsernameBoxProps> = ({ username }) => {
);
};

interface ManageAccountButtonProps {
externalAccountManagementUrl: string;
}

const ManageAccountButton: React.FC<ManageAccountButtonProps> = ({ externalAccountManagementUrl }) => (
<AccessibleButton
onClick={null}
element="a"
kind="primary"
target="_blank"
rel="noreferrer noopener"
href={externalAccountManagementUrl}
data-testid="external-account-management-link"
>
<PopOutIcon className="mx_UserProfileSettings_accountmanageIcon" width="24" height="24" />
{_t("settings|general|oidc_manage_button")}
</AccessibleButton>
);

interface UserProfileSettingsProps {
// The URL to redirect the user to in order to manage their account.
externalAccountManagementUrl?: string;
// Whether the homeserver allows the user to set their display name.
canSetDisplayName: boolean;
// Whether the homeserver allows the user to set their avatar.
Expand All @@ -65,7 +88,11 @@ interface UserProfileSettingsProps {
/**
* A group of settings views to allow the user to set their profile information.
*/
const UserProfileSettings: React.FC<UserProfileSettingsProps> = ({ canSetDisplayName, canSetAvatar }) => {
const UserProfileSettings: React.FC<UserProfileSettingsProps> = ({
externalAccountManagementUrl,
canSetDisplayName,
canSetAvatar,
}) => {
const [avatarURL, setAvatarURL] = useState(OwnProfileStore.instance.avatarMxc);
const [displayName, setDisplayName] = useState(OwnProfileStore.instance.displayName ?? "");
const [avatarError, setAvatarError] = useState<boolean>(false);
Expand Down Expand Up @@ -192,6 +219,11 @@ const UserProfileSettings: React.FC<UserProfileSettingsProps> = ({ canSetDisplay
</Alert>
)}
{userIdentifier && <UsernameBox username={userIdentifier} />}
{externalAccountManagementUrl && (
<div className="mx_UserProfileSettings_profile_buttons">
<ManageAccountButton externalAccountManagementUrl={externalAccountManagementUrl} />
</div>
)}
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,41 +215,13 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
);
}

let externalAccountManagement: JSX.Element | undefined;
if (this.state.externalAccountManagementUrl) {
const { hostname } = new URL(this.state.externalAccountManagementUrl);

externalAccountManagement = (
<>
<SettingsSubsectionText data-testid="external-account-management-outer">
{_t(
"settings|general|external_account_management",
{ hostname },
{ code: (sub) => <code>{sub}</code> },
)}
</SettingsSubsectionText>
<AccessibleButton
onClick={null}
element="a"
kind="primary"
target="_blank"
rel="noreferrer noopener"
href={this.state.externalAccountManagementUrl}
data-testid="external-account-management-link"
>
{_t("settings|general|oidc_manage_button")}
</AccessibleButton>
</>
);
}
return (
<>
<SettingsSubsection
heading={_t("settings|general|account_section")}
stretchContent
data-testid="accountSection"
>
{externalAccountManagement}
{passwordChangeSection}
</SettingsSubsection>
</>
Expand Down Expand Up @@ -324,6 +296,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
<SettingsTab data-testid="mx_GeneralUserSettingsTab">
<SettingsSection>
<UserProfileSettings
externalAccountManagementUrl={this.state.externalAccountManagementUrl}
canSetDisplayName={this.state.canSetDisplayName}
canSetAvatar={this.state.canSetAvatar}
/>
Expand Down
1 change: 0 additions & 1 deletion src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -2511,7 +2511,6 @@
"error_revoke_msisdn_discovery": "Unable to revoke sharing for phone number",
"error_share_email_discovery": "Unable to share email address",
"error_share_msisdn_discovery": "Unable to share phone number",
"external_account_management": "Your account details are managed separately at <code>%(hostname)s</code>.",
"identity_server_no_token": "No identity access token found",
"identity_server_not_set": "Identity server not set",
"incorrect_msisdn_verification": "Incorrect verification code",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,10 @@ describe("<GeneralUserSettingsTab />", () => {
} as unknown as OidcClientStore;
jest.spyOn(stores, "oidcClientStore", "get").mockReturnValue(mockOidcClientStore);

const { getByTestId } = render(getComponent());
render(getComponent());

// wait for well-known call to settle
await flushPromises();

expect(getByTestId("external-account-management-outer").textContent).toMatch(/.*id\.server\.org/);
expect(getByTestId("external-account-management-link").getAttribute("href")).toMatch(accountManagementLink);
const manageAccountLink = await screen.findByRole("button", { name: "Manage account" });
expect(manageAccountLink.getAttribute("href")).toMatch(accountManagementLink);
});

describe("Manage integrations", () => {
Expand Down
Loading