Prepare for OIDC QR Login PR (#12463)
* Move LoginWithQRSection to the top of the settings tab Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Refactor LoginWithQRSection to a Functional Component Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Extract LoginWithQR types Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update LoginWithQRFlow styling & copy Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Re-add missing buttons and update snapshots Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Use compound spacings Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
1c79bbb1ae
commit
641a20ce63
10 changed files with 598 additions and 389 deletions
|
@ -32,36 +32,10 @@ limitations under the License.
|
||||||
margin-top: $spacing-8;
|
margin-top: $spacing-8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_LoginWithQR_separator {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
&::before,
|
|
||||||
&::after {
|
|
||||||
content: "";
|
|
||||||
flex: 1;
|
|
||||||
border-bottom: 1px solid $quinary-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(:empty) {
|
|
||||||
&::before {
|
|
||||||
margin-right: 1em;
|
|
||||||
}
|
|
||||||
&::after {
|
|
||||||
margin-left: 1em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
font-size: $font-15px;
|
font-size: $font-15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_UserSettingsDialog .mx_LoginWithQR {
|
.mx_UserSettingsDialog .mx_LoginWithQR {
|
||||||
.mx_AccessibleButton + .mx_AccessibleButton {
|
|
||||||
margin-left: $spacing-12;
|
|
||||||
}
|
|
||||||
|
|
||||||
font: var(--cpd-font-body-md-regular);
|
font: var(--cpd-font-body-md-regular);
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
|
@ -69,18 +43,14 @@ limitations under the License.
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
li {
|
h2 {
|
||||||
line-height: 1.8;
|
margin-top: $spacing-24;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_QRCode {
|
.mx_QRCode {
|
||||||
margin: $spacing-28 0;
|
margin: $spacing-28 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_LoginWithQR_buttons {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_LoginWithQR_qrWrapper {
|
.mx_LoginWithQR_qrWrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
@ -91,12 +61,6 @@ limitations under the License.
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
.mx_LoginWithQR_centreTitle {
|
|
||||||
h1 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 > svg {
|
h1 > svg {
|
||||||
&.normal {
|
&.normal {
|
||||||
color: $secondary-content;
|
color: $secondary-content;
|
||||||
|
@ -137,11 +101,69 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
ol {
|
ol {
|
||||||
list-style-position: inside;
|
|
||||||
padding-inline-start: 0;
|
padding-inline-start: 0;
|
||||||
|
list-style: none; /* list markers do not support the outlined number styling we need */
|
||||||
|
|
||||||
li::marker {
|
li {
|
||||||
color: $accent;
|
position: relative;
|
||||||
|
padding-left: var(--cpd-space-7x);
|
||||||
|
color: 1px solid $input-placeholder;
|
||||||
|
margin-bottom: var(--cpd-space-4x);
|
||||||
|
line-height: 20px;
|
||||||
|
text-align: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Circled number list item marker */
|
||||||
|
li::before {
|
||||||
|
content: counter(list-item);
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
display: inline-block;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid $input-placeholder;
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
label[for="mx_LoginWithQR_checkCode"] {
|
||||||
|
margin-top: var(--cpd-space-6x);
|
||||||
|
color: var(--cpd-color-text-primary);
|
||||||
|
margin-bottom: var(--cpd-space-1x);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_LoginWithQR_icon {
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: var(--cpd-space-3x);
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
background-color: var(--cpd-color-bg-success-subtle);
|
||||||
|
svg {
|
||||||
|
color: var(--cpd-color-icon-success-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mx_LoginWithQR_icon--critical {
|
||||||
|
background-color: var(--cpd-color-bg-critical-subtle);
|
||||||
|
svg {
|
||||||
|
color: var(--cpd-color-icon-critical-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_LoginWithQR_checkCode_input {
|
||||||
|
margin-bottom: var(--cpd-space-1x);
|
||||||
|
text-align: initial;
|
||||||
|
|
||||||
|
input {
|
||||||
|
/* Workaround for one of the input rules in _common.pcss being not specific enough */
|
||||||
|
padding: 0;
|
||||||
|
padding-inline-start: calc(40px / 2 - (1ch / 2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,13 +186,39 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_LoginWithQR_breadcrumbs {
|
.mx_LoginWithQR_breadcrumbs {
|
||||||
font-size: $font-13px;
|
font-size: $font-13px;
|
||||||
color: var(--cpd-color-text-secondary);
|
color: $secondary-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_LoginWithQR_main {
|
.mx_LoginWithQR_main {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
align-items: center;
|
||||||
|
color: $primary-content;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: $secondary-content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mx_LoginWithQR_error .mx_LoginWithQR_main {
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_LoginWithQR_buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: $spacing-16;
|
||||||
|
margin-top: var(--cpd-space-6x);
|
||||||
|
|
||||||
|
.mx_AccessibleButton {
|
||||||
|
width: 300px;
|
||||||
|
height: 48px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_QRCode {
|
.mx_QRCode {
|
||||||
|
|
43
src/components/views/auth/LoginWithQR-types.ts
Normal file
43
src/components/views/auth/LoginWithQR-types.ts
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
Copyright 2024 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The intention of this enum is to have a mode that scans a QR code instead of generating one.
|
||||||
|
*/
|
||||||
|
export enum Mode {
|
||||||
|
/**
|
||||||
|
* A QR code with be generated and shown
|
||||||
|
*/
|
||||||
|
Show = "show",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Phase {
|
||||||
|
Loading,
|
||||||
|
ShowingQR,
|
||||||
|
Connecting,
|
||||||
|
Connected,
|
||||||
|
WaitingForDevice,
|
||||||
|
Verifying,
|
||||||
|
Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Click {
|
||||||
|
Cancel,
|
||||||
|
Decline,
|
||||||
|
Approve,
|
||||||
|
TryAgain,
|
||||||
|
Back,
|
||||||
|
}
|
|
@ -24,34 +24,7 @@ import { HTTPError, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import { wrapRequestWithDialog } from "../../../utils/UserInteractiveAuth";
|
import { wrapRequestWithDialog } from "../../../utils/UserInteractiveAuth";
|
||||||
import LoginWithQRFlow from "./LoginWithQRFlow";
|
import LoginWithQRFlow from "./LoginWithQRFlow";
|
||||||
|
import { Click, Mode, Phase } from "./LoginWithQR-types";
|
||||||
/**
|
|
||||||
* The intention of this enum is to have a mode that scans a QR code instead of generating one.
|
|
||||||
*/
|
|
||||||
export enum Mode {
|
|
||||||
/**
|
|
||||||
* A QR code with be generated and shown
|
|
||||||
*/
|
|
||||||
Show = "show",
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum Phase {
|
|
||||||
Loading,
|
|
||||||
ShowingQR,
|
|
||||||
Connecting,
|
|
||||||
Connected,
|
|
||||||
WaitingForDevice,
|
|
||||||
Verifying,
|
|
||||||
Error,
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum Click {
|
|
||||||
Cancel,
|
|
||||||
Decline,
|
|
||||||
Approve,
|
|
||||||
TryAgain,
|
|
||||||
Back,
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
client: MatrixClient;
|
client: MatrixClient;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
Copyright 2022 - 2024 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -14,19 +14,24 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React, { ReactNode } from "react";
|
||||||
import { RendezvousFailureReason } from "matrix-js-sdk/src/rendezvous";
|
import { RendezvousFailureReason as LegacyRendezvousFailureReason } from "matrix-js-sdk/src/rendezvous";
|
||||||
import { Icon as ChevronLeftIcon } from "@vector-im/compound-design-tokens/icons/chevron-left.svg";
|
import { Icon as ChevronLeftIcon } from "@vector-im/compound-design-tokens/icons/chevron-left.svg";
|
||||||
|
import { Icon as CheckCircleSolidIcon } from "@vector-im/compound-design-tokens/icons/check-circle-solid.svg";
|
||||||
|
import { Icon as ErrorIcon } from "@vector-im/compound-design-tokens/icons/error.svg";
|
||||||
|
import { Heading, Text } from "@vector-im/compound-web";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import QRCode from "../elements/QRCode";
|
import QRCode from "../elements/QRCode";
|
||||||
import Spinner from "../elements/Spinner";
|
import Spinner from "../elements/Spinner";
|
||||||
import { Icon as InfoIcon } from "../../../../res/img/element-icons/i.svg";
|
import { Icon as InfoIcon } from "../../../../res/img/element-icons/i.svg";
|
||||||
import { Click, FailureReason, LoginWithQRFailureReason, Phase } from "./LoginWithQR";
|
import { Click, Phase } from "./LoginWithQR-types";
|
||||||
import SdkConfig from "../../../SdkConfig";
|
import SdkConfig from "../../../SdkConfig";
|
||||||
|
import { FailureReason, LoginWithQRFailureReason } from "./LoginWithQR";
|
||||||
|
|
||||||
interface IProps {
|
interface Props {
|
||||||
phase: Phase;
|
phase: Phase;
|
||||||
code?: string;
|
code?: string;
|
||||||
onClick(type: Click): Promise<void>;
|
onClick(type: Click): Promise<void>;
|
||||||
|
@ -39,8 +44,8 @@ interface IProps {
|
||||||
*
|
*
|
||||||
* This uses the unstable feature of MSC3906: https://github.com/matrix-org/matrix-spec-proposals/pull/3906
|
* This uses the unstable feature of MSC3906: https://github.com/matrix-org/matrix-spec-proposals/pull/3906
|
||||||
*/
|
*/
|
||||||
export default class LoginWithQRFlow extends React.Component<IProps> {
|
export default class LoginWithQRFlow extends React.Component<Props> {
|
||||||
public constructor(props: IProps) {
|
public constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,49 +77,75 @@ export default class LoginWithQRFlow extends React.Component<IProps> {
|
||||||
let main: JSX.Element | undefined;
|
let main: JSX.Element | undefined;
|
||||||
let buttons: JSX.Element | undefined;
|
let buttons: JSX.Element | undefined;
|
||||||
let backButton = true;
|
let backButton = true;
|
||||||
let cancellationMessage: string | undefined;
|
let className = "";
|
||||||
let centreTitle = false;
|
|
||||||
|
|
||||||
switch (this.props.phase) {
|
switch (this.props.phase) {
|
||||||
case Phase.Error:
|
case Phase.Error: {
|
||||||
|
let success = false;
|
||||||
|
let title: string | undefined;
|
||||||
|
let message: ReactNode | undefined;
|
||||||
|
|
||||||
switch (this.props.failureReason) {
|
switch (this.props.failureReason) {
|
||||||
case RendezvousFailureReason.Expired:
|
case LegacyRendezvousFailureReason.UnsupportedAlgorithm:
|
||||||
cancellationMessage = _t("auth|qr_code_login|error_linking_incomplete");
|
case LegacyRendezvousFailureReason.UnsupportedTransport:
|
||||||
|
case LegacyRendezvousFailureReason.HomeserverLacksSupport:
|
||||||
|
title = _t("auth|qr_code_login|error_unsupported_protocol_title");
|
||||||
|
message = _t("auth|qr_code_login|error_unsupported_protocol");
|
||||||
break;
|
break;
|
||||||
case RendezvousFailureReason.InvalidCode:
|
|
||||||
cancellationMessage = _t("auth|qr_code_login|error_invalid_scanned_code");
|
case LegacyRendezvousFailureReason.UserCancelled:
|
||||||
|
title = _t("auth|qr_code_login|error_user_cancelled_title");
|
||||||
|
message = _t("auth|qr_code_login|error_user_cancelled");
|
||||||
break;
|
break;
|
||||||
case RendezvousFailureReason.UnsupportedAlgorithm:
|
|
||||||
cancellationMessage = _t("auth|qr_code_login|error_device_unsupported");
|
case LegacyRendezvousFailureReason.Expired:
|
||||||
|
title = _t("auth|qr_code_login|error_expired_title");
|
||||||
|
message = _t("auth|qr_code_login|error_expired");
|
||||||
break;
|
break;
|
||||||
case RendezvousFailureReason.UserDeclined:
|
|
||||||
cancellationMessage = _t("auth|qr_code_login|error_request_declined");
|
case LegacyRendezvousFailureReason.InvalidCode:
|
||||||
|
title = _t("auth|qr_code_login|error_insecure_channel_detected_title");
|
||||||
|
message = (
|
||||||
|
<>
|
||||||
|
{_t("auth|qr_code_login|error_insecure_channel_detected")}
|
||||||
|
|
||||||
|
<Text as="h2" size="lg" weight="semibold" data-testid="cancellation-message">
|
||||||
|
{_t("auth|qr_code_login|error_insecure_channel_detected_instructions")}
|
||||||
|
</Text>
|
||||||
|
<ol>
|
||||||
|
<li>{_t("auth|qr_code_login|error_insecure_channel_detected_instructions_1")}</li>
|
||||||
|
<li>{_t("auth|qr_code_login|error_insecure_channel_detected_instructions_2")}</li>
|
||||||
|
<li>{_t("auth|qr_code_login|error_insecure_channel_detected_instructions_3")}</li>
|
||||||
|
</ol>
|
||||||
|
</>
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case RendezvousFailureReason.OtherDeviceAlreadySignedIn:
|
|
||||||
cancellationMessage = _t("auth|qr_code_login|error_device_already_signed_in");
|
case LegacyRendezvousFailureReason.OtherDeviceAlreadySignedIn:
|
||||||
|
success = true;
|
||||||
|
title = _t("auth|qr_code_login|error_other_device_already_signed_in_title");
|
||||||
|
message = _t("auth|qr_code_login|error_other_device_already_signed_in");
|
||||||
break;
|
break;
|
||||||
case RendezvousFailureReason.OtherDeviceNotSignedIn:
|
|
||||||
cancellationMessage = _t("auth|qr_code_login|error_device_not_signed_in");
|
case LegacyRendezvousFailureReason.UserDeclined:
|
||||||
break;
|
title = _t("auth|qr_code_login|error_user_declined_title");
|
||||||
case RendezvousFailureReason.UserCancelled:
|
message = _t("auth|qr_code_login|error_user_declined");
|
||||||
cancellationMessage = _t("auth|qr_code_login|error_request_cancelled");
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LoginWithQRFailureReason.RateLimited:
|
case LoginWithQRFailureReason.RateLimited:
|
||||||
cancellationMessage = _t("auth|qr_code_login|error_rate_limited");
|
title = _t("error|something_went_wrong");
|
||||||
break;
|
message = _t("auth|qr_code_login|error_rate_limited");
|
||||||
case RendezvousFailureReason.Unknown:
|
|
||||||
cancellationMessage = _t("auth|qr_code_login|error_unexpected");
|
|
||||||
break;
|
|
||||||
case RendezvousFailureReason.HomeserverLacksSupport:
|
|
||||||
cancellationMessage = _t("auth|qr_code_login|error_homeserver_lacks_support");
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case LegacyRendezvousFailureReason.OtherDeviceNotSignedIn:
|
||||||
|
case LegacyRendezvousFailureReason.Unknown:
|
||||||
default:
|
default:
|
||||||
cancellationMessage = _t("auth|qr_code_login|error_request_cancelled");
|
title = _t("error|something_went_wrong");
|
||||||
|
message = _t("auth|qr_code_login|error_unexpected");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
centreTitle = true;
|
className = "mx_LoginWithQR_error";
|
||||||
backButton = false;
|
backButton = false;
|
||||||
main = <p data-testid="cancellation-message">{cancellationMessage}</p>;
|
|
||||||
buttons = (
|
buttons = (
|
||||||
<>
|
<>
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
|
@ -127,7 +158,23 @@ export default class LoginWithQRFlow extends React.Component<IProps> {
|
||||||
{this.cancelButton()}
|
{this.cancelButton()}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
main = (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={classNames("mx_LoginWithQR_icon", {
|
||||||
|
"mx_LoginWithQR_icon--critical": !success,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{success ? <CheckCircleSolidIcon width="32px" /> : <ErrorIcon width="32px" />}
|
||||||
|
</div>
|
||||||
|
<Heading as="h1" size="sm" weight="semibold">
|
||||||
|
{title}
|
||||||
|
</Heading>
|
||||||
|
{typeof message === "object" ? message : <p data-testid="cancellation-message">{message}</p>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case Phase.Connected:
|
case Phase.Connected:
|
||||||
backButton = false;
|
backButton = false;
|
||||||
main = (
|
main = (
|
||||||
|
@ -145,13 +192,6 @@ export default class LoginWithQRFlow extends React.Component<IProps> {
|
||||||
|
|
||||||
buttons = (
|
buttons = (
|
||||||
<>
|
<>
|
||||||
<AccessibleButton
|
|
||||||
data-testid="decline-login-button"
|
|
||||||
kind="primary_outline"
|
|
||||||
onClick={this.handleClick(Click.Decline)}
|
|
||||||
>
|
|
||||||
{_t("action|cancel")}
|
|
||||||
</AccessibleButton>
|
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
data-testid="approve-login-button"
|
data-testid="approve-login-button"
|
||||||
kind="primary"
|
kind="primary"
|
||||||
|
@ -159,23 +199,28 @@ export default class LoginWithQRFlow extends React.Component<IProps> {
|
||||||
>
|
>
|
||||||
{_t("action|approve")}
|
{_t("action|approve")}
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
|
<AccessibleButton
|
||||||
|
data-testid="decline-login-button"
|
||||||
|
kind="primary_outline"
|
||||||
|
onClick={this.handleClick(Click.Decline)}
|
||||||
|
>
|
||||||
|
{_t("action|cancel")}
|
||||||
|
</AccessibleButton>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case Phase.ShowingQR:
|
case Phase.ShowingQR:
|
||||||
if (this.props.code) {
|
if (this.props.code) {
|
||||||
const code = (
|
const data = Buffer.from(this.props.code ?? "");
|
||||||
<div className="mx_LoginWithQR_qrWrapper">
|
|
||||||
<QRCode
|
|
||||||
data={[{ data: Buffer.from(this.props.code ?? ""), mode: "byte" }]}
|
|
||||||
className="mx_QRCode"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
main = (
|
main = (
|
||||||
<>
|
<>
|
||||||
<h1>{_t("auth|qr_code_login|scan_code_instruction")}</h1>
|
<Heading as="h1" size="sm" weight="semibold">
|
||||||
{code}
|
{_t("auth|qr_code_login|scan_code_instruction")}
|
||||||
|
</Heading>
|
||||||
|
<div className="mx_LoginWithQR_qrWrapper">
|
||||||
|
<QRCode data={[{ data, mode: "byte" }]} className="mx_QRCode" />
|
||||||
|
</div>
|
||||||
<ol>
|
<ol>
|
||||||
<li>
|
<li>
|
||||||
{_t("auth|qr_code_login|open_element_other_device", {
|
{_t("auth|qr_code_login|open_element_other_device", {
|
||||||
|
@ -209,30 +254,27 @@ export default class LoginWithQRFlow extends React.Component<IProps> {
|
||||||
buttons = this.cancelButton();
|
buttons = this.cancelButton();
|
||||||
break;
|
break;
|
||||||
case Phase.Verifying:
|
case Phase.Verifying:
|
||||||
centreTitle = true;
|
|
||||||
main = this.simpleSpinner(_t("auth|qr_code_login|completing_setup"));
|
main = this.simpleSpinner(_t("auth|qr_code_login|completing_setup"));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-testid="login-with-qr" className="mx_LoginWithQR">
|
<div data-testid="login-with-qr" className={classNames("mx_LoginWithQR", className)}>
|
||||||
<div className={centreTitle ? "mx_LoginWithQR_centreTitle" : ""}>
|
{backButton ? (
|
||||||
{backButton ? (
|
<div className="mx_LoginWithQR_heading">
|
||||||
<div className="mx_LoginWithQR_heading">
|
<AccessibleButton
|
||||||
<AccessibleButton
|
data-testid="back-button"
|
||||||
data-testid="back-button"
|
className="mx_LoginWithQR_BackButton"
|
||||||
className="mx_LoginWithQR_BackButton"
|
onClick={this.handleClick(Click.Back)}
|
||||||
onClick={this.handleClick(Click.Back)}
|
title="Back"
|
||||||
title="Back"
|
>
|
||||||
>
|
<ChevronLeftIcon />
|
||||||
<ChevronLeftIcon />
|
</AccessibleButton>
|
||||||
</AccessibleButton>
|
<div className="mx_LoginWithQR_breadcrumbs">
|
||||||
<div className="mx_LoginWithQR_breadcrumbs">
|
{_t("settings|sessions|title")} / {_t("settings|sessions|sign_in_with_qr")}
|
||||||
{_t("settings|sessions|title")} / {_t("settings|sessions|sign_in_with_qr")}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
</div>
|
||||||
</div>
|
) : null}
|
||||||
<div className="mx_LoginWithQR_main">{main}</div>
|
<div className="mx_LoginWithQR_main">{main}</div>
|
||||||
<div className="mx_LoginWithQR_buttons">{buttons}</div>
|
<div className="mx_LoginWithQR_buttons">{buttons}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -35,39 +35,40 @@ interface IProps {
|
||||||
wellKnown?: IClientWellKnown;
|
wellKnown?: IClientWellKnown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class LoginWithQRSection extends React.Component<IProps> {
|
function shouldShowQrLegacy(
|
||||||
public constructor(props: IProps) {
|
versions?: IServerVersions,
|
||||||
super(props);
|
wellKnown?: IClientWellKnown,
|
||||||
}
|
capabilities?: Capabilities,
|
||||||
|
): boolean {
|
||||||
public render(): JSX.Element | null {
|
// Needs server support for (get_login_token or OIDC Device Authorization Grant) and MSC3886:
|
||||||
// Needs server support for get_login_token and MSC3886:
|
// in r0 of MSC3882 it is exposed as a feature flag, but in stable and unstable r1 it is a capability
|
||||||
// in r0 of MSC3882 it is exposed as a feature flag, but in stable and unstable r1 it is a capability
|
const loginTokenCapability = GET_LOGIN_TOKEN_CAPABILITY.findIn<IGetLoginTokenCapability>(capabilities);
|
||||||
const capability = GET_LOGIN_TOKEN_CAPABILITY.findIn<IGetLoginTokenCapability>(this.props.capabilities);
|
const getLoginTokenSupported =
|
||||||
const getLoginTokenSupported =
|
!!versions?.unstable_features?.["org.matrix.msc3882"] || !!loginTokenCapability?.enabled;
|
||||||
!!this.props.versions?.unstable_features?.["org.matrix.msc3882"] || !!capability?.enabled;
|
const msc3886Supported =
|
||||||
const msc3886Supported =
|
!!versions?.unstable_features?.["org.matrix.msc3886"] || !!wellKnown?.["io.element.rendezvous"]?.server;
|
||||||
!!this.props.versions?.unstable_features?.["org.matrix.msc3886"] ||
|
return getLoginTokenSupported && msc3886Supported;
|
||||||
this.props.wellKnown?.["io.element.rendezvous"]?.server;
|
|
||||||
const offerShowQr = getLoginTokenSupported && msc3886Supported;
|
|
||||||
|
|
||||||
// don't show anything if no method is available
|
|
||||||
if (!offerShowQr) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SettingsSubsection heading={_t("settings|sessions|sign_in_with_qr")}>
|
|
||||||
<div className="mx_LoginWithQRSection">
|
|
||||||
<p className="mx_SettingsTab_subsectionText">
|
|
||||||
{_t("settings|sessions|sign_in_with_qr_description")}
|
|
||||||
</p>
|
|
||||||
<AccessibleButton onClick={this.props.onShowQr} kind="primary">
|
|
||||||
<QrCodeIcon height={20} width={20} />
|
|
||||||
{_t("settings|sessions|sign_in_with_qr_button")}
|
|
||||||
</AccessibleButton>
|
|
||||||
</div>
|
|
||||||
</SettingsSubsection>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LoginWithQRSection: React.FC<IProps> = ({ onShowQr, versions, capabilities, wellKnown }) => {
|
||||||
|
const offerShowQr = shouldShowQrLegacy(versions, wellKnown, capabilities);
|
||||||
|
|
||||||
|
// don't show anything if no method is available
|
||||||
|
if (!offerShowQr) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingsSubsection heading={_t("settings|sessions|sign_in_with_qr")}>
|
||||||
|
<div className="mx_LoginWithQRSection">
|
||||||
|
<p className="mx_SettingsTab_subsectionText">{_t("settings|sessions|sign_in_with_qr_description")}</p>
|
||||||
|
<AccessibleButton onClick={onShowQr} kind="primary">
|
||||||
|
<QrCodeIcon height={20} width={20} />
|
||||||
|
{_t("settings|sessions|sign_in_with_qr_button")}
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
</SettingsSubsection>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LoginWithQRSection;
|
||||||
|
|
|
@ -32,7 +32,8 @@ import { ExtendedDevice } from "../../devices/types";
|
||||||
import { deleteDevicesWithInteractiveAuth } from "../../devices/deleteDevices";
|
import { deleteDevicesWithInteractiveAuth } from "../../devices/deleteDevices";
|
||||||
import SettingsTab from "../SettingsTab";
|
import SettingsTab from "../SettingsTab";
|
||||||
import LoginWithQRSection from "../../devices/LoginWithQRSection";
|
import LoginWithQRSection from "../../devices/LoginWithQRSection";
|
||||||
import LoginWithQR, { Mode } from "../../../auth/LoginWithQR";
|
import LoginWithQR from "../../../auth/LoginWithQR";
|
||||||
|
import { Mode } from "../../../auth/LoginWithQR-types";
|
||||||
import { useAsyncMemo } from "../../../../../hooks/useAsyncMemo";
|
import { useAsyncMemo } from "../../../../../hooks/useAsyncMemo";
|
||||||
import QuestionDialog from "../../../dialogs/QuestionDialog";
|
import QuestionDialog from "../../../dialogs/QuestionDialog";
|
||||||
import { FilterVariation } from "../../devices/filter";
|
import { FilterVariation } from "../../devices/filter";
|
||||||
|
@ -284,6 +285,12 @@ const SessionManagerTab: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<SettingsTab>
|
<SettingsTab>
|
||||||
<SettingsSection heading={_t("settings|sessions|title")}>
|
<SettingsSection heading={_t("settings|sessions|title")}>
|
||||||
|
<LoginWithQRSection
|
||||||
|
onShowQr={onShowQrClicked}
|
||||||
|
versions={clientVersions}
|
||||||
|
capabilities={capabilities}
|
||||||
|
wellKnown={wellKnown}
|
||||||
|
/>
|
||||||
<SecurityRecommendations
|
<SecurityRecommendations
|
||||||
devices={devices}
|
devices={devices}
|
||||||
goToFilteredList={onGoToFilteredList}
|
goToFilteredList={onGoToFilteredList}
|
||||||
|
@ -337,12 +344,6 @@ const SessionManagerTab: React.FC = () => {
|
||||||
/>
|
/>
|
||||||
</SettingsSubsection>
|
</SettingsSubsection>
|
||||||
)}
|
)}
|
||||||
<LoginWithQRSection
|
|
||||||
onShowQr={onShowQrClicked}
|
|
||||||
versions={clientVersions}
|
|
||||||
capabilities={capabilities}
|
|
||||||
wellKnown={wellKnown}
|
|
||||||
/>
|
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
</SettingsTab>
|
</SettingsTab>
|
||||||
);
|
);
|
||||||
|
|
|
@ -249,21 +249,29 @@
|
||||||
"completing_setup": "Completing set up of your new device",
|
"completing_setup": "Completing set up of your new device",
|
||||||
"confirm_code_match": "Check that the code below matches with your other device:",
|
"confirm_code_match": "Check that the code below matches with your other device:",
|
||||||
"connecting": "Connecting…",
|
"connecting": "Connecting…",
|
||||||
"error_device_already_signed_in": "The other device is already signed in.",
|
"error_expired": "Sign in expired. Please try again.",
|
||||||
"error_device_not_signed_in": "The other device isn't signed in.",
|
"error_expired_title": "The sign in was not completed in time",
|
||||||
"error_device_unsupported": "Linking with this device is not supported.",
|
"error_insecure_channel_detected": "A secure connection could not be made to the new device. Your existing devices are still safe and you don't need to worry about them.",
|
||||||
"error_homeserver_lacks_support": "The homeserver doesn't support signing in another device.",
|
"error_insecure_channel_detected_instructions": "Now what?",
|
||||||
"error_invalid_scanned_code": "The scanned code is invalid.",
|
"error_insecure_channel_detected_instructions_1": "Try signing in to the other device again with a QR code in case this was a network problem",
|
||||||
"error_linking_incomplete": "The linking wasn't completed in the required time.",
|
"error_insecure_channel_detected_instructions_2": "If you encounter the same problem, try a different wifi network or use your mobile data instead of wifi",
|
||||||
|
"error_insecure_channel_detected_instructions_3": "If that doesn't work, sign in manually",
|
||||||
|
"error_insecure_channel_detected_title": "Connection not secure",
|
||||||
|
"error_other_device_already_signed_in": "You don’t need to do anything else.",
|
||||||
|
"error_other_device_already_signed_in_title": "Your other device is already signed in",
|
||||||
"error_rate_limited": "Too many attempts in a short time. Wait some time before trying again.",
|
"error_rate_limited": "Too many attempts in a short time. Wait some time before trying again.",
|
||||||
"error_request_cancelled": "The request was cancelled.",
|
"error_unexpected": "An unexpected error occurred. The request to connect your other device has been cancelled.",
|
||||||
"error_request_declined": "The request was declined on the other device.",
|
"error_unsupported_protocol": "This device does not support signing in to the other device with a QR code.",
|
||||||
"error_unexpected": "An unexpected error occurred.",
|
"error_unsupported_protocol_title": "Other device not compatible",
|
||||||
"follow_remaining_instructions": "Follow the remaining instructions to verify your other device",
|
"error_user_cancelled": "The sign in was cancelled on the other device.",
|
||||||
|
"error_user_cancelled_title": "Sign in request cancelled",
|
||||||
|
"error_user_declined": "You declined the request from your other device to sign in.",
|
||||||
|
"error_user_declined_title": "Sign in declined",
|
||||||
|
"follow_remaining_instructions": "Follow the instructions to link your other device",
|
||||||
"open_element_other_device": "Open %(brand)s on your other device",
|
"open_element_other_device": "Open %(brand)s on your other device",
|
||||||
"point_the_camera": "Point the camera at the QR code shown here",
|
"point_the_camera": "Point the camera at the QR code shown here",
|
||||||
"scan_code_instruction": "Scan the QR code with another device",
|
"scan_code_instruction": "Scan the QR code with another device",
|
||||||
"scan_qr_code": "Scan QR code",
|
"scan_qr_code": "Sign in with QR code",
|
||||||
"select_qr_code": "Select \"%(scanQRCode)s\"",
|
"select_qr_code": "Select \"%(scanQRCode)s\"",
|
||||||
"sign_in_new_device": "Sign in new device",
|
"sign_in_new_device": "Sign in new device",
|
||||||
"waiting_for_device": "Waiting for device to sign in"
|
"waiting_for_device": "Waiting for device to sign in"
|
||||||
|
|
|
@ -20,7 +20,8 @@ import React from "react";
|
||||||
import { MSC3906Rendezvous, RendezvousFailureReason } from "matrix-js-sdk/src/rendezvous";
|
import { MSC3906Rendezvous, RendezvousFailureReason } from "matrix-js-sdk/src/rendezvous";
|
||||||
import { HTTPError, LoginTokenPostResponse } from "matrix-js-sdk/src/matrix";
|
import { HTTPError, LoginTokenPostResponse } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import LoginWithQR, { Click, Mode, Phase } from "../../../../../src/components/views/auth/LoginWithQR";
|
import LoginWithQR from "../../../../../src/components/views/auth/LoginWithQR";
|
||||||
|
import { Click, Mode, Phase } from "../../../../../src/components/views/auth/LoginWithQR-types";
|
||||||
import type { MatrixClient } from "matrix-js-sdk/src/matrix";
|
import type { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
jest.mock("matrix-js-sdk/src/rendezvous");
|
jest.mock("matrix-js-sdk/src/rendezvous");
|
||||||
|
|
|
@ -19,12 +19,8 @@ import React from "react";
|
||||||
import { RendezvousFailureReason } from "matrix-js-sdk/src/rendezvous";
|
import { RendezvousFailureReason } from "matrix-js-sdk/src/rendezvous";
|
||||||
|
|
||||||
import LoginWithQRFlow from "../../../../../src/components/views/auth/LoginWithQRFlow";
|
import LoginWithQRFlow from "../../../../../src/components/views/auth/LoginWithQRFlow";
|
||||||
import {
|
import { LoginWithQRFailureReason, FailureReason } from "../../../../../src/components/views/auth/LoginWithQR";
|
||||||
Click,
|
import { Click, Phase } from "../../../../../src/components/views/auth/LoginWithQR-types";
|
||||||
Phase,
|
|
||||||
LoginWithQRFailureReason,
|
|
||||||
FailureReason,
|
|
||||||
} from "../../../../../src/components/views/auth/LoginWithQR";
|
|
||||||
|
|
||||||
describe("<LoginWithQRFlow />", () => {
|
describe("<LoginWithQRFlow />", () => {
|
||||||
const onClick = jest.fn();
|
const onClick = jest.fn();
|
||||||
|
|
|
@ -3,19 +3,28 @@
|
||||||
exports[`<LoginWithQRFlow /> errors renders data_mismatch 1`] = `
|
exports[`<LoginWithQRFlow /> errors renders data_mismatch 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR"
|
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||||
data-testid="login-with-qr"
|
data-testid="login-with-qr"
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
class="mx_LoginWithQR_centreTitle"
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR_main"
|
class="mx_LoginWithQR_main"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
class="mx_LoginWithQR_icon mx_LoginWithQR_icon--critical"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
width="32px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h1
|
||||||
|
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
|
||||||
|
>
|
||||||
|
Something went wrong!
|
||||||
|
</h1>
|
||||||
<p
|
<p
|
||||||
data-testid="cancellation-message"
|
data-testid="cancellation-message"
|
||||||
>
|
>
|
||||||
The request was cancelled.
|
An unexpected error occurred. The request to connect your other device has been cancelled.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
@ -45,19 +54,28 @@ exports[`<LoginWithQRFlow /> errors renders data_mismatch 1`] = `
|
||||||
exports[`<LoginWithQRFlow /> errors renders expired 1`] = `
|
exports[`<LoginWithQRFlow /> errors renders expired 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR"
|
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||||
data-testid="login-with-qr"
|
data-testid="login-with-qr"
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
class="mx_LoginWithQR_centreTitle"
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR_main"
|
class="mx_LoginWithQR_main"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
class="mx_LoginWithQR_icon mx_LoginWithQR_icon--critical"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
width="32px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h1
|
||||||
|
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
|
||||||
|
>
|
||||||
|
The sign in was not completed in time
|
||||||
|
</h1>
|
||||||
<p
|
<p
|
||||||
data-testid="cancellation-message"
|
data-testid="cancellation-message"
|
||||||
>
|
>
|
||||||
The linking wasn't completed in the required time.
|
Sign in expired. Please try again.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
@ -87,19 +105,28 @@ exports[`<LoginWithQRFlow /> errors renders expired 1`] = `
|
||||||
exports[`<LoginWithQRFlow /> errors renders homeserver_lacks_support 1`] = `
|
exports[`<LoginWithQRFlow /> errors renders homeserver_lacks_support 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR"
|
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||||
data-testid="login-with-qr"
|
data-testid="login-with-qr"
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
class="mx_LoginWithQR_centreTitle"
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR_main"
|
class="mx_LoginWithQR_main"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
class="mx_LoginWithQR_icon mx_LoginWithQR_icon--critical"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
width="32px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h1
|
||||||
|
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
|
||||||
|
>
|
||||||
|
Other device not compatible
|
||||||
|
</h1>
|
||||||
<p
|
<p
|
||||||
data-testid="cancellation-message"
|
data-testid="cancellation-message"
|
||||||
>
|
>
|
||||||
The homeserver doesn't support signing in another device.
|
This device does not support signing in to the other device with a QR code.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
@ -129,20 +156,42 @@ exports[`<LoginWithQRFlow /> errors renders homeserver_lacks_support 1`] = `
|
||||||
exports[`<LoginWithQRFlow /> errors renders invalid_code 1`] = `
|
exports[`<LoginWithQRFlow /> errors renders invalid_code 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR"
|
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||||
data-testid="login-with-qr"
|
data-testid="login-with-qr"
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
class="mx_LoginWithQR_centreTitle"
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR_main"
|
class="mx_LoginWithQR_main"
|
||||||
>
|
>
|
||||||
<p
|
<div
|
||||||
|
class="mx_LoginWithQR_icon mx_LoginWithQR_icon--critical"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
width="32px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h1
|
||||||
|
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
|
||||||
|
>
|
||||||
|
Connection not secure
|
||||||
|
</h1>
|
||||||
|
A secure connection could not be made to the new device. Your existing devices are still safe and you don't need to worry about them.
|
||||||
|
<h2
|
||||||
|
class="_typography_yh5dq_162 _font-body-lg-semibold_yh5dq_83"
|
||||||
data-testid="cancellation-message"
|
data-testid="cancellation-message"
|
||||||
>
|
>
|
||||||
The scanned code is invalid.
|
Now what?
|
||||||
</p>
|
</h2>
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
Try signing in to the other device again with a QR code in case this was a network problem
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
If you encounter the same problem, try a different wifi network or use your mobile data instead of wifi
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
If that doesn't work, sign in manually
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR_buttons"
|
class="mx_LoginWithQR_buttons"
|
||||||
|
@ -171,19 +220,28 @@ exports[`<LoginWithQRFlow /> errors renders invalid_code 1`] = `
|
||||||
exports[`<LoginWithQRFlow /> errors renders other_device_already_signed_in 1`] = `
|
exports[`<LoginWithQRFlow /> errors renders other_device_already_signed_in 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR"
|
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||||
data-testid="login-with-qr"
|
data-testid="login-with-qr"
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
class="mx_LoginWithQR_centreTitle"
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR_main"
|
class="mx_LoginWithQR_main"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
class="mx_LoginWithQR_icon"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
width="32px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h1
|
||||||
|
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
|
||||||
|
>
|
||||||
|
Your other device is already signed in
|
||||||
|
</h1>
|
||||||
<p
|
<p
|
||||||
data-testid="cancellation-message"
|
data-testid="cancellation-message"
|
||||||
>
|
>
|
||||||
The other device is already signed in.
|
You don’t need to do anything else.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
@ -213,19 +271,28 @@ exports[`<LoginWithQRFlow /> errors renders other_device_already_signed_in 1`] =
|
||||||
exports[`<LoginWithQRFlow /> errors renders other_device_not_signed_in 1`] = `
|
exports[`<LoginWithQRFlow /> errors renders other_device_not_signed_in 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR"
|
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||||
data-testid="login-with-qr"
|
data-testid="login-with-qr"
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
class="mx_LoginWithQR_centreTitle"
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR_main"
|
class="mx_LoginWithQR_main"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
class="mx_LoginWithQR_icon mx_LoginWithQR_icon--critical"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
width="32px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h1
|
||||||
|
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
|
||||||
|
>
|
||||||
|
Something went wrong!
|
||||||
|
</h1>
|
||||||
<p
|
<p
|
||||||
data-testid="cancellation-message"
|
data-testid="cancellation-message"
|
||||||
>
|
>
|
||||||
The other device isn't signed in.
|
An unexpected error occurred. The request to connect your other device has been cancelled.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
@ -255,15 +322,24 @@ exports[`<LoginWithQRFlow /> errors renders other_device_not_signed_in 1`] = `
|
||||||
exports[`<LoginWithQRFlow /> errors renders rate_limited 1`] = `
|
exports[`<LoginWithQRFlow /> errors renders rate_limited 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR"
|
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||||
data-testid="login-with-qr"
|
data-testid="login-with-qr"
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
class="mx_LoginWithQR_centreTitle"
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR_main"
|
class="mx_LoginWithQR_main"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
class="mx_LoginWithQR_icon mx_LoginWithQR_icon--critical"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
width="32px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h1
|
||||||
|
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
|
||||||
|
>
|
||||||
|
Something went wrong!
|
||||||
|
</h1>
|
||||||
<p
|
<p
|
||||||
data-testid="cancellation-message"
|
data-testid="cancellation-message"
|
||||||
>
|
>
|
||||||
|
@ -297,19 +373,28 @@ exports[`<LoginWithQRFlow /> errors renders rate_limited 1`] = `
|
||||||
exports[`<LoginWithQRFlow /> errors renders unknown 1`] = `
|
exports[`<LoginWithQRFlow /> errors renders unknown 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR"
|
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||||
data-testid="login-with-qr"
|
data-testid="login-with-qr"
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
class="mx_LoginWithQR_centreTitle"
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR_main"
|
class="mx_LoginWithQR_main"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
class="mx_LoginWithQR_icon mx_LoginWithQR_icon--critical"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
width="32px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h1
|
||||||
|
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
|
||||||
|
>
|
||||||
|
Something went wrong!
|
||||||
|
</h1>
|
||||||
<p
|
<p
|
||||||
data-testid="cancellation-message"
|
data-testid="cancellation-message"
|
||||||
>
|
>
|
||||||
An unexpected error occurred.
|
An unexpected error occurred. The request to connect your other device has been cancelled.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
@ -339,19 +424,28 @@ exports[`<LoginWithQRFlow /> errors renders unknown 1`] = `
|
||||||
exports[`<LoginWithQRFlow /> errors renders unsupported_algorithm 1`] = `
|
exports[`<LoginWithQRFlow /> errors renders unsupported_algorithm 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR"
|
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||||
data-testid="login-with-qr"
|
data-testid="login-with-qr"
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
class="mx_LoginWithQR_centreTitle"
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR_main"
|
class="mx_LoginWithQR_main"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
class="mx_LoginWithQR_icon mx_LoginWithQR_icon--critical"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
width="32px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h1
|
||||||
|
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
|
||||||
|
>
|
||||||
|
Other device not compatible
|
||||||
|
</h1>
|
||||||
<p
|
<p
|
||||||
data-testid="cancellation-message"
|
data-testid="cancellation-message"
|
||||||
>
|
>
|
||||||
Linking with this device is not supported.
|
This device does not support signing in to the other device with a QR code.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
@ -381,19 +475,28 @@ exports[`<LoginWithQRFlow /> errors renders unsupported_algorithm 1`] = `
|
||||||
exports[`<LoginWithQRFlow /> errors renders unsupported_transport 1`] = `
|
exports[`<LoginWithQRFlow /> errors renders unsupported_transport 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR"
|
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||||
data-testid="login-with-qr"
|
data-testid="login-with-qr"
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
class="mx_LoginWithQR_centreTitle"
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR_main"
|
class="mx_LoginWithQR_main"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
class="mx_LoginWithQR_icon mx_LoginWithQR_icon--critical"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
width="32px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h1
|
||||||
|
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
|
||||||
|
>
|
||||||
|
Other device not compatible
|
||||||
|
</h1>
|
||||||
<p
|
<p
|
||||||
data-testid="cancellation-message"
|
data-testid="cancellation-message"
|
||||||
>
|
>
|
||||||
The request was cancelled.
|
This device does not support signing in to the other device with a QR code.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
@ -423,19 +526,28 @@ exports[`<LoginWithQRFlow /> errors renders unsupported_transport 1`] = `
|
||||||
exports[`<LoginWithQRFlow /> errors renders user_cancelled 1`] = `
|
exports[`<LoginWithQRFlow /> errors renders user_cancelled 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR"
|
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||||
data-testid="login-with-qr"
|
data-testid="login-with-qr"
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
class="mx_LoginWithQR_centreTitle"
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR_main"
|
class="mx_LoginWithQR_main"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
class="mx_LoginWithQR_icon mx_LoginWithQR_icon--critical"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
width="32px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h1
|
||||||
|
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
|
||||||
|
>
|
||||||
|
Sign in request cancelled
|
||||||
|
</h1>
|
||||||
<p
|
<p
|
||||||
data-testid="cancellation-message"
|
data-testid="cancellation-message"
|
||||||
>
|
>
|
||||||
The request was cancelled.
|
The sign in was cancelled on the other device.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
@ -465,19 +577,28 @@ exports[`<LoginWithQRFlow /> errors renders user_cancelled 1`] = `
|
||||||
exports[`<LoginWithQRFlow /> errors renders user_declined 1`] = `
|
exports[`<LoginWithQRFlow /> errors renders user_declined 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR"
|
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||||
data-testid="login-with-qr"
|
data-testid="login-with-qr"
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
class="mx_LoginWithQR_centreTitle"
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR_main"
|
class="mx_LoginWithQR_main"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
class="mx_LoginWithQR_icon mx_LoginWithQR_icon--critical"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
width="32px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h1
|
||||||
|
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
|
||||||
|
>
|
||||||
|
Sign in declined
|
||||||
|
</h1>
|
||||||
<p
|
<p
|
||||||
data-testid="cancellation-message"
|
data-testid="cancellation-message"
|
||||||
>
|
>
|
||||||
The request was declined on the other device.
|
You declined the request from your other device to sign in.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
@ -511,34 +632,32 @@ exports[`<LoginWithQRFlow /> renders QR code 1`] = `
|
||||||
data-testid="login-with-qr"
|
data-testid="login-with-qr"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class=""
|
class="mx_LoginWithQR_heading"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR_heading"
|
aria-label="Back"
|
||||||
|
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
|
||||||
|
data-state="closed"
|
||||||
|
data-testid="back-button"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<div
|
<div />
|
||||||
aria-label="Back"
|
</div>
|
||||||
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
|
<div
|
||||||
data-state="closed"
|
class="mx_LoginWithQR_breadcrumbs"
|
||||||
data-testid="back-button"
|
>
|
||||||
role="button"
|
Sessions
|
||||||
tabindex="0"
|
/
|
||||||
>
|
Link new device
|
||||||
<div />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="mx_LoginWithQR_breadcrumbs"
|
|
||||||
>
|
|
||||||
Sessions
|
|
||||||
/
|
|
||||||
Link new device
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR_main"
|
class="mx_LoginWithQR_main"
|
||||||
>
|
>
|
||||||
<h1>
|
<h1
|
||||||
|
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
|
||||||
|
>
|
||||||
Scan the QR code with another device
|
Scan the QR code with another device
|
||||||
</h1>
|
</h1>
|
||||||
<div
|
<div
|
||||||
|
@ -562,7 +681,7 @@ exports[`<LoginWithQRFlow /> renders QR code 1`] = `
|
||||||
<span>
|
<span>
|
||||||
Select "
|
Select "
|
||||||
<b>
|
<b>
|
||||||
Scan QR code
|
Sign in with QR code
|
||||||
</b>
|
</b>
|
||||||
"
|
"
|
||||||
</span>
|
</span>
|
||||||
|
@ -571,7 +690,7 @@ exports[`<LoginWithQRFlow /> renders QR code 1`] = `
|
||||||
Point the camera at the QR code shown here
|
Point the camera at the QR code shown here
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Follow the remaining instructions to verify your other device
|
Follow the instructions to link your other device
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
|
@ -588,9 +707,6 @@ exports[`<LoginWithQRFlow /> renders code when connected 1`] = `
|
||||||
class="mx_LoginWithQR"
|
class="mx_LoginWithQR"
|
||||||
data-testid="login-with-qr"
|
data-testid="login-with-qr"
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
class=""
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR_main"
|
class="mx_LoginWithQR_main"
|
||||||
>
|
>
|
||||||
|
@ -616,14 +732,6 @@ exports[`<LoginWithQRFlow /> renders code when connected 1`] = `
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR_buttons"
|
class="mx_LoginWithQR_buttons"
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
|
|
||||||
data-testid="decline-login-button"
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||||
data-testid="approve-login-button"
|
data-testid="approve-login-button"
|
||||||
|
@ -632,6 +740,14 @@ exports[`<LoginWithQRFlow /> renders code when connected 1`] = `
|
||||||
>
|
>
|
||||||
Approve
|
Approve
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
|
||||||
|
data-testid="decline-login-button"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -644,28 +760,24 @@ exports[`<LoginWithQRFlow /> renders spinner while connecting 1`] = `
|
||||||
data-testid="login-with-qr"
|
data-testid="login-with-qr"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class=""
|
class="mx_LoginWithQR_heading"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR_heading"
|
aria-label="Back"
|
||||||
|
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
|
||||||
|
data-state="closed"
|
||||||
|
data-testid="back-button"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<div
|
<div />
|
||||||
aria-label="Back"
|
</div>
|
||||||
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
|
<div
|
||||||
data-state="closed"
|
class="mx_LoginWithQR_breadcrumbs"
|
||||||
data-testid="back-button"
|
>
|
||||||
role="button"
|
Sessions
|
||||||
tabindex="0"
|
/
|
||||||
>
|
Link new device
|
||||||
<div />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="mx_LoginWithQR_breadcrumbs"
|
|
||||||
>
|
|
||||||
Sessions
|
|
||||||
/
|
|
||||||
Link new device
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
@ -715,28 +827,24 @@ exports[`<LoginWithQRFlow /> renders spinner while loading 1`] = `
|
||||||
data-testid="login-with-qr"
|
data-testid="login-with-qr"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class=""
|
class="mx_LoginWithQR_heading"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR_heading"
|
aria-label="Back"
|
||||||
|
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
|
||||||
|
data-state="closed"
|
||||||
|
data-testid="back-button"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<div
|
<div />
|
||||||
aria-label="Back"
|
</div>
|
||||||
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
|
<div
|
||||||
data-state="closed"
|
class="mx_LoginWithQR_breadcrumbs"
|
||||||
data-testid="back-button"
|
>
|
||||||
role="button"
|
Sessions
|
||||||
tabindex="0"
|
/
|
||||||
>
|
Link new device
|
||||||
<div />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="mx_LoginWithQR_breadcrumbs"
|
|
||||||
>
|
|
||||||
Sessions
|
|
||||||
/
|
|
||||||
Link new device
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
@ -774,28 +882,24 @@ exports[`<LoginWithQRFlow /> renders spinner while signing in 1`] = `
|
||||||
data-testid="login-with-qr"
|
data-testid="login-with-qr"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class=""
|
class="mx_LoginWithQR_heading"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR_heading"
|
aria-label="Back"
|
||||||
|
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
|
||||||
|
data-state="closed"
|
||||||
|
data-testid="back-button"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<div
|
<div />
|
||||||
aria-label="Back"
|
</div>
|
||||||
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
|
<div
|
||||||
data-state="closed"
|
class="mx_LoginWithQR_breadcrumbs"
|
||||||
data-testid="back-button"
|
>
|
||||||
role="button"
|
Sessions
|
||||||
tabindex="0"
|
/
|
||||||
>
|
Link new device
|
||||||
<div />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="mx_LoginWithQR_breadcrumbs"
|
|
||||||
>
|
|
||||||
Sessions
|
|
||||||
/
|
|
||||||
Link new device
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
@ -845,28 +949,24 @@ exports[`<LoginWithQRFlow /> renders spinner while verifying 1`] = `
|
||||||
data-testid="login-with-qr"
|
data-testid="login-with-qr"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR_centreTitle"
|
class="mx_LoginWithQR_heading"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR_heading"
|
aria-label="Back"
|
||||||
|
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
|
||||||
|
data-state="closed"
|
||||||
|
data-testid="back-button"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<div
|
<div />
|
||||||
aria-label="Back"
|
</div>
|
||||||
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
|
<div
|
||||||
data-state="closed"
|
class="mx_LoginWithQR_breadcrumbs"
|
||||||
data-testid="back-button"
|
>
|
||||||
role="button"
|
Sessions
|
||||||
tabindex="0"
|
/
|
||||||
>
|
Link new device
|
||||||
<div />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="mx_LoginWithQR_breadcrumbs"
|
|
||||||
>
|
|
||||||
Sessions
|
|
||||||
/
|
|
||||||
Link new device
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
@ -907,28 +1007,24 @@ exports[`<LoginWithQRFlow /> renders spinner whilst QR generating 1`] = `
|
||||||
data-testid="login-with-qr"
|
data-testid="login-with-qr"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class=""
|
class="mx_LoginWithQR_heading"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_LoginWithQR_heading"
|
aria-label="Back"
|
||||||
|
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
|
||||||
|
data-state="closed"
|
||||||
|
data-testid="back-button"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<div
|
<div />
|
||||||
aria-label="Back"
|
</div>
|
||||||
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
|
<div
|
||||||
data-state="closed"
|
class="mx_LoginWithQR_breadcrumbs"
|
||||||
data-testid="back-button"
|
>
|
||||||
role="button"
|
Sessions
|
||||||
tabindex="0"
|
/
|
||||||
>
|
Link new device
|
||||||
<div />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="mx_LoginWithQR_breadcrumbs"
|
|
||||||
>
|
|
||||||
Sessions
|
|
||||||
/
|
|
||||||
Link new device
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|
Loading…
Reference in a new issue