mirror of
https://github.com/element-hq/element-web
synced 2024-11-29 04:48:50 +03:00
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_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;
|
||||||
});
|
});
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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;
|
||||||
|
|
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. */
|
/* 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;
|
||||||
}
|
}
|
||||||
|
|
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 { 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)}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in a new issue