"Verify this device" redesign (#30596)

* add variant of ResetIdentityBody for when the user has no verif. methods

* no longer distinguish between the using having a passphrase or not

* use vertical stack of buttons via EncryptionCard

and update wording

* swap logic order to match rendering order

* use the same dialog when no verification options available

* make it agree with the design more

* allow signing out on initial login

* apply styling changes and remove duplicate elements

* fix and add tests

* add missing snapshot

* Apply suggestions from code review

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>

* use a boolean property to disable blurring instead of adding a class

* change string identifiers

* apply changes from review -- simplify logic

* change class name to avoid confusion

---------

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
This commit is contained in:
Hubert Chathi
2025-09-12 14:37:14 -04:00
committed by GitHub
parent 1e0cdf7b14
commit 9ad239f87f
23 changed files with 583 additions and 208 deletions

View File

@@ -83,13 +83,66 @@ describe("CompleteSecurity", () => {
jest.spyOn(SetupEncryptionStore, "sharedInstance").mockReturnValue(store);
const panel = await act(() => render(<CompleteSecurity onFinished={() => {}} />));
// No recovery methods are available, so only the "Can't confirm?" button should be visible
expect(screen.queryByRole("button", { name: "Can't confirm?" })).toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Use another device" })).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Use recovery key" })).not.toBeInTheDocument();
// When we hit reset
await act(async () => panel.getByRole("button", { name: "Proceed with reset" }).click());
await act(async () => panel.getByRole("button", { name: "Can't confirm?" }).click());
// Then the reset identity dialog appears
expect(screen.getByRole("heading", { name: "You need to reset your identity" })).toBeInTheDocument();
expect(panel.getByRole("button", { name: "Continue" })).toBeInTheDocument();
});
it("Allows verifying with another device if one is available", async () => {
// Given a store and a dialog based on it
const store = new SetupEncryptionStore();
jest.spyOn(store, "fetchKeyInfo").mockImplementation(async () => {
store.hasDevicesToVerifyAgainst = true;
store.phase = Phase.Intro;
store.emit("update");
});
jest.spyOn(SetupEncryptionStore, "sharedInstance").mockReturnValue(store);
const panel = await act(() => render(<CompleteSecurity onFinished={() => {}} />));
// The snapshot should have "Use another device" and "Can't confirm?"
// buttons, but no "Use recovery key".
expect(panel.asFragment()).toMatchSnapshot();
// When we hit reset
await act(async () => panel.getByRole("button", { name: "Can't confirm?" }).click());
// Then the reset identity dialog appears, and should have a different
// title from when there were no verification methods available.
expect(
screen.getByRole("heading", { name: "Are you sure you want to reset your identity?" }),
).toBeInTheDocument();
});
it("Allows verifying with recovery key if one is available", async () => {
// Given a store and a dialog based on it
const store = new SetupEncryptionStore();
jest.spyOn(store, "fetchKeyInfo").mockImplementation(async () => {
store.keyInfo = {} as any;
store.phase = Phase.Intro;
store.emit("update");
});
jest.spyOn(SetupEncryptionStore, "sharedInstance").mockReturnValue(store);
const panel = await act(() => render(<CompleteSecurity onFinished={() => {}} />));
// The snapshot should have "Use recovery key" and "Can't confirm?"
// buttons, but no "Use another device".
expect(panel.asFragment()).toMatchSnapshot();
// When we hit reset
await act(async () => panel.getByRole("button", { name: "Can't confirm?" }).click());
// Then the reset identity dialog appears, and should have a different
// title from when there were no verification methods available.
expect(
screen.getByRole("heading", { name: "Are you sure you want to reset your identity?" }),
).toBeInTheDocument();
expect(panel.getByRole("button", { name: "Continue" })).toBeInTheDocument();
});
});

View File

