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:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user