1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-18 09:13:33 +00:00

[PM-8214] New Device Verification Notice UI (#12360)

* starting

* setup first page for new device verification notice

* update designs for first page. rename components and files

* added second page for new device verification notice

* update notice page one with bit radio buttons. routing logic. user email

* updated routing for new device verification notice to show before vault based on flags, and can navigate back to vault after submission

* fix translations. added remind me later link and nav to page 2

* sync the design for mobile and web

* update routes in desktop

* updated styles for desktop

* moved new device verification notice guard

* update types for new device notice page one

* add null check to page one

* types

* types for page one, page two, service, and guard

* types

* update component and guard for null check

* add navigation to two step login btn and account email btn

* remove empty file

* update fill of icons to support light & dark modes

* add question mark to email access verification copy

* remove unused map

* use links for navigation elements
- an empty href is needed so the links are keyboard accessible

* remove clip path from exclamation svg

- No noticeable difference in the end result

* inline email message into markup

---------

Co-authored-by: Nick Krantz <nick@livefront.com>
This commit is contained in:
Jason Ng
2024-12-19 09:59:42 -05:00
committed by GitHub
parent 23212fb9ae
commit 1d04a0a399
19 changed files with 540 additions and 4 deletions

View File

@@ -0,0 +1,30 @@
<form [formGroup]="formGroup" [bitSubmit]="submit">
<p class="tw-text-center" bitTypography="body1">
{{ "newDeviceVerificationNoticeContentPage1" | i18n }}
</p>
<bit-card
class="tw-pb-0"
[ngClass]="{
'tw-flex tw-flex-col tw-items-center !tw-rounded-b-none': isDesktop,
'md:tw-flex md:tw-flex-col md:tw-items-center md:!tw-rounded-b-none': !isDesktop,
}"
>
<p bitTypography="body2" class="text-muted md:tw-w-9/12">
{{ "newDeviceVerificationNoticePageOneFormContent" | i18n: this.currentEmail }}
</p>
<bit-radio-group formControlName="hasEmailAccess" class="md:tw-w-9/12">
<bit-radio-button id="option_A" [value]="0">
<bit-label>{{ "newDeviceVerificationNoticePageOneEmailAccessNo" | i18n }}</bit-label>
</bit-radio-button>
<bit-radio-button id="option_B" [value]="1">
<bit-label>{{ "newDeviceVerificationNoticePageOneEmailAccessYes" | i18n }}</bit-label>
</bit-radio-button>
</bit-radio-group>
</bit-card>
<button bitButton type="submit" buttonType="primary" class="tw-w-full tw-mt-4">
{{ "continue" | i18n }}
</button>
</form>

View File

@@ -0,0 +1,82 @@
import { CommonModule } from "@angular/common";
import { Component, OnInit } from "@angular/core";
import { FormBuilder, FormControl, ReactiveFormsModule } from "@angular/forms";
import { Router } from "@angular/router";
import { firstValueFrom, Observable } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { ClientType } from "@bitwarden/common/enums";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { UserId } from "@bitwarden/common/types/guid";
import {
AsyncActionsModule,
ButtonModule,
CardComponent,
FormFieldModule,
RadioButtonModule,
TypographyModule,
} from "@bitwarden/components";
import { NewDeviceVerificationNoticeService } from "./../../services/new-device-verification-notice.service";
@Component({
standalone: true,
selector: "app-new-device-verification-notice-page-one",
templateUrl: "./new-device-verification-notice-page-one.component.html",
imports: [
CardComponent,
CommonModule,
JslibModule,
TypographyModule,
ButtonModule,
RadioButtonModule,
FormFieldModule,
AsyncActionsModule,
ReactiveFormsModule,
],
})
export class NewDeviceVerificationNoticePageOneComponent implements OnInit {
protected formGroup = this.formBuilder.group({
hasEmailAccess: new FormControl(0),
});
protected isDesktop: boolean;
readonly currentAcct$: Observable<Account | null> = this.accountService.activeAccount$;
protected currentEmail: string = "";
private currentUserId: UserId | null = null;
constructor(
private formBuilder: FormBuilder,
private router: Router,
private accountService: AccountService,
private newDeviceVerificationNoticeService: NewDeviceVerificationNoticeService,
private platformUtilsService: PlatformUtilsService,
) {
this.isDesktop = this.platformUtilsService.getClientType() === ClientType.Desktop;
}
async ngOnInit() {
const currentAcct = await firstValueFrom(this.currentAcct$);
if (!currentAcct) {
return;
}
this.currentEmail = currentAcct.email;
this.currentUserId = currentAcct.id;
}
submit = async () => {
if (this.formGroup.controls.hasEmailAccess.value === 0) {
await this.router.navigate(["new-device-notice/setup"]);
} else if (this.formGroup.controls.hasEmailAccess.value === 1) {
await this.newDeviceVerificationNoticeService.updateNewDeviceVerificationNoticeState(
this.currentUserId,
{
last_dismissal: new Date(),
permanent_dismissal: false,
},
);
await this.router.navigate(["/vault"]);
}
};
}

