Skip to content

Extract ResetIdentityBody into a separate object to allow re-using it #29700

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 14, 2025
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
124 changes: 124 additions & 0 deletions src/components/views/settings/encryption/ResetIdentityBody.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* Copyright 2024-2025 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 { Button, InlineSpinner, VisualList, VisualListItem } from "@vector-im/compound-web";
import CheckIcon from "@vector-im/compound-design-tokens/assets/web/icons/check";
import InfoIcon from "@vector-im/compound-design-tokens/assets/web/icons/info";
import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
import React, { type JSX, useState, type MouseEventHandler } from "react";

import { _t } from "../../../../languageHandler";
import { EncryptionCard } from "./EncryptionCard";
import { uiAuthCallback } from "../../../../CreateCrossSigning";
import { EncryptionCardButtons } from "./EncryptionCardButtons";
import { EncryptionCardEmphasisedContent } from "./EncryptionCardEmphasisedContent";
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";

interface ResetIdentityBodyProps {
/**
* Called when the identity is reset.
*/
onFinish: MouseEventHandler<HTMLButtonElement>;
/**
* Called when the cancel button is clicked.
*/
onCancelClick: () => void;

/**
* The variant of the panel to show. We show more warnings in the 'compromised' variant (no use in showing a user
* this warning if they have to reset because they no longer have their key)
*/
variant: ResetIdentityBodyVariant;
}

/**
* "compromised" is shown when the user chooses 'reset' explicitly in settings, usually because they believe their
* identity has been compromised.
*
* "sync_failed" is shown when the user tried to recover their identity but the process failed, probably because
* the required information is missing from recovery.
*
* "forgot" is shown when the user has just forgotten their passphrase.
*/
export type ResetIdentityBodyVariant = "compromised" | "forgot" | "sync_failed";

/**
* User interface component allowing the user to reset their cryptographic identity.
*
* Used by {@link ResetIdentityPanel}.
*/
export function ResetIdentityBody({ onCancelClick, onFinish, variant }: ResetIdentityBodyProps): JSX.Element {
const matrixClient = useMatrixClientContext();

// After the user clicks "Continue", we disable the button so it can't be
// clicked again, and warn the user not to close the window.
const [inProgress, setInProgress] = useState(false);

return (
<EncryptionCard Icon={ErrorIcon} destructive={true} title={titleForVariant(variant)}>
<EncryptionCardEmphasisedContent>
<VisualList>
<VisualListItem Icon={CheckIcon} success={true}>
{_t("settings|encryption|advanced|breadcrumb_first_description")}
</VisualListItem>
<VisualListItem Icon={InfoIcon}>
{_t("settings|encryption|advanced|breadcrumb_second_description")}
</VisualListItem>
<VisualListItem Icon={InfoIcon}>
{_t("settings|encryption|advanced|breadcrumb_third_description")}
</VisualListItem>
</VisualList>
{variant === "compromised" && <span>{_t("settings|encryption|advanced|breadcrumb_warning")}</span>}
</EncryptionCardEmphasisedContent>
<EncryptionCardButtons>
<Button
destructive={true}
disabled={inProgress}
onClick={async (evt) => {
setInProgress(true);
await matrixClient
.getCrypto()
?.resetEncryption((makeRequest) => uiAuthCallback(matrixClient, makeRequest));
onFinish(evt);
}}
>
{inProgress ? (
<>
<InlineSpinner /> {_t("settings|encryption|advanced|reset_in_progress")}
</>
) : (
_t("action|continue")
)}
</Button>
{inProgress ? (
<EncryptionCardEmphasisedContent>
<span className="mx_ResetIdentityPanel_warning">
{_t("settings|encryption|advanced|do_not_close_warning")}
</span>
</EncryptionCardEmphasisedContent>
) : (
<Button kind="tertiary" onClick={onCancelClick}>
{_t("action|cancel")}
</Button>
)}
</EncryptionCardButtons>
</EncryptionCard>
);
}

function titleForVariant(variant: ResetIdentityBodyVariant): string {
switch (variant) {
case "compromised":
return _t("settings|encryption|advanced|breadcrumb_title");
case "sync_failed":
return _t("settings|encryption|advanced|breadcrumb_title_sync_failed");

default:
case "forgot":
return _t("settings|encryption|advanced|breadcrumb_title_forgot");
}
}
99 changes: 8 additions & 91 deletions src/components/views/settings/encryption/ResetIdentityPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,11 @@
* Please see LICENSE files in the repository root for full details.
*/

import { Breadcrumb, Button, InlineSpinner, VisualList, VisualListItem } from "@vector-im/compound-web";
import CheckIcon from "@vector-im/compound-design-tokens/assets/web/icons/check";
import InfoIcon from "@vector-im/compound-design-tokens/assets/web/icons/info";
import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
import React, { type JSX, useState, type MouseEventHandler } from "react";
import { Breadcrumb } from "@vector-im/compound-web";
import React, { type JSX, type MouseEventHandler } from "react";

import { _t } from "../../../../languageHandler";
import { EncryptionCard } from "./EncryptionCard";
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
import { uiAuthCallback } from "../../../../CreateCrossSigning";
import { EncryptionCardButtons } from "./EncryptionCardButtons";
import { EncryptionCardEmphasisedContent } from "./EncryptionCardEmphasisedContent";
import { ResetIdentityBody, type ResetIdentityBodyVariant } from "./ResetIdentityBody";

