Split out email & phone number settings to separate components & move discovery to privacy tab (#12670)

* WIP update of threepid settings section

* Remove email / phone number section from original place

and don't show the new one if 3pids are disabled

* Update snapshots

* Pull identity server / 3pid binding settings out to separate component

and put it in the security & privacy section which is its new home

* Update snapshot

* Move relevant part of test & update screenshots / snapshots

* Remove unnecessary dependency

* Add test for discovery settings

* Add spacing in terms agreement
This commit is contained in:
David Baker
2024-06-26 14:04:19 +01:00
committed by GitHub
parent 72475240ec
commit ea0baee101
13 changed files with 454 additions and 269 deletions

View File

@@ -0,0 +1,190 @@
/*
Copyright 2024 The Matrix.org Foundation C.I.C.
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.
*/
import React, { useCallback, useEffect, useState } from "react";
import { SERVICE_TYPES, ThreepidMedium } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import { Alert } from "@vector-im/compound-web";
import DiscoveryEmailAddresses from "../discovery/EmailAddresses";
import DiscoveryPhoneNumbers from "../discovery/PhoneNumbers";
import { getThreepidsWithBindStatus } from "../../../../boundThreepids";
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
import { ThirdPartyIdentifier } from "../../../../AddThreepid";
import SettingsStore from "../../../../settings/SettingsStore";
import { UIFeature } from "../../../../settings/UIFeature";
import { _t } from "../../../../languageHandler";
import SetIdServer from "../SetIdServer";
import SettingsSubsection from "../shared/SettingsSubsection";
import InlineTermsAgreement from "../../terms/InlineTermsAgreement";
import { Service, ServicePolicyPair, startTermsFlow } from "../../../../Terms";
import IdentityAuthClient from "../../../../IdentityAuthClient";
import { abbreviateUrl } from "../../../../utils/UrlUtils";
import { useDispatcher } from "../../../../hooks/useDispatcher";
import defaultDispatcher from "../../../../dispatcher/dispatcher";
import { ActionPayload } from "../../../../dispatcher/payloads";
type RequiredPolicyInfo =
| {
// This object is passed along to a component for handling
policiesAndServices: null; // From the startTermsFlow callback
agreedUrls: null; // From the startTermsFlow callback
resolve: null; // Promise resolve function for startTermsFlow callback
}
| {
policiesAndServices: ServicePolicyPair[];
agreedUrls: string[];
resolve: (values: string[]) => void;
};
/**
* Settings controlling how a user's email addreses and phone numbers can be used to discover them
*/
export const DiscoverySettings: React.FC = () => {
const client = useMatrixClientContext();
const [emails, setEmails] = useState<ThirdPartyIdentifier[]>([]);
const [phoneNumbers, setPhoneNumbers] = useState<ThirdPartyIdentifier[]>([]);
const [loadingState, setLoadingState] = useState<"loading" | "loaded" | "error">("loading");
const [idServerName, setIdServerName] = useState<string | undefined>(abbreviateUrl(client.getIdentityServerUrl()));
const [canMake3pidChanges, setCanMake3pidChanges] = useState<boolean>(false);
const [requiredPolicyInfo, setRequiredPolicyInfo] = useState<RequiredPolicyInfo>({
// This object is passed along to a component for handling
policiesAndServices: null, // From the startTermsFlow callback
agreedUrls: null, // From the startTermsFlow callback
resolve: null, // Promise resolve function for startTermsFlow callback
});
const [hasTerms, setHasTerms] = useState<boolean>(false);
const getThreepidState = useCallback(async () => {
const threepids = await getThreepidsWithBindStatus(client);
setEmails(threepids.filter((a) => a.medium === ThreepidMedium.Email));
setPhoneNumbers(threepids.filter((a) => a.medium === ThreepidMedium.Phone));
}, [client]);
useDispatcher(
defaultDispatcher,
useCallback(
(payload: ActionPayload) => {
if (payload.action === "id_server_changed") {
setIdServerName(abbreviateUrl(client.getIdentityServerUrl()));
getThreepidState().then();
}
},
[client, getThreepidState],
),
);
useEffect(() => {
(async () => {
try {
await getThreepidState();
const capabilities = await client.getCapabilities();
setCanMake3pidChanges(
!capabilities["m.3pid_changes"] || capabilities["m.3pid_changes"].enabled === true,
);
// By starting the terms flow we get the logic for checking which terms the user has signed
// for free. So we might as well use that for our own purposes.
const idServerUrl = client.getIdentityServerUrl();
if (!idServerUrl) {
return;
}
const authClient = new IdentityAuthClient();
try {
const idAccessToken = await authClient.getAccessToken({ check: false });
await startTermsFlow(
client,
[new Service(SERVICE_TYPES.IS, idServerUrl, idAccessToken!)],
(policiesAndServices, agreedUrls, extraClassNames) => {
return new Promise((resolve) => {
setIdServerName(abbreviateUrl(idServerUrl));
setHasTerms(true);
setRequiredPolicyInfo({
policiesAndServices,
agreedUrls,
resolve,
});
});
},
);
// User accepted all terms
setHasTerms(false);
} catch (e) {
logger.warn(
`Unable to reach identity server at ${idServerUrl} to check ` + `for terms in Settings`,
);
logger.warn(e);
}
setLoadingState("loaded");
} catch (e) {
setLoadingState("error");
}
})();
}, [client, getThreepidState]);
if (!SettingsStore.getValue(UIFeature.ThirdPartyID)) return null;
if (hasTerms && requiredPolicyInfo.policiesAndServices) {
const intro = (
<Alert type="info" title={_t("settings|general|discovery_needs_terms_title")}>
{_t("settings|general|discovery_needs_terms", { serverName: idServerName })}
</Alert>
);
return (
<>
<InlineTermsAgreement
policiesAndServicePairs={requiredPolicyInfo.policiesAndServices}
agreedUrls={requiredPolicyInfo.agreedUrls}
onFinished={requiredPolicyInfo.resolve}
introElement={intro}
/>
{/* has its own heading as it includes the current identity server */}
<SetIdServer missingTerms={true} />
</>
);
}
const threepidSection = idServerName ? (
<>
<DiscoveryEmailAddresses
emails={emails}
isLoading={loadingState === "loading"}
disabled={!canMake3pidChanges}
/>
<DiscoveryPhoneNumbers
msisdns={phoneNumbers}
isLoading={loadingState === "loading"}
disabled={!canMake3pidChanges}
/>
</>
) : null;
return (
<SettingsSubsection heading={_t("settings|discovery|title")} data-testid="discoverySection">
{threepidSection}
{/* has its own heading as it includes the current identity server */}
<SetIdServer missingTerms={false} />
</SettingsSubsection>
);
};
export default DiscoverySettings;