Add success dialog after key backup (#10177)
This commit is contained in:
parent
a854e941cc
commit
9b267e7bc4
11 changed files with 144 additions and 2 deletions
|
@ -183,6 +183,10 @@ describe("Cryptography", function () {
|
|||
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("not.exist");
|
||||
|
||||
cy.contains("Secure Backup successful").should("exist");
|
||||
cy.contains("Done").click();
|
||||
cy.contains("Secure Backup successful").should("not.exist");
|
||||
});
|
||||
return;
|
||||
});
|
||||
|
|
|
@ -181,12 +181,14 @@ describe("Decryption Failure Bar", () => {
|
|||
|
||||
cy.contains(".mx_DecryptionFailureBar_button", "Reset").click();
|
||||
|
||||
// Set up key backup
|
||||
cy.get(".mx_Dialog").within(() => {
|
||||
cy.contains(".mx_Dialog_primary", "Continue").click();
|
||||
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
|
||||
cy.contains(".mx_AccessibleButton", "Download").click();
|
||||
cy.contains(".mx_Dialog_primary:not([disabled])", "Continue").click();
|
||||
cy.contains("Done").click();
|
||||
});
|
||||
|
||||
cy.get(".mx_DecryptionFailureBar .mx_DecryptionFailureBar_message_headline").should(
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
@import "./components/views/spaces/_QuickThemeSwitcher.pcss";
|
||||
@import "./components/views/typography/_Caption.pcss";
|
||||
@import "./compound/_Icon.pcss";
|
||||
@import "./compound/_SuccessDialog.pcss";
|
||||
@import "./structures/_AutoHideScrollbar.pcss";
|
||||
@import "./structures/_AutocompleteInput.pcss";
|
||||
@import "./structures/_BackdropPanel.pcss";
|
||||
|
|
|
@ -29,10 +29,22 @@ limitations under the License.
|
|||
color: $accent;
|
||||
}
|
||||
|
||||
.mx_Icon_bg-accent-light {
|
||||
background-color: rgba($accent, 0.1);
|
||||
}
|
||||
|
||||
.mx_Icon_alert {
|
||||
color: $alert;
|
||||
}
|
||||
|
||||
.mx_Icon_circle-40 {
|
||||
border-radius: 20px;
|
||||
flex: 0 0 40px;
|
||||
height: 40px;
|
||||
padding: 0 12px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.mx_Icon_8 {
|
||||
flex: 0 0 8px;
|
||||
height: 8px;
|
||||
|
|
48
res/css/compound/_SuccessDialog.pcss
Normal file
48
res/css/compound/_SuccessDialog.pcss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,6 +23,14 @@ limitations under the License.
|
|||
/* never asked. */
|
||||
width: 560px;
|
||||
|
||||
&.mx_SuccessDialog {
|
||||
padding: 56px; /* 80px from design - 24px wrapper padding */
|
||||
|
||||
.mx_Dialog_title {
|
||||
margin-bottom: $spacing-16;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_SettingsFlag {
|
||||
display: flex;
|
||||
}
|
||||
|
|
20
res/img/element-icons/check.svg
Normal file
20
res/img/element-icons/check.svg
Normal 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 |
|
@ -23,6 +23,7 @@ import { TrustInfo } from "matrix-js-sdk/src/crypto/backup";
|
|||
import { CrossSigningKeys, UIAFlow } from "matrix-js-sdk/src/matrix";
|
||||
import { IRecoveryKey } from "matrix-js-sdk/src/crypto/api";
|
||||
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
|
||||
import classNames from "classnames";
|
||||
|
||||
import { MatrixClientPeg } from "../../../../MatrixClientPeg";
|
||||
import { _t, _td } from "../../../../languageHandler";
|
||||
|
@ -48,6 +49,7 @@ import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
|||
import Spinner from "../../../../components/views/elements/Spinner";
|
||||
import InteractiveAuthDialog from "../../../../components/views/dialogs/InteractiveAuthDialog";
|
||||
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!
|
||||
enum Phase {
|
||||
|
@ -59,6 +61,7 @@ enum Phase {
|
|||
PassphraseConfirm = "passphrase_confirm",
|
||||
ShowKey = "show_key",
|
||||
Storing = "storing",
|
||||
Stored = "stored",
|
||||
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) {
|
||||
if (this.state.canUploadKeysWithPasswordOnly && e.httpStatus === 401 && e.data.flows) {
|
||||
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 {
|
||||
return (
|
||||
<div>
|
||||
|
@ -837,11 +856,27 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
|||
return _t("Save your Security Key");
|
||||
case Phase.Storing:
|
||||
return _t("Setting up keys");
|
||||
case Phase.Stored:
|
||||
return _t("Secure Backup successful");
|
||||
default:
|
||||
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 {
|
||||
let content;
|
||||
if (this.state.error) {
|
||||
|
@ -884,6 +919,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
|||
case Phase.Storing:
|
||||
content = this.renderBusyPhase();
|
||||
break;
|
||||
case Phase.Stored:
|
||||
content = this.renderStoredPhase();
|
||||
break;
|
||||
case Phase.ConfirmSkip:
|
||||
content = this.renderPhaseSkipConfirm();
|
||||
break;
|
||||
|
@ -912,8 +950,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
|||
|
||||
return (
|
||||
<BaseDialog
|
||||
className="mx_CreateSecretStorageDialog"
|
||||
className={this.classNames}
|
||||
onFinished={this.props.onFinished}
|
||||
top={this.topComponent}
|
||||
title={this.titleForPhase(this.state.phase)}
|
||||
titleClass={titleClass}
|
||||
hasCancel={this.props.hasCancel && [Phase.Passphrase].includes(this.state.phase)}
|
||||
|
|
|
@ -50,6 +50,9 @@ interface IProps extends IDialogProps {
|
|||
// determine its size. Default: true.
|
||||
"fixedWidth"?: boolean;
|
||||
|
||||
// To be displayed at the top of the dialog. Even above the title.
|
||||
"top"?: React.ReactNode;
|
||||
|
||||
// Title for the dialog.
|
||||
"title"?: JSX.Element | string;
|
||||
// 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,
|
||||
})}
|
||||
>
|
||||
{this.props.top}
|
||||
<div
|
||||
className={classNames("mx_Dialog_header", {
|
||||
mx_Dialog_headerWithButton: !!this.props.headerButton,
|
||||
|
|
|
@ -24,6 +24,7 @@ import BaseDialog from "./BaseDialog";
|
|||
import DialogButtons from "../elements/DialogButtons";
|
||||
|
||||
interface IProps extends IDialogProps {
|
||||
top?: ReactNode;
|
||||
title?: string;
|
||||
description?: ReactNode;
|
||||
className?: string;
|
||||
|
@ -49,6 +50,7 @@ export default class InfoDialog extends React.Component<IProps> {
|
|||
<BaseDialog
|
||||
className="mx_InfoDialog"
|
||||
onFinished={this.props.onFinished}
|
||||
top={this.props.top}
|
||||
title={this.props.title}
|
||||
contentId="mx_Dialog_content"
|
||||
hasCancel={this.props.hasCloseButton}
|
||||
|
|
|
@ -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.",
|
||||
"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",
|
||||
"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",
|
||||
"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.",
|
||||
|
@ -3639,6 +3640,7 @@
|
|||
"Set a Security Phrase": "Set a Security Phrase",
|
||||
"Confirm Security Phrase": "Confirm Security Phrase",
|
||||
"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",
|
||||
"Passphrases must match": "Passphrases must match",
|
||||
"Passphrase must not be empty": "Passphrase must not be empty",
|
||||
|
|
Loading…
Reference in a new issue