1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-21 02:33:46 +00:00

[SM-453] user onboarding component (#4707)

* wip onboarding component

* fix button type

* remove dismiss button

* add completion logic

* update styles; add download cli section; add click logic; add loading spinner

* update i18n

* update icons; rearrange items; fix import item logic

* add complete i18n

* fix reactivity

* move visibility logic into presentational component

* add button type

* apply code reviews

* add loading spinner to page

* onboarding dismissal should persist when switching orgs

* add workaround for inconsistent icon size

* fix full storybook

* apply code review; update stories
This commit is contained in:
Will Martin
2023-02-21 11:25:41 -05:00
committed by GitHub
parent 7ce4579214
commit 581f69256d
11 changed files with 408 additions and 68 deletions

View File

@@ -1,14 +1,14 @@
import { Component, OnDestroy, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { ActivatedRoute, Router } from "@angular/router";
import {
combineLatest,
combineLatestWith,
map,
Observable,
startWith,
Subject,
switchMap,
Subject,
takeUntil,
combineLatest,
startWith,
distinct,
} from "rxjs";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
@@ -21,7 +21,6 @@ import {
ProjectDeleteOperation,
} from "../projects/dialog/project-delete-dialog.component";
import {
OperationType,
ProjectDialogComponent,
ProjectOperation,
} from "../projects/dialog/project-dialog.component";
@@ -30,41 +29,68 @@ import {
SecretDeleteDialogComponent,
SecretDeleteOperation,
} from "../secrets/dialog/secret-delete.component";
import { SecretDialogComponent, SecretOperation } from "../secrets/dialog/secret-dialog.component";
import {
OperationType,
SecretDialogComponent,
SecretOperation,
} from "../secrets/dialog/secret-dialog.component";
import { SecretService } from "../secrets/secret.service";
import {
ServiceAccountDialogComponent,
ServiceAccountOperation,
} from "../service-accounts/dialog/service-account-dialog.component";
import { ServiceAccountService } from "../service-accounts/service-account.service";
type Tasks = {
importSecrets: boolean;
createSecret: boolean;
createProject: boolean;
createServiceAccount: boolean;
};
@Component({
selector: "sm-overview",
templateUrl: "./overview.component.html",
})
export class OverviewComponent implements OnInit, OnDestroy {
private destroy$ = new Subject<void>();
/**
* Number of items to show in tables
*/
private destroy$: Subject<void> = new Subject<void>();
private tableSize = 10;
private organizationId: string;
protected organizationName: string;
protected view$: Observable<{
allProjects: ProjectListView[];
allSecrets: SecretListView[];
latestProjects: ProjectListView[];
latestSecrets: SecretListView[];
tasks: Tasks;
}>;
constructor(
private route: ActivatedRoute,
private router: Router,
private projectService: ProjectService,
private secretService: SecretService,
private serviceAccountService: ServiceAccountService,
private dialogService: DialogService,
private organizationService: OrganizationService,
private secretService: SecretService
) {}
private organizationService: OrganizationService
) {
/**
* We want to remount the `sm-onboarding` component on route change.
* The component only toggles its visibility on init and on user dismissal.
*/
this.router.routeReuseStrategy.shouldReuseRoute = () => false;
}
ngOnInit() {
this.route.params
const orgId$ = this.route.params.pipe(
map((p) => p.organizationId),
distinct()
);
orgId$
.pipe(
map((params) => this.organizationService.get(params.organizationId)),
map((orgId) => this.organizationService.get(orgId)),
takeUntil(this.destroy$)
)
.subscribe((org) => {
@@ -72,25 +98,33 @@ export class OverviewComponent implements OnInit, OnDestroy {
this.organizationName = org.name;
});
const projects$ = this.projectService.project$.pipe(
startWith(null),
combineLatestWith(this.route.params),
switchMap(() => this.getProjects())
const projects$ = combineLatest([
orgId$,
this.projectService.project$.pipe(startWith(null)),
]).pipe(switchMap(([orgId]) => this.projectService.getProjects(orgId)));
const secrets$ = combineLatest([orgId$, this.secretService.secret$.pipe(startWith(null))]).pipe(
switchMap(([orgId]) => this.secretService.getSecrets(orgId))
);
const secrets$ = this.secretService.secret$.pipe(
startWith(null),
combineLatestWith(this.route.params),
switchMap(() => this.getSecrets())
);
const serviceAccounts$ = combineLatest([
orgId$,
this.serviceAccountService.serviceAccount$.pipe(startWith(null)),
]).pipe(switchMap(([orgId]) => this.serviceAccountService.getServiceAccounts(orgId)));
this.view$ = combineLatest([projects$, secrets$]).pipe(
map(([projects, secrets]) => {
this.view$ = combineLatest([projects$, secrets$, serviceAccounts$]).pipe(
map(([projects, secrets, serviceAccounts]) => {
return {
allProjects: projects,
allSecrets: secrets,
latestProjects: this.getRecentItems(projects, this.tableSize),
latestSecrets: this.getRecentItems(secrets, this.tableSize),
allProjects: projects,
allSecrets: secrets,
tasks: {
importSecrets: secrets.length > 0,
createSecret: secrets.length > 0,
createProject: projects.length > 0,
createServiceAccount: serviceAccounts.length > 0,
},
};
})
);
@@ -111,10 +145,6 @@ export class OverviewComponent implements OnInit, OnDestroy {
// Projects ---
private async getProjects(): Promise<ProjectListView[]> {
return await this.projectService.getProjects(this.organizationId);
}
openEditProject(projectId: string) {
this.dialogService.open<unknown, ProjectOperation>(ProjectDialogComponent, {
data: {
@@ -134,6 +164,14 @@ export class OverviewComponent implements OnInit, OnDestroy {
});
}
openServiceAccountDialog() {
this.dialogService.open<unknown, ServiceAccountOperation>(ServiceAccountDialogComponent, {
data: {
organizationId: this.organizationId,
},
});
}
openDeleteProjectDialog(event: ProjectListView[]) {
this.dialogService.open<unknown, ProjectDeleteOperation>(ProjectDeleteDialogComponent, {
data: {
@@ -144,8 +182,13 @@ export class OverviewComponent implements OnInit, OnDestroy {
// Secrets ---
private async getSecrets(): Promise<SecretListView[]> {
return await this.secretService.getSecrets(this.organizationId);
openSecretDialog() {
this.dialogService.open<unknown, SecretOperation>(SecretDialogComponent, {
data: {
organizationId: this.organizationId,
operation: OperationType.Add,
},
});
}
openEditSecret(secretId: string) {