Skip to content

Commit

Permalink
WIP recovery section
Browse files Browse the repository at this point in the history
  • Loading branch information
florianduros committed Dec 10, 2024
1 parent 8747e62 commit 2710462
Show file tree
Hide file tree
Showing 10 changed files with 310 additions and 9 deletions.
22 changes: 15 additions & 7 deletions res/css/_common.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,9 @@ legend {
.mx_Dialog
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
.mx_UserProfileSettings button
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button),
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
.mx_EncryptionUserSettingsTab button
),
.mx_Dialog input[type="submit"],
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton),
.mx_Dialog_buttons input[type="submit"] {
Expand All @@ -616,16 +618,18 @@ legend {
.mx_Dialog
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
.mx_UserProfileSettings button
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(
.mx_ShareDialog button
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
.mx_EncryptionUserSettingsTab button
):last-child {
margin-right: 0px;
}

.mx_Dialog
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
.mx_UserProfileSettings button
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):focus,
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
.mx_EncryptionUserSettingsTab button
):focus,
.mx_Dialog input[type="submit"]:focus,
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):focus,
.mx_Dialog_buttons input[type="submit"]:focus {
Expand All @@ -637,7 +641,9 @@ legend {
.mx_Dialog_buttons
button.mx_Dialog_primary:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):not(
.mx_UserProfileSettings button
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button),
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
.mx_EncryptionUserSettingsTab button
),
.mx_Dialog_buttons input[type="submit"].mx_Dialog_primary {
color: var(--cpd-color-text-on-solid-primary);
background-color: var(--cpd-color-bg-action-primary-rest);
Expand All @@ -650,7 +656,7 @@ legend {
.mx_Dialog_buttons
button.danger:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):not(.mx_UserProfileSettings button):not(
.mx_ThemeChoicePanel_CustomTheme button
):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button),
):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(.mx_EncryptionUserSettingsTab button),
.mx_Dialog_buttons input[type="submit"].danger {
background-color: var(--cpd-color-bg-critical-primary);
border: solid 1px var(--cpd-color-bg-critical-primary);
Expand All @@ -666,7 +672,9 @@ legend {
.mx_Dialog
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
.mx_UserProfileSettings button
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):disabled,
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
.mx_EncryptionUserSettingsTab button
):disabled,
.mx_Dialog input[type="submit"]:disabled,
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):disabled,
.mx_Dialog_buttons input[type="submit"]:disabled {
Expand Down
3 changes: 3 additions & 0 deletions res/css/_components.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,9 @@
@import "./views/settings/_ThemeChoicePanel.pcss";
@import "./views/settings/_UpdateCheckButton.pcss";
@import "./views/settings/_UserProfileSettings.pcss";
@import "./views/settings/encryption/_ChangeRecoveryKey.pcss";
@import "./views/settings/encryption/_RecoveryPanel.pcss";
@import "./views/settings/encryption/_EncryptionCard.pcss";
@import "./views/settings/tabs/_SettingsBanner.pcss";
@import "./views/settings/tabs/_SettingsIndent.pcss";
@import "./views/settings/tabs/_SettingsSection.pcss";
Expand Down
63 changes: 63 additions & 0 deletions res/css/views/settings/encryption/_ChangeRecoveryKey.pcss
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
* Please see LICENSE files in the repository root for full details.
*/

.mx_ChangeRecoveryKey_header {
display: flex;
flex-direction: column;
gap: var(--cpd-space-4x);
align-items: center;

> h2 {
margin: 0;
}

> span {
color: var(--cpd-color-text-secondary);
text-align: center;
}
}

.mx_ChangeRecoveryKey_content {
display: grid;
grid-template:
"header button" auto
"content button" auto / 1fr 36px;
column-gap: var(--cpd-space-3x);
row-gap: var(--cpd-space-1x);
align-items: center;

> span {
grid-area: header;
}

> div {
grid-area: content;
display: flex;
flex-direction: column;
gap: var(--cpd-space-2x);
color: var(--cpd-color-text-secondary);

> code {
background-color: var(--cpd-color-bg-subtle-secondary);
border-radius: var(--cpd-space-2x);
padding: var(--cpd-space-3x) var(--cpd-space-4x);
}
}

> button {
margin: 0 var(--cpd-space-1x);
color: var(--cpd-color-bg-subtle-secondary);
grid-area: button;
}
}

.mx_ChangeRecoveryKey_action {
display: flex;
flex-direction: column;
gap: var(--cpd-space-4x);
justify-content: center;
}
17 changes: 17 additions & 0 deletions res/css/views/settings/encryption/_EncryptionCard.pcss
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
* Please see LICENSE files in the repository root for full details.
*/

.mx_EncryptionCard {
display: flex;
flex-direction: column;
gap: var(--cpd-space-8x);
padding: var(--cpd-space-10x);
border-radius: var(--cpd-space-4x);
/* From figma */
box-shadow: 0 1.2px 2.4px 0 rgba(27, 29, 34, 0.15);
border: 1px solid var(--cpd-color-gray-400);
}
22 changes: 22 additions & 0 deletions res/css/views/settings/encryption/_RecoveryPanel.pcss
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
* Please see LICENSE files in the repository root for full details.
*/

.mx_RecoveryPanel {
.mx_RecoveryPanel_Subheader {
display: flex;
flex-direction: column;
gap: var(--cpd-space-2x);

> span {
display: flex;
align-items: center;
gap: var(--cpd-space-2x);
font: var(--cpd-font-body-sm-medium);
color: var(--cpd-color-text-success-primary);
}
}
}
60 changes: 60 additions & 0 deletions src/components/views/settings/encryption/ChangeRecoveryKey.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
* Please see LICENSE files in the repository root for full details.
*/

import React, { JSX } from "react";
import { Breadcrumb, Heading, BigIcon, IconButton, Text, Button } from "@vector-im/compound-web";

Check failure on line 9 in src/components/views/settings/encryption/ChangeRecoveryKey.tsx

View workflow job for this annotation

GitHub Actions / Typescript Syntax Check

Module '"@vector-im/compound-web"' has no exported member 'Breadcrumb'.

Check failure on line 9 in src/components/views/settings/encryption/ChangeRecoveryKey.tsx

View workflow job for this annotation

GitHub Actions / Typescript Syntax Check

Module '"@vector-im/compound-web"' has no exported member 'BigIcon'.
import KeyIcon from "@vector-im/compound-design-tokens/assets/web/icons/key-solid";
import CopyIcon from "@vector-im/compound-design-tokens/assets/web/icons/copy";

import { _t } from "../../../../languageHandler.tsx";
import { EncryptionCard } from "./EncryptionCard.tsx";

interface ChangeRecoveryKeyProps {
onBackClick: () => void;
}

export function ChangeRecoveryKey({ onBackClick }: ChangeRecoveryKeyProps): JSX.Element {
return (
<>
<Breadcrumb
backLabel={_t("action|back")}
onBackClick={onBackClick}
pages={[_t("settings|encryption|title"), _t("settings|encryption|recovery|change_recovery_key")]}
onPageClick={onBackClick}
/>
<EncryptionCard>
<div className="mx_ChangeRecoveryKey_header">
<BigIcon>
<KeyIcon />
</BigIcon>
<Heading as="h2" size="sm" weight="semibold">
{_t("settings|encryption|recovery|change_recovery_key_title")}
</Heading>
<span>{_t("settings|encryption|recovery|change_recovery_key_description")}</span>
</div>
<div className="mx_ChangeRecoveryKey_content">
<Text as="span" weight="medium">
{_t("settings|encryption|recovery|change_recovery_key_content_title")}
</Text>
<div>
<code>Text text text text text text text text text text text text text text text </code>
<Text as="span" size="sm">
{_t("settings|encryption|recovery|change_recovery_key_content_description")}
</Text>
</div>
<IconButton size="28px">
<CopyIcon />
</IconButton>
</div>
<div className="mx_ChangeRecoveryKey_action">
<Button>{_t("action|continue")}</Button>
<Button kind="tertiary">{_t("action|cancel")}</Button>
</div>
</EncryptionCard>
</>
);
}
15 changes: 15 additions & 0 deletions src/components/views/settings/encryption/EncryptionCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
* Please see LICENSE files in the repository root for full details.
*/

import React, { JSX, PropsWithChildren } from "react";

/**
* A styled card for encryption settings.
*/
export function EncryptionCard({ children }: PropsWithChildren): JSX.Element {
return <div className="mx_EncryptionCard">{children}</div>;
}
89 changes: 89 additions & 0 deletions src/components/views/settings/encryption/RecoveryPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
* Please see LICENSE files in the repository root for full details.
*/

import React, { JSX, MouseEventHandler, useEffect, useState } from "react";
import { Button, InlineSpinner } from "@vector-im/compound-web";
import KeyIcon from "@vector-im/compound-design-tokens/assets/web/icons/key";
import CheckCircleIcon from "@vector-im/compound-design-tokens/assets/web/icons/check-circle-solid";

import { SettingsSection } from "../shared/SettingsSection";
import { _t } from "../../../../languageHandler";
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
import { SettingsHeader } from "../SettingsHeader";

interface RecoveryPanelProps {
onChangingRecoveryKeyClick: MouseEventHandler<HTMLButtonElement>;
}

/**
* This component allows the user to set up or change their recovery key.
*/
export function RecoveryPanel({ onChangingRecoveryKeyClick }: RecoveryPanelProps): JSX.Element {
const [hasRecoveryKey, setHasRecoveryKey] = useState<boolean | null>(null);
const isLoading = hasRecoveryKey === null;
const matrixClient = useMatrixClientContext();

useEffect(() => {
const getRecoveryKey = async (): Promise<void> =>
setHasRecoveryKey(Boolean(await matrixClient.getCrypto()?.getKeyBackupInfo()));
getRecoveryKey();
}, [matrixClient]);

return (
<SettingsSection
legacy={false}
heading={
<SettingsHeader
hasRecommendedTag={!isLoading && !hasRecoveryKey}
label={_t("settings|encryption|recovery|title")}
/>
}
subHeading={<Subheader hasRecoveryKey={hasRecoveryKey} />}
className="mx_RecoveryPanel"
>
{isLoading && <InlineSpinner />}
{!isLoading && (
<>
{hasRecoveryKey ? (
<Button size="sm" kind="secondary" Icon={KeyIcon} onClick={onChangingRecoveryKeyClick}>
{_t("settings|encryption|recovery|change_recovery_key")}
</Button>
) : (
<Button size="sm" kind="primary" Icon={KeyIcon}>
{_t("settings|encryption|recovery|set_up_recovery")}
</Button>
)}
</>
)}
</SettingsSection>
);
}

/**
* The subheader for the recovery panel.
*/
interface SubheaderProps {
/**
* Whether the user has a recovery key.
* If null, the recovery key is still fetching.
*/
hasRecoveryKey: boolean | null;
}

function Subheader({ hasRecoveryKey }: SubheaderProps): JSX.Element {
if (!hasRecoveryKey) return <>{_t("settings|encryption|recovery|description")}</>;

return (
<div className="mx_RecoveryPanel_Subheader">
{_t("settings|encryption|recovery|description")}
<span>
<CheckCircleIcon width="20" height="20" />
{_t("settings|encryption|recovery|key_active")}
</span>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,23 @@
* Please see LICENSE files in the repository root for full details.
*/

import React, { JSX } from "react";
import React, { JSX, useState } from "react";

import SettingsTab from "../SettingsTab";
import { RecoveryPanel } from "../../encryption/RecoveryPanel";
import { ChangeRecoveryKey } from "../../encryption/ChangeRecoveryKey.tsx";

type Panel = "main" | "change_recovery_key" | "set_recovery_key";

export function EncryptionUserSettingsTab(): JSX.Element {
return <SettingsTab />;
const [panel, setPanel] = useState<Panel>("main");
const displayChangeRecoveryKey = panel === "change_recovery_key";
const displayMain = panel === "main";

return (
<SettingsTab className="mx_EncryptionUserSettingsTab">
{displayChangeRecoveryKey && <ChangeRecoveryKey onBackClick={() => setPanel("main")} />}
{displayMain && <RecoveryPanel onChangingRecoveryKeyClick={() => setPanel("change_recovery_key")} />}
</SettingsTab>
);
}
11 changes: 11 additions & 0 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -2464,6 +2464,17 @@
"enable_markdown_description": "Start messages with <code>/plain</code> to send without markdown.",
"encryption": {
"dialog_title": "<strong>Settings:</strong> Encryption",
"recovery": {
"change_recovery_key": "Change recovery key",
"change_recovery_key_content_description": "Do not share this with anyone!",
"change_recovery_key_content_title": "Recovery key",
"change_recovery_key_description": "Get a new recovery key if you've lost your existing one. After changing your recovery key, your old one will no longer work.",
"change_recovery_key_title": "Change recovery key?",
"description": "Recover your cryptographic identity and message history with a recovery key if you’ve lost all your existing devices.",
"key_active": "Recovery key active",
"set_up_recovery": "Set up recovery",
"title": "Recovery"
},
"title": "Encryption"
},
"general": {
Expand Down

0 comments on commit 2710462

Please sign in to comment.