Files
element-web/src/components/views/settings/encryption/RecoveryPanel.tsx
Florian Duros b7f8623617 Encryption tab: hide Advanced section when the key storage is out of sync (#29129)
* fix(encryption tab): hide the advanced section when the secrets are not cached locally

The secret verification is now made at the level of `EncryptionUserSettingsTab` instead at the `RecoveryPanel` level. In the `EncryptionUserSettingsTab`, we decide to only display `RecoveryPanelOutOfSync` in case of uncached secrets.

`RecoveryPanelOutOfSync` is simplified version of `RecoveryPanel` handling only the `secrets_not_cached` case.

* refactor(encryption tab): simplify the `RecoveryPanel` without having to handle the missing secrets

* test(encryption tab): move test about cached secrets in `EncryptionUserSettingsTab-test.tsx`

* test(encryption tab): move e2e test which are testing all the encryption tab in `encryption-tab.spec.ts

* refactor(encryption tab): move `RecoveryPanelOutOfSync` in its own file

- fix typos
- call onFinish after accessSecretStorage
- onFinish doesn't need to be asynchronous

* doc(encryption tab): improve documentation when the secrets are not cached locally

* test(encryption tab): improve test documentation and naming

* doc(encryption tab): improve `RecoveryPanelOutOfSync` documentation
2025-02-03 13:47:55 +00:00

89 lines
3.2 KiB
TypeScript

/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
import React, { JSX } from "react";
import { Button, InlineSpinner } from "@vector-im/compound-web";
import KeyIcon from "@vector-im/compound-design-tokens/assets/web/icons/key";
import { SettingsSection } from "../shared/SettingsSection";
import { _t } from "../../../../languageHandler";
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
import { SettingsHeader } from "../SettingsHeader";
import { useAsyncMemo } from "../../../../hooks/useAsyncMemo";
/**
* The possible states of the recovery panel.
* - `loading`: We are checking the recovery key and the secrets.
* - `missing_recovery_key`: The user has no recovery key.
* - `good`: The user has a recovery key and the secrets are cached.
*/
type State = "loading" | "missing_recovery_key" | "good";
interface RecoveryPanelProps {
/**
* Callback for when the user wants to set up or change their recovery key.
*
* @param setupNewKey - set if the user does not already have a recovery key (and has therefore clicked on
* "Set up recovery" rather than "Change recovery key").
*/
onChangeRecoveryKeyClick: (setupNewKey: boolean) => void;
}
/**
* This component allows the user to set up or change their recovery key.
*/
export function RecoveryPanel({ onChangeRecoveryKeyClick }: RecoveryPanelProps): JSX.Element {
const matrixClient = useMatrixClientContext();
const state = useAsyncMemo<State>(
async () => {
// Check if the user has a recovery key
const hasRecoveryKey = Boolean(await matrixClient.secretStorage.getDefaultKeyId());
if (hasRecoveryKey) return "good";
else return "missing_recovery_key";
},
[matrixClient],
"loading",
);
const isMissingRecoveryKey = state === "missing_recovery_key";
let content: JSX.Element;
switch (state) {
case "loading":
content = <InlineSpinner aria-label={_t("common|loading")} />;
break;
case "missing_recovery_key":
content = (
<Button size="sm" kind="primary" Icon={KeyIcon} onClick={() => onChangeRecoveryKeyClick(true)}>
{_t("settings|encryption|recovery|set_up_recovery")}
</Button>
);
break;
case "good":
content = (
<Button size="sm" kind="secondary" Icon={KeyIcon} onClick={() => onChangeRecoveryKeyClick(false)}>
{_t("settings|encryption|recovery|change_recovery_key")}
</Button>
);
}
return (
<SettingsSection
legacy={false}
heading={
<SettingsHeader
hasRecommendedTag={isMissingRecoveryKey}
label={_t("settings|encryption|recovery|title")}
/>
}
subHeading={_t("settings|encryption|recovery|description")}
data-testid="recoveryPanel"
>
{content}
</SettingsSection>
);
}