Move the account management button (#12663)

* Disable profile controls if the HS doesn't allow them to be set

Also updates to the js-sdk interface changes in https://github.com/matrix-org/matrix-js-sdk/pull/4246

* Remove unnecessary await

* Pass disabled prop to accessiblebutton in avatarsetting

* Move the account management button

The section it lives in with the server name goes, and the button
just lives on its own in the profile section.

* Update test

* Revert bits of previous PR that are no longer wanted

because we squash merge so git can no longer make sense of what changes
have been applied.

* More squash-merge fails

* More more squash merge fails
This commit is contained in:
David Baker 2024-07-04 10:46:26 +01:00 committed by GitHub
parent 1fbc97296c
commit e48110d7c6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 46 additions and 36 deletions

View file

@ -47,6 +47,15 @@ limitations under the License.
font-size: 15px; font-size: 15px;
font-weight: 500; 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) { @media (max-width: 768px) {

View file

@ -17,6 +17,7 @@ limitations under the License.
import React, { ChangeEvent, useCallback, useEffect, useMemo, useState } from "react"; import React, { ChangeEvent, useCallback, useEffect, useMemo, useState } from "react";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { EditInPlace, Alert, ErrorMessage } from "@vector-im/compound-web"; 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 { _t } from "../../../languageHandler";
import { OwnProfileStore } from "../../../stores/OwnProfileStore"; import { OwnProfileStore } from "../../../stores/OwnProfileStore";
@ -29,6 +30,7 @@ import UserIdentifierCustomisations from "../../../customisations/UserIdentifier
import { useId } from "../../../utils/useId"; import { useId } from "../../../utils/useId";
import CopyableText from "../elements/CopyableText"; import CopyableText from "../elements/CopyableText";
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
import AccessibleButton from "../elements/AccessibleButton";
const SpinnerToast: React.FC = ({ children }) => ( const SpinnerToast: React.FC = ({ children }) => (
<> <>
@ -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 { 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. // Whether the homeserver allows the user to set their display name.
canSetDisplayName: boolean; canSetDisplayName: boolean;
// Whether the homeserver allows the user to set their avatar. // Whether the homeserver allows the user to set their avatar.
@ -65,7 +88,11 @@ interface UserProfileSettingsProps {
/** /**
* A group of settings views to allow the user to set their profile information. * 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 [avatarURL, setAvatarURL] = useState(OwnProfileStore.instance.avatarMxc);
const [displayName, setDisplayName] = useState(OwnProfileStore.instance.displayName ?? ""); const [displayName, setDisplayName] = useState(OwnProfileStore.instance.displayName ?? "");
const [avatarError, setAvatarError] = useState<boolean>(false); const [avatarError, setAvatarError] = useState<boolean>(false);
@ -192,6 +219,11 @@ const UserProfileSettings: React.FC<UserProfileSettingsProps> = ({ canSetDisplay
</Alert> </Alert>
)} )}
{userIdentifier && <UsernameBox username={userIdentifier} />} {userIdentifier && <UsernameBox username={userIdentifier} />}
{externalAccountManagementUrl && (
<div className="mx_UserProfileSettings_profile_buttons">
<ManageAccountButton externalAccountManagementUrl={externalAccountManagementUrl} />
</div>
)}
</div> </div>
); );
}; };

View file

@ -215,33 +215,6 @@ 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 ( return (
<> <>
<SettingsSubsection <SettingsSubsection
@ -249,7 +222,6 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
stretchContent stretchContent
data-testid="accountSection" data-testid="accountSection"
> >
{externalAccountManagement}
{passwordChangeSection} {passwordChangeSection}
</SettingsSubsection> </SettingsSubsection>
</> </>
@ -324,6 +296,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
<SettingsTab data-testid="mx_GeneralUserSettingsTab"> <SettingsTab data-testid="mx_GeneralUserSettingsTab">
<SettingsSection> <SettingsSection>
<UserProfileSettings <UserProfileSettings
externalAccountManagementUrl={this.state.externalAccountManagementUrl}
canSetDisplayName={this.state.canSetDisplayName} canSetDisplayName={this.state.canSetDisplayName}
canSetAvatar={this.state.canSetAvatar} canSetAvatar={this.state.canSetAvatar}
/> />

View file

@ -2511,7 +2511,6 @@
"error_revoke_msisdn_discovery": "Unable to revoke sharing for phone number", "error_revoke_msisdn_discovery": "Unable to revoke sharing for phone number",
"error_share_email_discovery": "Unable to share email address", "error_share_email_discovery": "Unable to share email address",
"error_share_msisdn_discovery": "Unable to share phone number", "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_no_token": "No identity access token found",
"identity_server_not_set": "Identity server not set", "identity_server_not_set": "Identity server not set",
"incorrect_msisdn_verification": "Incorrect verification code", "incorrect_msisdn_verification": "Incorrect verification code",

View file

@ -92,13 +92,10 @@ describe("<GeneralUserSettingsTab />", () => {
} as unknown as OidcClientStore; } as unknown as OidcClientStore;
jest.spyOn(stores, "oidcClientStore", "get").mockReturnValue(mockOidcClientStore); jest.spyOn(stores, "oidcClientStore", "get").mockReturnValue(mockOidcClientStore);
const { getByTestId } = render(getComponent()); render(getComponent());
// wait for well-known call to settle const manageAccountLink = await screen.findByRole("button", { name: "Manage account" });
await flushPromises(); expect(manageAccountLink.getAttribute("href")).toMatch(accountManagementLink);
expect(getByTestId("external-account-management-outer").textContent).toMatch(/.*id\.server\.org/);
expect(getByTestId("external-account-management-link").getAttribute("href")).toMatch(accountManagementLink);
}); });
describe("Manage integrations", () => { describe("Manage integrations", () => {