Add success dialog after key backup (#10177)

This commit is contained in:
Michael Weimann 2023-02-17 13:35:13 +01:00 committed by GitHub
parent a854e941cc
commit 9b267e7bc4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 144 additions and 2 deletions

View file

@ -183,6 +183,10 @@ describe("Cryptography", function () {
cy.contains(".mx_Dialog_primary:not([disabled])", "Continue").click(); cy.contains(".mx_Dialog_primary:not([disabled])", "Continue").click();
cy.contains(".mx_Dialog_title", "Setting up keys").should("exist"); cy.contains(".mx_Dialog_title", "Setting up keys").should("exist");
cy.contains(".mx_Dialog_title", "Setting up keys").should("not.exist"); cy.contains(".mx_Dialog_title", "Setting up keys").should("not.exist");
cy.contains("Secure Backup successful").should("exist");
cy.contains("Done").click();
cy.contains("Secure Backup successful").should("not.exist");
}); });
return; return;
}); });

View file

@ -181,12 +181,14 @@ describe("Decryption Failure Bar", () => {
cy.contains(".mx_DecryptionFailureBar_button", "Reset").click(); cy.contains(".mx_DecryptionFailureBar_button", "Reset").click();
// Set up key backup
cy.get(".mx_Dialog").within(() => { cy.get(".mx_Dialog").within(() => {
cy.contains(".mx_Dialog_primary", "Continue").click(); cy.contains(".mx_Dialog_primary", "Continue").click();
cy.get(".mx_CreateSecretStorageDialog_recoveryKey code").invoke("text").as("securityKey"); cy.get(".mx_CreateSecretStorageDialog_recoveryKey code").invoke("text").as("securityKey");
// Clicking download instead of Copy because of https://github.com/cypress-io/cypress/issues/2851 // Clicking download instead of Copy because of https://github.com/cypress-io/cypress/issues/2851
cy.contains(".mx_AccessibleButton", "Download").click(); cy.contains(".mx_AccessibleButton", "Download").click();
cy.contains(".mx_Dialog_primary:not([disabled])", "Continue").click(); cy.contains(".mx_Dialog_primary:not([disabled])", "Continue").click();
cy.contains("Done").click();
}); });
cy.get(".mx_DecryptionFailureBar .mx_DecryptionFailureBar_message_headline").should( cy.get(".mx_DecryptionFailureBar .mx_DecryptionFailureBar_message_headline").should(

View file

@ -50,6 +50,7 @@
@import "./components/views/spaces/_QuickThemeSwitcher.pcss"; @import "./components/views/spaces/_QuickThemeSwitcher.pcss";
@import "./components/views/typography/_Caption.pcss"; @import "./components/views/typography/_Caption.pcss";
@import "./compound/_Icon.pcss"; @import "./compound/_Icon.pcss";
@import "./compound/_SuccessDialog.pcss";
@import "./structures/_AutoHideScrollbar.pcss"; @import "./structures/_AutoHideScrollbar.pcss";
@import "./structures/_AutocompleteInput.pcss"; @import "./structures/_AutocompleteInput.pcss";
@import "./structures/_BackdropPanel.pcss"; @import "./structures/_BackdropPanel.pcss";

View file

@ -29,10 +29,22 @@ limitations under the License.
color: $accent; color: $accent;
} }
.mx_Icon_bg-accent-light {
background-color: rgba($accent, 0.1);
}
.mx_Icon_alert { .mx_Icon_alert {
color: $alert; color: $alert;
} }
.mx_Icon_circle-40 {
border-radius: 20px;
flex: 0 0 40px;
height: 40px;
padding: 0 12px;
width: 40px;
}
.mx_Icon_8 { .mx_Icon_8 {
flex: 0 0 8px; flex: 0 0 8px;
height: 8px; height: 8px;

View file

@ -0,0 +1,48 @@
/*
Copyright 2023 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.
*/
.mx_SuccessDialog {
text-align: center;
.mx_Icon {
mask-border: $spacing-16;
}
.mx_Dialog_header {
margin: 0 0 $spacing-16;
padding: 0;
}
.mx_Dialog_title {
margin: 0;
}
.mx_Dialog_content {
color: $secondary-content;
margin: 0 0 $spacing-40;
}
.mx_Dialog_buttons {
.mx_Dialog_buttons_row {
justify-content: center;
button.mx_Dialog_primary {
height: 48px;
min-width: 328px;
}
}
}
}

View file

@ -23,6 +23,14 @@ limitations under the License.
/* never asked. */ /* never asked. */
width: 560px; width: 560px;
&.mx_SuccessDialog {
padding: 56px; /* 80px from design - 24px wrapper padding */
.mx_Dialog_title {
margin-bottom: $spacing-16;
}
}
.mx_SettingsFlag { .mx_SettingsFlag {
display: flex; display: flex;
} }

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="18.86364"
height="14.318086"
viewBox="0 0 18.86364 14.318086"
fill="none"
version="1.1"
id="svg4"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<path
d="M 1.25,8.03668 6.13636,13.06814 17.61364,1.25"
stroke="currentColor"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
id="path2" />
</svg>

After

Width:  |  Height:  |  Size: 488 B

View file

@ -23,6 +23,7 @@ import { TrustInfo } from "matrix-js-sdk/src/crypto/backup";
import { CrossSigningKeys, UIAFlow } from "matrix-js-sdk/src/matrix"; import { CrossSigningKeys, UIAFlow } from "matrix-js-sdk/src/matrix";
import { IRecoveryKey } from "matrix-js-sdk/src/crypto/api"; import { IRecoveryKey } from "matrix-js-sdk/src/crypto/api";
import { CryptoEvent } from "matrix-js-sdk/src/crypto"; import { CryptoEvent } from "matrix-js-sdk/src/crypto";
import classNames from "classnames";
import { MatrixClientPeg } from "../../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../../MatrixClientPeg";
import { _t, _td } from "../../../../languageHandler"; import { _t, _td } from "../../../../languageHandler";
@ -48,6 +49,7 @@ import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
import Spinner from "../../../../components/views/elements/Spinner"; import Spinner from "../../../../components/views/elements/Spinner";
import InteractiveAuthDialog from "../../../../components/views/dialogs/InteractiveAuthDialog"; import InteractiveAuthDialog from "../../../../components/views/dialogs/InteractiveAuthDialog";
import { IValidationResult } from "../../../../components/views/elements/Validation"; import { IValidationResult } from "../../../../components/views/elements/Validation";
import { Icon as CheckmarkIcon } from "../../../../../res/img/element-icons/check.svg";
// I made a mistake while converting this and it has to be fixed! // I made a mistake while converting this and it has to be fixed!
enum Phase { enum Phase {
@ -59,6 +61,7 @@ enum Phase {
PassphraseConfirm = "passphrase_confirm", PassphraseConfirm = "passphrase_confirm",
ShowKey = "show_key", ShowKey = "show_key",
Storing = "storing", Storing = "storing",
Stored = "stored",
ConfirmSkip = "confirm_skip", ConfirmSkip = "confirm_skip",
} }
@ -361,7 +364,10 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
}, },
}); });
} }
this.props.onFinished(true);
this.setState({
phase: Phase.Stored,
});
} catch (e) { } catch (e) {
if (this.state.canUploadKeysWithPasswordOnly && e.httpStatus === 401 && e.data.flows) { if (this.state.canUploadKeysWithPasswordOnly && e.httpStatus === 401 && e.data.flows) {
this.setState({ this.setState({
@ -785,6 +791,19 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
); );
} }
private renderStoredPhase(): JSX.Element {
return (
<>
<p className="mx_Dialog_content">{_t("Your keys are now being backed up from this device.")}</p>
<DialogButtons
primaryButton={_t("Done")}
onPrimaryButtonClick={() => this.props.onFinished(true)}
hasCancel={false}
/>
</>
);
}
private renderPhaseLoadError(): JSX.Element { private renderPhaseLoadError(): JSX.Element {
return ( return (
<div> <div>
@ -837,11 +856,27 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
return _t("Save your Security Key"); return _t("Save your Security Key");
case Phase.Storing: case Phase.Storing:
return _t("Setting up keys"); return _t("Setting up keys");
case Phase.Stored:
return _t("Secure Backup successful");
default: default:
return ""; return "";
} }
} }
private get topComponent(): React.ReactNode | null {
if (this.state.phase === Phase.Stored) {
return <CheckmarkIcon className="mx_Icon mx_Icon_circle-40 mx_Icon_accent mx_Icon_bg-accent-light" />;
}
return null;
}
private get classNames(): string {
return classNames("mx_CreateSecretStorageDialog", {
mx_SuccessDialog: this.state.phase === Phase.Stored,
});
}
public render(): React.ReactNode { public render(): React.ReactNode {
let content; let content;
if (this.state.error) { if (this.state.error) {
@ -884,6 +919,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
case Phase.Storing: case Phase.Storing:
content = this.renderBusyPhase(); content = this.renderBusyPhase();
break; break;
case Phase.Stored:
content = this.renderStoredPhase();
break;
case Phase.ConfirmSkip: case Phase.ConfirmSkip:
content = this.renderPhaseSkipConfirm(); content = this.renderPhaseSkipConfirm();
break; break;
@ -912,8 +950,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
return ( return (
<BaseDialog <BaseDialog
className="mx_CreateSecretStorageDialog" className={this.classNames}
onFinished={this.props.onFinished} onFinished={this.props.onFinished}
top={this.topComponent}
title={this.titleForPhase(this.state.phase)} title={this.titleForPhase(this.state.phase)}
titleClass={titleClass} titleClass={titleClass}
hasCancel={this.props.hasCancel && [Phase.Passphrase].includes(this.state.phase)} hasCancel={this.props.hasCancel && [Phase.Passphrase].includes(this.state.phase)}

View file

@ -50,6 +50,9 @@ interface IProps extends IDialogProps {
// determine its size. Default: true. // determine its size. Default: true.
"fixedWidth"?: boolean; "fixedWidth"?: boolean;
// To be displayed at the top of the dialog. Even above the title.
"top"?: React.ReactNode;
// Title for the dialog. // Title for the dialog.
"title"?: JSX.Element | string; "title"?: JSX.Element | string;
// Specific aria label to use, if not provided will set aria-labelledBy to mx_Dialog_title // Specific aria label to use, if not provided will set aria-labelledBy to mx_Dialog_title
@ -161,6 +164,7 @@ export default class BaseDialog extends React.Component<IProps> {
mx_Dialog_fixedWidth: this.props.fixedWidth, mx_Dialog_fixedWidth: this.props.fixedWidth,
})} })}
> >
{this.props.top}
<div <div
className={classNames("mx_Dialog_header", { className={classNames("mx_Dialog_header", {
mx_Dialog_headerWithButton: !!this.props.headerButton, mx_Dialog_headerWithButton: !!this.props.headerButton,

View file

@ -24,6 +24,7 @@ import BaseDialog from "./BaseDialog";
import DialogButtons from "../elements/DialogButtons"; import DialogButtons from "../elements/DialogButtons";
interface IProps extends IDialogProps { interface IProps extends IDialogProps {
top?: ReactNode;
title?: string; title?: string;
description?: ReactNode; description?: ReactNode;
className?: string; className?: string;
@ -49,6 +50,7 @@ export default class InfoDialog extends React.Component<IProps> {
<BaseDialog <BaseDialog
className="mx_InfoDialog" className="mx_InfoDialog"
onFinished={this.props.onFinished} onFinished={this.props.onFinished}
top={this.props.top}
title={this.props.title} title={this.props.title}
contentId="mx_Dialog_content" contentId="mx_Dialog_content"
hasCancel={this.props.hasCloseButton} hasCancel={this.props.hasCloseButton}

View file

@ -3632,6 +3632,7 @@
"Enter a Security Phrase only you know, as it's used to safeguard your data. To be secure, you shouldn't re-use your account password.": "Enter a Security Phrase only you know, as it's used to safeguard your data. To be secure, you shouldn't re-use your account password.", "Enter a Security Phrase only you know, as it's used to safeguard your data. To be secure, you shouldn't re-use your account password.": "Enter a Security Phrase only you know, as it's used to safeguard your data. To be secure, you shouldn't re-use your account password.",
"Store your Security Key somewhere safe, like a password manager or a safe, as it's used to safeguard your encrypted data.": "Store your Security Key somewhere safe, like a password manager or a safe, as it's used to safeguard your encrypted data.", "Store your Security Key somewhere safe, like a password manager or a safe, as it's used to safeguard your encrypted data.": "Store your Security Key somewhere safe, like a password manager or a safe, as it's used to safeguard your encrypted data.",
"%(downloadButton)s or %(copyButton)s": "%(downloadButton)s or %(copyButton)s", "%(downloadButton)s or %(copyButton)s": "%(downloadButton)s or %(copyButton)s",
"Your keys are now being backed up from this device.": "Your keys are now being backed up from this device.",
"Unable to query secret storage status": "Unable to query secret storage status", "Unable to query secret storage status": "Unable to query secret storage status",
"If you cancel now, you may lose encrypted messages & data if you lose access to your logins.": "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.", "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.": "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.",
"You can also set up Secure Backup & manage your keys in Settings.": "You can also set up Secure Backup & manage your keys in Settings.", "You can also set up Secure Backup & manage your keys in Settings.": "You can also set up Secure Backup & manage your keys in Settings.",
@ -3639,6 +3640,7 @@
"Set a Security Phrase": "Set a Security Phrase", "Set a Security Phrase": "Set a Security Phrase",
"Confirm Security Phrase": "Confirm Security Phrase", "Confirm Security Phrase": "Confirm Security Phrase",
"Save your Security Key": "Save your Security Key", "Save your Security Key": "Save your Security Key",
"Secure Backup successful": "Secure Backup successful",
"Unable to set up secret storage": "Unable to set up secret storage", "Unable to set up secret storage": "Unable to set up secret storage",
"Passphrases must match": "Passphrases must match", "Passphrases must match": "Passphrases must match",
"Passphrase must not be empty": "Passphrase must not be empty", "Passphrase must not be empty": "Passphrase must not be empty",