interface ResetIdentityPanelProps {
/**
Expand All @@ -29,33 +22,17 @@ interface ResetIdentityPanelProps {
onCancelClick: () => void;

/**
* The variant of the panel to show. We show more warnings in the 'compromised' variant (no use in showing a user
* this warning if they have to reset because they no longer have their key)
* Which variant of this panel to show.
*/
variant: ResetIdentityPanelVariant;
variant: ResetIdentityBodyVariant;
}

/**
* "compromised" is shown when the user chooses 'reset' explicitly in settings, usually because they believe their
* identity has been compromised.
* The Encryption Settings panel for resetting the identity of the current user.
*
* "sync_failed" is shown when the user tried to recover their identity but the process failed, probably because
* the required information is missing from recovery.
*
* "forgot" is shown when the user has just forgotten their passphrase.
*/
export type ResetIdentityPanelVariant = "compromised" | "forgot" | "sync_failed";

/**
* The panel for resetting the identity of the current user.
* A thin wrapper around {@link ResetIdentityBody}, just adding breadcrumbs.
*/
export function ResetIdentityPanel({ onCancelClick, onFinish, variant }: ResetIdentityPanelProps): JSX.Element {
const matrixClient = useMatrixClientContext();

// After the user clicks "Continue", we disable the button so it can't be
// clicked again, and warn the user not to close the window.
const [inProgress, setInProgress] = useState(false);

return (
<>
<Breadcrumb
Expand All @@ -64,67 +41,7 @@ export function ResetIdentityPanel({ onCancelClick, onFinish, variant }: ResetId
pages={[_t("settings|encryption|title"), _t("settings|encryption|advanced|breadcrumb_page")]}
onPageClick={onCancelClick}
/>
<EncryptionCard Icon={ErrorIcon} destructive={true} title={titleForVariant(variant)}>
<EncryptionCardEmphasisedContent>
<VisualList>
<VisualListItem Icon={CheckIcon} success={true}>
{_t("settings|encryption|advanced|breadcrumb_first_description")}
</VisualListItem>
<VisualListItem Icon={InfoIcon}>
{_t("settings|encryption|advanced|breadcrumb_second_description")}
</VisualListItem>
<VisualListItem Icon={InfoIcon}>
{_t("settings|encryption|advanced|breadcrumb_third_description")}
</VisualListItem>
</VisualList>
{variant === "compromised" && <span>{_t("settings|encryption|advanced|breadcrumb_warning")}</span>}
</EncryptionCardEmphasisedContent>
<EncryptionCardButtons>
<Button
destructive={true}
disabled={inProgress}
onClick={async (evt) => {
setInProgress(true);
await matrixClient
.getCrypto()
?.resetEncryption((makeRequest) => uiAuthCallback(matrixClient, makeRequest));
onFinish(evt);
}}
>
{inProgress ? (
<>
<InlineSpinner /> {_t("settings|encryption|advanced|reset_in_progress")}
</>
) : (
_t("action|continue")
)}
</Button>
{inProgress ? (
<EncryptionCardEmphasisedContent>
<span className="mx_ResetIdentityPanel_warning">
{_t("settings|encryption|advanced|do_not_close_warning")}
</span>
</EncryptionCardEmphasisedContent>
) : (
<Button kind="tertiary" onClick={onCancelClick}>
{_t("action|cancel")}
</Button>
)}
</EncryptionCardButtons>
</EncryptionCard>
<ResetIdentityBody onFinish={onFinish} onCancelClick={onCancelClick} variant={variant} />
</>
);
}

function titleForVariant(variant: ResetIdentityPanelVariant): string {
switch (variant) {
case "compromised":
return _t("settings|encryption|advanced|breadcrumb_title");
case "sync_failed":
return _t("settings|encryption|advanced|breadcrumb_title_sync_failed");

default:
case "forgot":
return _t("settings|encryption|advanced|breadcrumb_title_forgot");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import SetupEncryptionDialog from "../../../dialogs/security/SetupEncryptionDial
import { SettingsSection } from "../../shared/SettingsSection";
import { SettingsSubheader } from "../../SettingsSubheader";
import { AdvancedPanel } from "../../encryption/AdvancedPanel";
import { ResetIdentityPanel, type ResetIdentityPanelVariant } from "../../encryption/ResetIdentityPanel";
import { ResetIdentityPanel } from "../../encryption/ResetIdentityPanel";
import { type ResetIdentityBodyVariant } from "../../encryption/ResetIdentityBody";
import { RecoveryPanelOutOfSync } from "../../encryption/RecoveryPanelOutOfSync";
import { useTypedEventEmitter } from "../../../../../hooks/useEventEmitter";
import { KeyStoragePanel } from "../../encryption/KeyStoragePanel";
Expand Down Expand Up @@ -147,7 +148,7 @@ export function EncryptionUserSettingsTab({ initialState = "loading" }: Props):
* Given what state we want the tab to be in, what variant of the
* ResetIdentityPanel do we need?
*/
function findResetVariant(state: State): ResetIdentityPanelVariant {
function findResetVariant(state: State): ResetIdentityBodyVariant {
switch (state) {
case "reset_identity_compromised":
return "compromised";
Expand Down
Loading