@@ -0,0 +1,322 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CompleteSecurity Allows verifying with another device if one is available 1`] = `
<DocumentFragment>
<div
class="mx_AuthPage"
>
<div
class="mx_AuthPage_modal"
style="position: relative;"
>
<div
class="mx_AuthPage_modalContent"
style="display: flex; z-index: 1; border-radius: 8px;"
>
<div
class="mx_Dialog_border _glass_sepwu_8"
>
<div
class="mx_CompleteSecurityBody"
>
<h1
class="mx_CompleteSecurity_header"
>
<div
aria-label="Skip verification for now"
class="mx_AccessibleButton mx_CompleteSecurity_skip"
role="button"
tabindex="0"
/>
</h1>
<div
class="mx_CompleteSecurity_body"
>
<div
class="mx_EncryptionCard mx_EncryptionCard_noBorder mx_SetupEncryptionBody"
>
<div
class="mx_EncryptionCard_header"
>
<div
class="_content_o77nw_8"
data-size="large"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 22q-.824 0-1.412-.587A1.93 1.93 0 0 1 4 20V10q0-.825.588-1.412A1.93 1.93 0 0 1 6 8h1V6q0-2.075 1.463-3.537Q9.926 1 12 1q2.075 0 3.537 1.463Q17 3.925 17 6v2h1q.824 0 1.413.588Q20 9.175 20 10v10q0 .824-.587 1.413A1.93 1.93 0 0 1 18 22zM9 8h6V6q0-1.25-.875-2.125A2.9 2.9 0 0 0 12 3q-1.25 0-2.125.875A2.9 2.9 0 0 0 9 6z"
/>
</svg>
</div>
<h2
class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
>
Confirm your identity
</h2>
</div>
<div
class="flex mx_EncryptionCard_emphasisedContent"
style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: normal; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
>
<span>
Verify this device to set up secure messaging
</span>
<span>
<a
class="mx_ExternalLink"
href="https://element.io/help#encryption-device-verification"
rel="noreferrer noopener"
target="_blank"
>
Learn more
<i
class="mx_ExternalLink_icon"
/>
</a>
</span>
</div>
<div
class="mx_EncryptionCard_buttons"
>
<button
class="_button_vczzf_8"
data-kind="primary"
data-size="lg"
role="button"
tabindex="0"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3.5 20q-.625 0-1.062-.437A1.45 1.45 0 0 1 2 18.5q0-.625.438-1.062A1.45 1.45 0 0 1 3.5 17H4V6q0-.824.588-1.412A1.93 1.93 0 0 1 6 4h14q.424 0 .712.287Q21 4.576 21 5t-.288.713A.97.97 0 0 1 20 6H6v11h4.5q.624 0 1.063.438.437.437.437 1.062t-.437 1.063A1.45 1.45 0 0 1 10.5 20zM15 20a.97.97 0 0 1-.713-.288A.97.97 0 0 1 14 19V9q0-.424.287-.713A.97.97 0 0 1 15 8h6q.424 0 .712.287Q22 8.576 22 9v10q0 .424-.288.712A.97.97 0 0 1 21 20zm1-3h4v-7h-4z"
/>
</svg>
Use another device
</button>
<button
class="_button_vczzf_8"
data-kind="secondary"
data-size="lg"
role="button"
tabindex="0"
>
Can't confirm?
</button>
<button
class="_button_vczzf_8"
data-kind="tertiary"
data-size="lg"
role="button"
tabindex="0"
>
Sign out
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<footer
class="mx_AuthFooter"
role="contentinfo"
>
<a
href="https://element.io/blog"
rel="noreferrer noopener"
target="_blank"
>
Blog
</a>
<a
href="https://mastodon.matrix.org/@Element"
rel="noreferrer noopener"
target="_blank"
>
Mastodon
</a>
<a
href="https://github.com/element-hq/element-web"
rel="noreferrer noopener"
target="_blank"
>
GitHub
</a>
<a
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
Powered by Matrix
</a>
</footer>
</div>
</DocumentFragment>
`;
exports[`CompleteSecurity Allows verifying with recovery key if one is available 1`] = `
<DocumentFragment>
<div
class="mx_AuthPage"
>
<div
class="mx_AuthPage_modal"
style="position: relative;"
>
<div
class="mx_AuthPage_modalContent"
style="display: flex; z-index: 1; border-radius: 8px;"
>
<div
class="mx_Dialog_border _glass_sepwu_8"
>
<div
class="mx_CompleteSecurityBody"
>
<h1
class="mx_CompleteSecurity_header"
>
<div
aria-label="Skip verification for now"
class="mx_AccessibleButton mx_CompleteSecurity_skip"
role="button"
tabindex="0"
/>
</h1>
<div
class="mx_CompleteSecurity_body"
>
<div
class="mx_EncryptionCard mx_EncryptionCard_noBorder mx_SetupEncryptionBody"
>
<div
class="mx_EncryptionCard_header"
>
<div
class="_content_o77nw_8"
data-size="large"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 22q-.824 0-1.412-.587A1.93 1.93 0 0 1 4 20V10q0-.825.588-1.412A1.93 1.93 0 0 1 6 8h1V6q0-2.075 1.463-3.537Q9.926 1 12 1q2.075 0 3.537 1.463Q17 3.925 17 6v2h1q.824 0 1.413.588Q20 9.175 20 10v10q0 .824-.587 1.413A1.93 1.93 0 0 1 18 22zM9 8h6V6q0-1.25-.875-2.125A2.9 2.9 0 0 0 12 3q-1.25 0-2.125.875A2.9 2.9 0 0 0 9 6z"
/>
</svg>
</div>
<h2
class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
>
Confirm your identity
</h2>
</div>
<div
class="flex mx_EncryptionCard_emphasisedContent"
style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: normal; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
>
<span>
Verify this device to set up secure messaging
</span>
<span>
<a
class="mx_ExternalLink"
href="https://element.io/help#encryption-device-verification"
rel="noreferrer noopener"
target="_blank"
>
Learn more
<i
class="mx_ExternalLink_icon"
/>
</a>
</span>
</div>
<div
class="mx_EncryptionCard_buttons"
>
<button
class="_button_vczzf_8"
data-kind="primary"
data-size="lg"
role="button"
tabindex="0"
>
Use recovery key
</button>
<button
class="_button_vczzf_8"
data-kind="secondary"
data-size="lg"
role="button"
tabindex="0"
>
Can't confirm?
</button>
<button
class="_button_vczzf_8"
data-kind="tertiary"
data-size="lg"
role="button"
tabindex="0"
>
Sign out
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<footer
class="mx_AuthFooter"
role="contentinfo"
>
<a
href="https://element.io/blog"
rel="noreferrer noopener"
target="_blank"
>
Blog
</a>
<a
href="https://mastodon.matrix.org/@Element"
rel="noreferrer noopener"
target="_blank"
>
Mastodon
</a>
<a
href="https://github.com/element-hq/element-web"
rel="noreferrer noopener"
target="_blank"
>
GitHub
</a>
<a
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
Powered by Matrix
</a>
</footer>
</div>
</DocumentFragment>
`;