Skip to content
This repository was archived by the owner on Sep 10, 2024. It is now read-only.

Commit 56fcd0d

Browse files
committed
Redesign cross signing reset unlock flow
1 parent 6c4c7ad commit 56fcd0d

File tree

3 files changed

+153
-53
lines changed

3 files changed

+153
-53
lines changed

frontend/locales/en.json

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -185,17 +185,28 @@
185185
}
186186
},
187187
"reset_cross_signing": {
188-
"button": "Allow crypto identity reset",
189-
"description": "If you are not signed in anywhere else, and have forgotten or lost all recovery options youll need to reset your crypto identity. This means you will lose your existing message history, other users will see that you have reset your identity and you will need to verify your existing devices again.",
188+
"button": "Reset identity",
189+
"description": "If you're not signed in to any other devices and you've lost your recovery key, then you'll need to reset your identity to continue using the app.",
190190
"failure": {
191-
"description": "This might be a temporary problem, so please try again later. If the problem persists, please contact your server administrator.",
192-
"title": "Failed to allow crypto identity"
191+
"heading": "Failed to allow crypto identity reset",
192+
"description": "This might be a temporary problem, so please try again later. If the problem persists, please contact your server administrator."
193193
},
194-
"heading": "Reset crypto identity",
194+
"heading": "Reset your identity in case you can't confirm another way",
195195
"success": {
196-
"description": "A client can now temporarily reset your account crypto identity. Follow the instructions in your client to complete the process.",
197-
"title": "Crypto identity reset temporarily allowed"
198-
}
196+
"heading": "Identity reset successfully. Go back to the app to finish the process.",
197+
"description": "The identity reset has been approved for the next {{minutes}} minutes. You can close this window and go back to the app to continue."
198+
},
199+
"cancelled": {
200+
"heading": "Identity reset cancelled.",
201+
"description_1": "You can close this window and go back to the app to continue.",
202+
"description_2": "If you're signed out everywhere and don't remember your recovery code, you'll still need to reset your identity."
203+
},
204+
"effect_list": {
205+
"positive_1": "Your account details, contacts, preferences, and chat list will be kept",
206+
"negative_1": "You will lose your existing message history",
207+
"negative_2": "You will need to verify all your existing devices and contacts again"
208+
},
209+
"warning": "Only reset your identity if you don't have access to another signed-in device and you've lost your recovery key."
199210
},
200211
"session": {
201212
"client_id_label": "Client ID",

frontend/src/components/PageHeading/PageHeading.module.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
text-align: center;
6464

6565
& .title {
66-
font: var(--cpd-font-heading-lg-semibold);
66+
font: var(--cpd-font-heading-md-semibold);
6767
letter-spacing: var(--cpd-font-letter-spacing-heading-xl);
6868
color: var(--cpd-color-text-primary);
6969
text-wrap: balance;

frontend/src/routes/reset-cross-signing.lazy.tsx

Lines changed: 133 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,19 @@
1313
// limitations under the License.
1414

1515
import { createLazyFileRoute, notFound } from "@tanstack/react-router";
16-
import IconArrowLeft from "@vector-im/compound-design-tokens/assets/web/icons/arrow-left";
17-
import IconKey from "@vector-im/compound-design-tokens/assets/web/icons/key";
18-
import { Alert, Button, Text } from "@vector-im/compound-web";
16+
import IconCheck from "@vector-im/compound-design-tokens/assets/web/icons/check";
17+
import IconCheckCircleSolid from "@vector-im/compound-design-tokens/assets/web/icons/check-circle-solid";
18+
import IconClose from "@vector-im/compound-design-tokens/assets/web/icons/close";
19+
import IconError from "@vector-im/compound-design-tokens/assets/web/icons/error";
20+
import IconKeyOffSolid from "@vector-im/compound-design-tokens/assets/web/icons/key-off-solid";
21+
import { Button, Text } from "@vector-im/compound-web";
22+
import {
23+
ForwardRefExoticComponent,
24+
RefAttributes,
25+
SVGProps,
26+
useState,
27+
MouseEvent,
28+
} from "react";
1929
import { useTranslation } from "react-i18next";
2030
import { useMutation, useQuery } from "urql";
2131

@@ -24,6 +34,10 @@ import { ButtonLink } from "../components/ButtonLink";
2434
import Layout from "../components/Layout";
2535
import LoadingSpinner from "../components/LoadingSpinner";
2636
import PageHeading from "../components/PageHeading";
37+
import {
38+
VisualList,
39+
VisualListItem,
40+
} from "../components/VisualList/VisualList";
2741
import { graphql } from "../gql";
2842

2943
import { CURRENT_VIEWER_QUERY } from "./reset-cross-signing";
@@ -51,6 +65,10 @@ export const Route = createLazyFileRoute("/reset-cross-signing")({
5165
component: ResetCrossSigning,
5266
});
5367

68+
// This value comes from Synapse and we have no way to query it from here
69+
// https://github.com/element-hq/synapse/blob/34b758644611721911a223814a7b35d8e14067e6/synapse/rest/admin/users.py#L1335
70+
const CROSS_SIGNING_REPLACEMENT_PERIOD_MS = 10 * 60 * 1000; // 10 minutes
71+
5472
function ResetCrossSigning(): React.ReactNode {
5573
const { deepLink } = Route.useSearch();
5674
const { t } = useTranslation();
@@ -60,6 +78,8 @@ function ResetCrossSigning(): React.ReactNode {
6078
const userId = viewer.data.viewer.id;
6179

6280
const [result, allowReset] = useMutation(ALLOW_CROSS_SIGING_RESET_MUTATION);
81+
const success = !!result.data && !result.error;
82+
const error = !success && result.error;
6383

6484
const onClick = async (): Promise<void> => {
6585
await allowReset({ userId });
@@ -75,53 +95,122 @@ function ResetCrossSigning(): React.ReactNode {
7595
});
7696
};
7797

98+
const [cancelled, setCancelled] = useState(false);
99+
100+
let cancelButton;
101+
if (!deepLink) {
102+
cancelButton = (
103+
<ButtonLink to="/" kind="tertiary">
104+
{t("action.back")}
105+
</ButtonLink>
106+
);
107+
} else if (!success && !error && !cancelled) {
108+
// Only show the back button for a deep link if the user hasn't yet completed the interaction
109+
cancelButton = (
110+
<Button
111+
as="a"
112+
kind="tertiary"
113+
onClick={(ev: MouseEvent) => {
114+
ev.preventDefault();
115+
setCancelled(true);
116+
}}
117+
>
118+
{t("action.cancel")}
119+
</Button>
120+
);
121+
}
122+
123+
let Icon: ForwardRefExoticComponent<
124+
Omit<SVGProps<SVGSVGElement>, "ref" | "children"> &
125+
RefAttributes<SVGSVGElement>
126+
>;
127+
let title: string;
128+
let body: JSX.Element;
129+
130+
if (cancelled) {
131+
Icon = IconKeyOffSolid;
132+
title = t("frontend.reset_cross_signing.cancelled.heading");
133+
body = (
134+
<>
135+
<Text className="text-center text-secondary" size="lg">
136+
{t("frontend.reset_cross_signing.cancelled.description_1")}
137+
</Text>
138+
<Text className="text-center text-secondary" size="lg">
139+
{t("frontend.reset_cross_signing.cancelled.description_2")}
140+
</Text>
141+
</>
142+
);
143+
} else if (success) {
144+
Icon = IconCheckCircleSolid;
145+
title = t("frontend.reset_cross_signing.success.heading");
146+
body = (
147+
<Text className="text-center text-secondary" size="lg">
148+
{t("frontend.reset_cross_signing.success.description", {
149+
minutes: CROSS_SIGNING_REPLACEMENT_PERIOD_MS / (60 * 1000),
150+
})}
151+
</Text>
152+
);
153+
} else if (error) {
154+
Icon = IconError;
155+
title = t("frontend.reset_cross_signing.failure.heading");
156+
body = (
157+
<Text className="text-center text-secondary" size="lg">
158+
{t("frontend.reset_cross_signing.failure.description")}
159+
</Text>
160+
);
161+
} else {
162+
Icon = IconError;
163+
title = t("frontend.reset_cross_signing.heading");
164+
body = (
165+
<>
166+
<Text className="text-center text-secondary" size="lg">
167+
{t("frontend.reset_cross_signing.description")}
168+
</Text>
169+
<VisualList>
170+
<VisualListItem
171+
Icon={IconCheck}
172+
iconColor="var(--cpd-color-icon-success-primary)"
173+
label={t("frontend.reset_cross_signing.effect_list.positive_1")}
174+
/>
175+
<VisualListItem
176+
Icon={IconClose}
177+
iconColor="var(--cpd-color-icon-critical-primary)"
178+
label={t("frontend.reset_cross_signing.effect_list.negative_1")}
179+
/>
180+
<VisualListItem
181+
Icon={IconClose}
182+
iconColor="var(--cpd-color-icon-critical-primary)"
183+
label={t("frontend.reset_cross_signing.effect_list.negative_2")}
184+
/>
185+
</VisualList>
186+
<Text className="text-center" size="md" weight="semibold">
187+
{t("frontend.reset_cross_signing.warning")}
188+
</Text>
189+
<Button
190+
kind="primary"
191+
destructive
192+
disabled={result.fetching}
193+
onClick={onClick}
194+
>
195+
{!!result.fetching && <LoadingSpinner inline />}
196+
{t("frontend.reset_cross_signing.button")}
197+
</Button>
198+
</>
199+
);
200+
}
201+
78202
return (
79203
<Layout>
80204
<BlockList>
81205
<PageHeading
82-
Icon={IconKey}
83-
title={t("frontend.reset_cross_signing.heading")}
84-
invalid
206+
Icon={Icon}
207+
title={title}
208+
invalid={!success}
209+
success={success}
85210
/>
86211

87-
{!result.data && !result.error && (
88-
<>
89-
<Text className="text-justify">
90-
{t("frontend.reset_cross_signing.description")}
91-
</Text>
92-
<Button
93-
kind="primary"
94-
destructive
95-
disabled={result.fetching}
96-
onClick={onClick}
97-
>
98-
{!!result.fetching && <LoadingSpinner inline />}
99-
{t("frontend.reset_cross_signing.button")}
100-
</Button>
101-
</>
102-
)}
103-
{result.data && (
104-
<Alert
105-
type="info"
106-
title={t("frontend.reset_cross_signing.success.title")}
107-
>
108-
{t("frontend.reset_cross_signing.success.description")}
109-
</Alert>
110-
)}
111-
{result.error && (
112-
<Alert
113-
type="critical"
114-
title={t("frontend.reset_cross_signing.failure.title")}
115-
>
116-
{t("frontend.reset_cross_signing.failure.description")}
117-
</Alert>
118-
)}
119-
120-
{!deepLink && (
121-
<ButtonLink to="/" kind="tertiary" Icon={IconArrowLeft}>
122-
{t("action.back")}
123-
</ButtonLink>
124-
)}
212+
{body}
213+
{cancelButton}
125214
</BlockList>
126215
</Layout>
127216
);

0 commit comments

Comments
 (0)