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>pull/28217/head
							parent
							
								
									1c79bbb1ae
								
							
						
					
					
						commit
						641a20ce63
					
				|  | @ -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 { | ||||
|  |  | |||
|  | @ -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 New Issue
	
	 Michael Telatynski
						Michael Telatynski