View File

@@ -0,0 +1,39 @@
<p class="tw-text-center" bitTypography="body1">
{{ "newDeviceVerificationNoticeContentPage2" | i18n }}
</p>
<a
href="#"
bitButton
(click)="navigateToTwoStepLogin($event)"
buttonType="primary"
class="tw-w-full tw-mt-4"
>
{{ "turnOnTwoStepLogin" | i18n }}
<i
class="bwi bwi-external-link bwi-lg bwi-fw"
aria-hidden="true"
[ngClass]="{ 'md:tw-hidden': !isDesktop }"
>
</i>
</a>
<a
href="#"
bitButton
(click)="navigateToChangeAcctEmail($event)"
buttonType="secondary"
class="tw-w-full tw-mt-4"
>
{{ "changeAcctEmail" | i18n }}
<i
class="bwi bwi-external-link bwi-lg bwi-fw"
aria-hidden="true"
[ngClass]="{ 'md:tw-hidden': !isDesktop }"
></i>
</a>
<div class="tw-flex tw-justify-center tw-mt-6">
<a bitLink linkType="primary" (click)="remindMeLaterSelect()">
{{ "remindMeLater" | i18n }}
</a>
</div>

View File

@@ -0,0 +1,95 @@
import { CommonModule } from "@angular/common";
import { Component, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { firstValueFrom, Observable } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { ClientType } from "@bitwarden/common/enums";
import {
Environment,
EnvironmentService,
} from "@bitwarden/common/platform/abstractions/environment.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { UserId } from "@bitwarden/common/types/guid";
import { ButtonModule, LinkModule, TypographyModule } from "@bitwarden/components";
import { NewDeviceVerificationNoticeService } from "../../services/new-device-verification-notice.service";
@Component({
standalone: true,
selector: "app-new-device-verification-notice-page-two",
templateUrl: "./new-device-verification-notice-page-two.component.html",
imports: [CommonModule, JslibModule, TypographyModule, ButtonModule, LinkModule],
})
export class NewDeviceVerificationNoticePageTwoComponent implements OnInit {
protected isWeb: boolean;
protected isDesktop: boolean;
readonly currentAcct$: Observable<Account | null> = this.accountService.activeAccount$;
private currentUserId: UserId | null = null;
private env$: Observable<Environment> = this.environmentService.environment$;
constructor(
private newDeviceVerificationNoticeService: NewDeviceVerificationNoticeService,
private router: Router,
private accountService: AccountService,
private platformUtilsService: PlatformUtilsService,
private environmentService: EnvironmentService,
) {
this.isWeb = this.platformUtilsService.getClientType() === ClientType.Web;
this.isDesktop = this.platformUtilsService.getClientType() === ClientType.Desktop;
}
async ngOnInit() {
const currentAcct = await firstValueFrom(this.currentAcct$);
if (!currentAcct) {
return;
}
this.currentUserId = currentAcct.id;
}
async navigateToTwoStepLogin(event: Event) {
event.preventDefault();
const env = await firstValueFrom(this.env$);
const url = env.getWebVaultUrl();
if (this.isWeb) {
await this.router.navigate(["/settings/security/two-factor"], {
queryParams: { fromNewDeviceVerification: true },
});
} else {
this.platformUtilsService.launchUri(
url + "/#/settings/security/two-factor/?fromNewDeviceVerification=true",
);
}
}
async navigateToChangeAcctEmail(event: Event) {
event.preventDefault();
const env = await firstValueFrom(this.env$);
const url = env.getWebVaultUrl();
if (this.isWeb) {
await this.router.navigate(["/settings/account"], {
queryParams: { fromNewDeviceVerification: true },
});
} else {
this.platformUtilsService.launchUri(
url + "/#/settings/account/?fromNewDeviceVerification=true",
);
}
}
async remindMeLaterSelect() {
await this.newDeviceVerificationNoticeService.updateNewDeviceVerificationNoticeState(
this.currentUserId,
{
last_dismissal: new Date(),
permanent_dismissal: false,
},
);
await this.router.navigate(["/vault"]);
}
}