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
res/css/views/auth
src
components/views
auth
settings
i18n/strings
test/components/views/settings/devices
|
@ -32,36 +32,10 @@ limitations under the License.
|
|||
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;
|
||||
}
|
||||
|
||||
.mx_UserSettingsDialog .mx_LoginWithQR {
|
||||
.mx_AccessibleButton + .mx_AccessibleButton {
|
||||
margin-left: $spacing-12;
|
||||
}
|
||||
|
||||
font: var(--cpd-font-body-md-regular);
|
||||
|
||||
h1 {
|
||||
|
@ -69,18 +43,14 @@ limitations under the License.
|
|||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
line-height: 1.8;
|
||||
h2 {
|
||||
margin-top: $spacing-24;
|
||||
}
|
||||
|
||||
.mx_QRCode {
|
||||
margin: $spacing-28 0;
|
||||
}
|
||||
|
||||
.mx_LoginWithQR_buttons {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mx_LoginWithQR_qrWrapper {
|
||||
display: flex;
|
||||
}
|
||||
|
@ -91,12 +61,6 @@ limitations under the License.
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.mx_LoginWithQR_centreTitle {
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
h1 > svg {
|
||||
&.normal {
|
||||
color: $secondary-content;
|
||||
|
@ -137,11 +101,69 @@ limitations under the License.
|
|||
}
|
||||
|
||||
ol {
|
||||
list-style-position: inside;
|
||||
padding-inline-start: 0;
|
||||
list-style: none; /* list markers do not support the outlined number styling we need */
|
||||
|
||||
li::marker {
|
||||
color: $accent;
|
||||
li {
|
||||
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 {
|
||||
font-size: $font-13px;
|
||||
color: var(--cpd-color-text-secondary);
|
||||
color: $secondary-content;
|
||||
}
|
||||
|
||||
.mx_LoginWithQR_main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
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 {
|
||||
|
|
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 { wrapRequestWithDialog } from "../../../utils/UserInteractiveAuth";
|
||||
import LoginWithQRFlow from "./LoginWithQRFlow";
|
||||
|
||||
/**
|
||||
* 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,
|
||||
}
|
||||
import { Click, Mode, Phase } from "./LoginWithQR-types";
|
||||
|
||||
interface IProps {
|
||||
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");
|
||||
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.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { RendezvousFailureReason } from "matrix-js-sdk/src/rendezvous";
|
||||
import React, { ReactNode } from "react";
|
||||
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 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 AccessibleButton from "../elements/AccessibleButton";
|
||||
import QRCode from "../elements/QRCode";
|
||||
import Spinner from "../elements/Spinner";
|
||||
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 { FailureReason, LoginWithQRFailureReason } from "./LoginWithQR";
|
||||
|
||||
interface IProps {
|
||||
interface Props {
|
||||
phase: Phase;
|
||||
code?: string;
|
||||
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
|
||||
*/
|
||||
export default class LoginWithQRFlow extends React.Component<IProps> {
|
||||
public constructor(props: IProps) {
|
||||
export default class LoginWithQRFlow extends React.Component<Props> {
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
|
@ -72,49 +77,75 @@ export default class LoginWithQRFlow extends React.Component<IProps> {
|
|||
let main: JSX.Element | undefined;
|
||||
let buttons: JSX.Element | undefined;
|
||||
let backButton = true;
|
||||
let cancellationMessage: string | undefined;
|
||||
let centreTitle = false;
|
||||
let className = "";
|
||||
|
||||
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) {
|
||||
case RendezvousFailureReason.Expired:
|
||||
cancellationMessage = _t("auth|qr_code_login|error_linking_incomplete");
|
||||
case LegacyRendezvousFailureReason.UnsupportedAlgorithm:
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
case RendezvousFailureReason.OtherDeviceNotSignedIn:
|
||||
cancellationMessage = _t("auth|qr_code_login|error_device_not_signed_in");
|
||||
break;
|
||||
case RendezvousFailureReason.UserCancelled:
|
||||
cancellationMessage = _t("auth|qr_code_login|error_request_cancelled");
|
||||
|
||||
case LegacyRendezvousFailureReason.UserDeclined:
|
||||
title = _t("auth|qr_code_login|error_user_declined_title");
|
||||
message = _t("auth|qr_code_login|error_user_declined");
|
||||
break;
|
||||
|
||||
case LoginWithQRFailureReason.RateLimited:
|
||||
cancellationMessage = _t("auth|qr_code_login|error_rate_limited");
|
||||
break;
|
||||
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");
|
||||
title = _t("error|something_went_wrong");
|
||||
message = _t("auth|qr_code_login|error_rate_limited");
|
||||
break;
|
||||
|
||||
case LegacyRendezvousFailureReason.OtherDeviceNotSignedIn:
|
||||
case LegacyRendezvousFailureReason.Unknown:
|
||||
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;
|
||||
}
|
||||
centreTitle = true;
|
||||
className = "mx_LoginWithQR_error";
|
||||
backButton = false;
|
||||
main = <p data-testid="cancellation-message">{cancellationMessage}</p>;
|
||||
buttons = (
|
||||
<>
|
||||
<AccessibleButton
|
||||
|
@ -127,7 +158,23 @@ export default class LoginWithQRFlow extends React.Component<IProps> {
|
|||
{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;
|
||||
}
|
||||
case Phase.Connected:
|
||||
backButton = false;
|
||||
main = (
|
||||
|
@ -145,13 +192,6 @@ export default class LoginWithQRFlow extends React.Component<IProps> {
|
|||
|
||||
buttons = (
|
||||
<>
|
||||
<AccessibleButton
|
||||
data-testid="decline-login-button"
|
||||
kind="primary_outline"
|
||||
onClick={this.handleClick(Click.Decline)}
|
||||
>
|
||||
{_t("action|cancel")}
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
data-testid="approve-login-button"
|
||||
kind="primary"
|
||||
|
@ -159,23 +199,28 @@ export default class LoginWithQRFlow extends React.Component<IProps> {
|
|||
>
|
||||
{_t("action|approve")}
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
data-testid="decline-login-button"
|
||||
kind="primary_outline"
|
||||
onClick={this.handleClick(Click.Decline)}
|
||||
>
|
||||
{_t("action|cancel")}
|
||||
</AccessibleButton>
|
||||
</>
|
||||
);
|
||||
break;
|
||||
case Phase.ShowingQR:
|
||||
if (this.props.code) {
|
||||
const code = (
|
||||
<div className="mx_LoginWithQR_qrWrapper">
|
||||
<QRCode
|
||||
data={[{ data: Buffer.from(this.props.code ?? ""), mode: "byte" }]}
|
||||
className="mx_QRCode"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
const data = Buffer.from(this.props.code ?? "");
|
||||
|
||||
main = (
|
||||
<>
|
||||
<h1>{_t("auth|qr_code_login|scan_code_instruction")}</h1>
|
||||
{code}
|
||||
<Heading as="h1" size="sm" weight="semibold">
|
||||
{_t("auth|qr_code_login|scan_code_instruction")}
|
||||
</Heading>
|
||||
<div className="mx_LoginWithQR_qrWrapper">
|
||||
<QRCode data={[{ data, mode: "byte" }]} className="mx_QRCode" />
|
||||
</div>
|
||||
<ol>
|
||||
<li>
|
||||
{_t("auth|qr_code_login|open_element_other_device", {
|
||||
|
@ -209,30 +254,27 @@ export default class LoginWithQRFlow extends React.Component<IProps> {
|
|||
buttons = this.cancelButton();
|
||||
break;
|
||||
case Phase.Verifying:
|
||||
centreTitle = true;
|
||||
main = this.simpleSpinner(_t("auth|qr_code_login|completing_setup"));
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-testid="login-with-qr" className="mx_LoginWithQR">
|
||||
<div className={centreTitle ? "mx_LoginWithQR_centreTitle" : ""}>
|
||||
{backButton ? (
|
||||
<div className="mx_LoginWithQR_heading">
|
||||
<AccessibleButton
|
||||
data-testid="back-button"
|
||||
className="mx_LoginWithQR_BackButton"
|
||||
onClick={this.handleClick(Click.Back)}
|
||||
title="Back"
|
||||
>
|
||||
<ChevronLeftIcon />
|
||||
</AccessibleButton>
|
||||
<div className="mx_LoginWithQR_breadcrumbs">
|
||||
{_t("settings|sessions|title")} / {_t("settings|sessions|sign_in_with_qr")}
|
||||
</div>
|
||||
<div data-testid="login-with-qr" className={classNames("mx_LoginWithQR", className)}>
|
||||
{backButton ? (
|
||||
<div className="mx_LoginWithQR_heading">
|
||||
<AccessibleButton
|
||||
data-testid="back-button"
|
||||
className="mx_LoginWithQR_BackButton"
|
||||
onClick={this.handleClick(Click.Back)}
|
||||
title="Back"
|
||||
>
|
||||
<ChevronLeftIcon />
|
||||
</AccessibleButton>
|
||||
<div className="mx_LoginWithQR_breadcrumbs">
|
||||
{_t("settings|sessions|title")} / {_t("settings|sessions|sign_in_with_qr")}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="mx_LoginWithQR_main">{main}</div>
|
||||
<div className="mx_LoginWithQR_buttons">{buttons}</div>
|
||||
</div>
|
||||
|
|
|
@ -35,39 +35,40 @@ interface IProps {
|
|||
wellKnown?: IClientWellKnown;
|
||||
}
|
||||
|
||||
export default class LoginWithQRSection extends React.Component<IProps> {
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
public render(): JSX.Element | null {
|
||||
// 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
|
||||
const capability = GET_LOGIN_TOKEN_CAPABILITY.findIn<IGetLoginTokenCapability>(this.props.capabilities);
|
||||
const getLoginTokenSupported =
|
||||
!!this.props.versions?.unstable_features?.["org.matrix.msc3882"] || !!capability?.enabled;
|
||||
const msc3886Supported =
|
||||
!!this.props.versions?.unstable_features?.["org.matrix.msc3886"] ||
|
||||
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>
|
||||
);
|
||||
}
|
||||
function shouldShowQrLegacy(
|
||||
versions?: IServerVersions,
|
||||
wellKnown?: IClientWellKnown,
|
||||
capabilities?: Capabilities,
|
||||
): boolean {
|
||||
// Needs server support for (get_login_token or OIDC Device Authorization Grant) and MSC3886:
|
||||
// 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 getLoginTokenSupported =
|
||||
!!versions?.unstable_features?.["org.matrix.msc3882"] || !!loginTokenCapability?.enabled;
|
||||
const msc3886Supported =
|
||||
!!versions?.unstable_features?.["org.matrix.msc3886"] || !!wellKnown?.["io.element.rendezvous"]?.server;
|
||||
return getLoginTokenSupported && msc3886Supported;
|
||||
}
|
||||
|
||||
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 SettingsTab from "../SettingsTab";
|
||||
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 QuestionDialog from "../../../dialogs/QuestionDialog";
|
||||
import { FilterVariation } from "../../devices/filter";
|
||||
|
@ -284,6 +285,12 @@ const SessionManagerTab: React.FC = () => {
|
|||
return (
|
||||
<SettingsTab>
|
||||
<SettingsSection heading={_t("settings|sessions|title")}>
|
||||
<LoginWithQRSection
|
||||
onShowQr={onShowQrClicked}
|
||||
versions={clientVersions}
|
||||
capabilities={capabilities}
|
||||
wellKnown={wellKnown}
|
||||
/>
|
||||
<SecurityRecommendations
|
||||
devices={devices}
|
||||
goToFilteredList={onGoToFilteredList}
|
||||
|
@ -337,12 +344,6 @@ const SessionManagerTab: React.FC = () => {
|
|||
/>
|
||||
</SettingsSubsection>
|
||||
)}
|
||||
<LoginWithQRSection
|
||||
onShowQr={onShowQrClicked}
|
||||
versions={clientVersions}
|
||||
capabilities={capabilities}
|
||||
wellKnown={wellKnown}
|
||||
/>
|
||||
</SettingsSection>
|
||||
</SettingsTab>
|
||||
);
|
||||
|
|
|
@ -249,21 +249,29 @@
|
|||
"completing_setup": "Completing set up of your new device",
|
||||
"confirm_code_match": "Check that the code below matches with your other device:",
|
||||
"connecting": "Connecting…",
|
||||
"error_device_already_signed_in": "The other device is already signed in.",
|
||||
"error_device_not_signed_in": "The other device isn't signed in.",
|
||||
"error_device_unsupported": "Linking with this device is not supported.",
|
||||
"error_homeserver_lacks_support": "The homeserver doesn't support signing in another device.",
|
||||
"error_invalid_scanned_code": "The scanned code is invalid.",
|
||||
"error_linking_incomplete": "The linking wasn't completed in the required time.",
|
||||
"error_expired": "Sign in expired. Please try again.",
|
||||
"error_expired_title": "The sign in was not completed in time",
|
||||
"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_insecure_channel_detected_instructions": "Now what?",
|
||||
"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_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_request_cancelled": "The request was cancelled.",
|
||||
"error_request_declined": "The request was declined on the other device.",
|
||||
"error_unexpected": "An unexpected error occurred.",
|
||||
"follow_remaining_instructions": "Follow the remaining instructions to verify your other device",
|
||||
"error_unexpected": "An unexpected error occurred. The request to connect your other device has been cancelled.",
|
||||
"error_unsupported_protocol": "This device does not support signing in to the other device with a QR code.",
|
||||
"error_unsupported_protocol_title": "Other device not compatible",
|
||||
"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",
|
||||
"point_the_camera": "Point the camera at the QR code shown here",
|
||||
"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\"",
|
||||
"sign_in_new_device": "Sign in new device",
|
||||
"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 { 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";
|
||||
|
||||
jest.mock("matrix-js-sdk/src/rendezvous");
|
||||
|
|
|
@ -19,12 +19,8 @@ import React from "react";
|
|||
import { RendezvousFailureReason } from "matrix-js-sdk/src/rendezvous";
|
||||
|
||||
import LoginWithQRFlow from "../../../../../src/components/views/auth/LoginWithQRFlow";
|
||||
import {
|
||||
Click,
|
||||
Phase,
|
||||
LoginWithQRFailureReason,
|
||||
FailureReason,
|
||||
} from "../../../../../src/components/views/auth/LoginWithQR";
|
||||
import { LoginWithQRFailureReason, FailureReason } from "../../../../../src/components/views/auth/LoginWithQR";
|
||||
import { Click, Phase } from "../../../../../src/components/views/auth/LoginWithQR-types";
|
||||
|
||||
describe("<LoginWithQRFlow />", () => {
|
||||
const onClick = jest.fn();
|
||||
|
|
|
@ -3,19 +3,28 @@
|
|||
exports[`<LoginWithQRFlow /> errors renders data_mismatch 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR"
|
||||
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_centreTitle"
|
||||
/>
|
||||
<div
|
||||
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
|
||||
data-testid="cancellation-message"
|
||||
>
|
||||
The request was cancelled.
|
||||
An unexpected error occurred. The request to connect your other device has been cancelled.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
|
@ -45,19 +54,28 @@ exports[`<LoginWithQRFlow /> errors renders data_mismatch 1`] = `
|
|||
exports[`<LoginWithQRFlow /> errors renders expired 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR"
|
||||
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_centreTitle"
|
||||
/>
|
||||
<div
|
||||
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
|
||||
data-testid="cancellation-message"
|
||||
>
|
||||
The linking wasn't completed in the required time.
|
||||
Sign in expired. Please try again.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
|
@ -87,19 +105,28 @@ exports[`<LoginWithQRFlow /> errors renders expired 1`] = `
|
|||
exports[`<LoginWithQRFlow /> errors renders homeserver_lacks_support 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR"
|
||||
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_centreTitle"
|
||||
/>
|
||||
<div
|
||||
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
|
||||
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>
|
||||
</div>
|
||||
<div
|
||||
|
@ -129,20 +156,42 @@ exports[`<LoginWithQRFlow /> errors renders homeserver_lacks_support 1`] = `
|
|||
exports[`<LoginWithQRFlow /> errors renders invalid_code 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR"
|
||||
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_centreTitle"
|
||||
/>
|
||||
<div
|
||||
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"
|
||||
>
|
||||
The scanned code is invalid.
|
||||
</p>
|
||||
Now what?
|
||||
</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
|
||||
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`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR"
|
||||
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_centreTitle"
|
||||
/>
|
||||
<div
|
||||
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
|
||||
data-testid="cancellation-message"
|
||||
>
|
||||
The other device is already signed in.
|
||||
You don’t need to do anything else.
|
||||
</p>
|
||||
</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`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR"
|
||||
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_centreTitle"
|
||||
/>
|
||||
<div
|
||||
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
|
||||
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>
|
||||
</div>
|
||||
<div
|
||||
|
@ -255,15 +322,24 @@ exports[`<LoginWithQRFlow /> errors renders other_device_not_signed_in 1`] = `
|
|||
exports[`<LoginWithQRFlow /> errors renders rate_limited 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR"
|
||||
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_centreTitle"
|
||||
/>
|
||||
<div
|
||||
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
|
||||
data-testid="cancellation-message"
|
||||
>
|
||||
|
@ -297,19 +373,28 @@ exports[`<LoginWithQRFlow /> errors renders rate_limited 1`] = `
|
|||
exports[`<LoginWithQRFlow /> errors renders unknown 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR"
|
||||
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_centreTitle"
|
||||
/>
|
||||
<div
|
||||
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
|
||||
data-testid="cancellation-message"
|
||||
>
|
||||
An unexpected error occurred.
|
||||
An unexpected error occurred. The request to connect your other device has been cancelled.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
|
@ -339,19 +424,28 @@ exports[`<LoginWithQRFlow /> errors renders unknown 1`] = `
|
|||
exports[`<LoginWithQRFlow /> errors renders unsupported_algorithm 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR"
|
||||
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_centreTitle"
|
||||
/>
|
||||
<div
|
||||
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
|
||||
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>
|
||||
</div>
|
||||
<div
|
||||
|
@ -381,19 +475,28 @@ exports[`<LoginWithQRFlow /> errors renders unsupported_algorithm 1`] = `
|
|||
exports[`<LoginWithQRFlow /> errors renders unsupported_transport 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR"
|
||||
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_centreTitle"
|
||||
/>
|
||||
<div
|
||||
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
|
||||
data-testid="cancellation-message"
|
||||
>
|
||||
The request was cancelled.
|
||||
This device does not support signing in to the other device with a QR code.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
|
@ -423,19 +526,28 @@ exports[`<LoginWithQRFlow /> errors renders unsupported_transport 1`] = `
|
|||
exports[`<LoginWithQRFlow /> errors renders user_cancelled 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR"
|
||||
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_centreTitle"
|
||||
/>
|
||||
<div
|
||||
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
|
||||
data-testid="cancellation-message"
|
||||
>
|
||||
The request was cancelled.
|
||||
The sign in was cancelled on the other device.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
|
@ -465,19 +577,28 @@ exports[`<LoginWithQRFlow /> errors renders user_cancelled 1`] = `
|
|||
exports[`<LoginWithQRFlow /> errors renders user_declined 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR"
|
||||
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_centreTitle"
|
||||
/>
|
||||
<div
|
||||
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
|
||||
data-testid="cancellation-message"
|
||||
>
|
||||
The request was declined on the other device.
|
||||
You declined the request from your other device to sign in.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
|
@ -511,34 +632,32 @@ exports[`<LoginWithQRFlow /> renders QR code 1`] = `
|
|||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class=""
|
||||
class="mx_LoginWithQR_heading"
|
||||
>
|
||||
<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
|
||||
aria-label="Back"
|
||||
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
|
||||
data-state="closed"
|
||||
data-testid="back-button"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_breadcrumbs"
|
||||
>
|
||||
Sessions
|
||||
/
|
||||
Link new device
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_breadcrumbs"
|
||||
>
|
||||
Sessions
|
||||
/
|
||||
Link new device
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_main"
|
||||
>
|
||||
<h1>
|
||||
<h1
|
||||
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
|
||||
>
|
||||
Scan the QR code with another device
|
||||
</h1>
|
||||
<div
|
||||
|
@ -562,7 +681,7 @@ exports[`<LoginWithQRFlow /> renders QR code 1`] = `
|
|||
<span>
|
||||
Select "
|
||||
<b>
|
||||
Scan QR code
|
||||
Sign in with QR code
|
||||
</b>
|
||||
"
|
||||
</span>
|
||||
|
@ -571,7 +690,7 @@ exports[`<LoginWithQRFlow /> renders QR code 1`] = `
|
|||
Point the camera at the QR code shown here
|
||||
</li>
|
||||
<li>
|
||||
Follow the remaining instructions to verify your other device
|
||||
Follow the instructions to link your other device
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
@ -588,9 +707,6 @@ exports[`<LoginWithQRFlow /> renders code when connected 1`] = `
|
|||
class="mx_LoginWithQR"
|
||||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class=""
|
||||
/>
|
||||
<div
|
||||
class="mx_LoginWithQR_main"
|
||||
>
|
||||
|
@ -616,14 +732,6 @@ exports[`<LoginWithQRFlow /> renders code when connected 1`] = `
|
|||
<div
|
||||
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
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
data-testid="approve-login-button"
|
||||
|
@ -632,6 +740,14 @@ exports[`<LoginWithQRFlow /> renders code when connected 1`] = `
|
|||
>
|
||||
Approve
|
||||
</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>
|
||||
|
@ -644,28 +760,24 @@ exports[`<LoginWithQRFlow /> renders spinner while connecting 1`] = `
|
|||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class=""
|
||||
class="mx_LoginWithQR_heading"
|
||||
>
|
||||
<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
|
||||
aria-label="Back"
|
||||
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
|
||||
data-state="closed"
|
||||
data-testid="back-button"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_breadcrumbs"
|
||||
>
|
||||
Sessions
|
||||
/
|
||||
Link new device
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_breadcrumbs"
|
||||
>
|
||||
Sessions
|
||||
/
|
||||
Link new device
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
@ -715,28 +827,24 @@ exports[`<LoginWithQRFlow /> renders spinner while loading 1`] = `
|
|||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class=""
|
||||
class="mx_LoginWithQR_heading"
|
||||
>
|
||||
<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
|
||||
aria-label="Back"
|
||||
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
|
||||
data-state="closed"
|
||||
data-testid="back-button"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_breadcrumbs"
|
||||
>
|
||||
Sessions
|
||||
/
|
||||
Link new device
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_breadcrumbs"
|
||||
>
|
||||
Sessions
|
||||
/
|
||||
Link new device
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
@ -774,28 +882,24 @@ exports[`<LoginWithQRFlow /> renders spinner while signing in 1`] = `
|
|||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class=""
|
||||
class="mx_LoginWithQR_heading"
|
||||
>
|
||||
<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
|
||||
aria-label="Back"
|
||||
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
|
||||
data-state="closed"
|
||||
data-testid="back-button"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_breadcrumbs"
|
||||
>
|
||||
Sessions
|
||||
/
|
||||
Link new device
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_breadcrumbs"
|
||||
>
|
||||
Sessions
|
||||
/
|
||||
Link new device
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
@ -845,28 +949,24 @@ exports[`<LoginWithQRFlow /> renders spinner while verifying 1`] = `
|
|||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_centreTitle"
|
||||
class="mx_LoginWithQR_heading"
|
||||
>
|
||||
<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
|
||||
aria-label="Back"
|
||||
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
|
||||
data-state="closed"
|
||||
data-testid="back-button"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_breadcrumbs"
|
||||
>
|
||||
Sessions
|
||||
/
|
||||
Link new device
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_breadcrumbs"
|
||||
>
|
||||
Sessions
|
||||
/
|
||||
Link new device
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
@ -907,28 +1007,24 @@ exports[`<LoginWithQRFlow /> renders spinner whilst QR generating 1`] = `
|
|||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class=""
|
||||
class="mx_LoginWithQR_heading"
|
||||
>
|
||||
<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
|
||||
aria-label="Back"
|
||||
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
|
||||
data-state="closed"
|
||||
data-testid="back-button"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_breadcrumbs"
|
||||
>
|
||||
Sessions
|
||||
/
|
||||
Link new device
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_breadcrumbs"
|
||||
>
|
||||
Sessions
|
||||
/
|
||||
Link new device
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
|
Loading…
Reference in a new issue