1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-12 06:13:38 +00:00

Feature/SG-878 - Add open simple dialog method on dialog service (#4425)

* SG-878 - First draft - Dialog service now has backwards compatible method for opening a configurable simple dialog.

* SG-878 - People comp - test cases for simple dialog method - more testing required

* SG-878 - Much more simple dialog work - investigating different approaches to see what will work best. Lots of WIP on this one. Includes first draft but working solution for solving placeholder support for non-localized strings.

* SG-878 - (1) Broke out enums and types into separate files for better single responsibility (2) Allow null cancelButtonText for single accept button support

* SG-878 - Configurable simple dialog - removed separate comp approach as it is a maint problem to have simple dialog implemented in two places.

* SG-878 - Added js doc comments for dialog service openSimpleDialog method

* SG-878 - Don't export ConfigurableSimpleDialogComp as only dialogService should use it

* SG-878 - (1) Refactor configurable simple dialog to reduce icon class repetition in html (2) Update simple dialog options to use new Translation interface and update comp to properly process placeholders again

* SG-878 - Reverting all simple dialog changes as, per discussion with Oscar, going to use composition and go with configurable-simple-dialog comp for use in the dialog service

* SG-878 - Testing latest simple dialog changes

* SG-878 - Update simple-dialog-options

* SG-878 - (1) People & collections component now use dialogService.openSimpleDialog vs custom org upgrade dialog comp (2) Rename configurable-simple-dialog to simple-configurable-dialog for better folder placement

* SG-878 - Update formatting of Simple dialog options js doc comments

* SG-878 - Remove test code

* SG-878 -Remove org upgrade dialog component as it has been replaced with dialog service openSimpleDialog method call

* SG-878 - Move models to be near where they are used which is in the simple-configurable-dialog folder.

* SG-878 - Refactor icon classes into simple getter per Oscar's suggestions

* SG-878 - Refactor Translation placeholderValues to be just placeholders

* SG-878 - Refactor Simple Dialog Options to remove isLocalized as it doesn't buy us that much to have it. We can just check if a passed in value is a string or a Translation object to determine if we need to translate it.

* SG-878 - Dialog Svc - remove backdrop classes from openSimpleDialog method as standard open method applies them

* SG-878 - (1) Refactor simple configurable dialog to use comp properties instead of re-using option props (2) Reduce html complexity  (3) Create translate func for code simplification (4)  Remove isTranslation type guard as simple object check is sufficient

* SG-668 - Refactoring collections & people comps use of dialog service openSimpleDialog to condense options per PR feedback

* SG-878 - SimpleConfigDialog - (1) Footer classes were missing so btns were not spaced out properly (2) cancel btn text fixed to reference component property

* SG-878 - First pass at creating a storybook example for the new openSimpleDialog method on the dialogService.

* SG-878 - SimpleConfigurableDialog storybook - now displays callout with dialog results for better example

* SG-878 - SimpleConfigurableDialog - use text-main tailwind class for h2s so that text is colored properly on black background

* SG-878 - SimpleConfigurableDialog - Remove unstyled buttons and colored text and replace with plain secondary buttons to fix visibility issues on dark background.

* Update libs/components/src/dialog/simple-configurable-dialog/models/simple-dialog-type.enum.ts

SG-878 - Remove early commentary about use of simple-dialog-type enum

Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>

* Update libs/components/src/dialog/simple-configurable-dialog/simple-configurable-dialog.component.html

SG-878 - SimpleConfigurableDialog html - consolidate title html to 1 line

Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>

* Update libs/components/src/dialog/simple-configurable-dialog/simple-configurable-dialog.component.ts

SG-878 - SimpleConfigurableDialog comp ts - remove unnecessary comment.

Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>

* Update libs/components/src/dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts

SG-878 - SimpleConfigDialog storybook fixes

Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>

* Update libs/components/src/dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts

SG-878 - SimpleConfigDialog storybook fixes

Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>

* Update libs/components/src/dialog/simple-configurable-dialog/simple-configurable-dialog.component.ts

SG-878 - SimpleConfigDialog comp - remove unnecessary comment

Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>

* SG-668 / SG-878 - Migrate Free Org Upgrade Flows logic from deprecated collections component to vault component

* SG-878 - Refactor the free org upgrade dialog to leverage separate methods to improve code and decrease cyclomatic complexity

Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>
This commit is contained in:
Jared Snider
2023-01-24 16:38:14 -05:00
committed by GitHub
parent 497b08df44
commit d40a891f71
16 changed files with 609 additions and 110 deletions

View File

@@ -1,6 +1,6 @@
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { lastValueFrom } from "rxjs";
import { ActivatedRoute, Router } from "@angular/router";
import { firstValueFrom, lastValueFrom } from "rxjs";
import { first } from "rxjs/operators";
import { ModalService } from "@bitwarden/angular/services/modal.service";
@@ -21,12 +21,16 @@ import {
} from "@bitwarden/common/models/response/collection.response";
import { ListResponse } from "@bitwarden/common/models/response/list.response";
import { CollectionView } from "@bitwarden/common/models/view/collection.view";
import { DialogService } from "@bitwarden/components";
import {
DialogService,
SimpleDialogCloseType,
SimpleDialogOptions,
SimpleDialogType,
} from "@bitwarden/components";
import { CollectionDialogResult, openCollectionDialog } from "../shared";
import { EntityUsersComponent } from "./entity-users.component";
import { OrgUpgradeDialogComponent } from "./org-upgrade-dialog/org-upgrade-dialog.component";
@Component({
selector: "app-org-manage-collections",
@@ -62,7 +66,8 @@ export class CollectionsComponent implements OnInit {
private searchService: SearchService,
private logService: LogService,
private organizationService: OrganizationService,
private dialogService: DialogService
private dialogService: DialogService,
private router: Router
) {}
async ngOnInit() {
@@ -138,23 +143,42 @@ export class CollectionsComponent implements OnInit {
this.collections.length === this.organization.maxCollections
) {
// Show org upgrade modal
const dialogBodyText = this.organization.canManageBilling
? this.i18nService.t(
"freeOrgMaxCollectionReachedManageBilling",
this.organization.maxCollections.toString()
)
: this.i18nService.t(
"freeOrgMaxCollectionReachedNoManageBilling",
this.organization.maxCollections.toString()
);
// It might be worth creating a simple
// org upgrade dialog service to launch the dialog here and in the people.comp
// once the enterprise pod is done w/ their organization module refactor.
const orgUpgradeSimpleDialogOpts: SimpleDialogOptions = {
title: this.i18nService.t("upgradeOrganization"),
content: this.i18nService.t(
this.organization.canManageBilling
? "freeOrgMaxCollectionReachedManageBilling"
: "freeOrgMaxCollectionReachedNoManageBilling",
this.organization.maxCollections
),
type: SimpleDialogType.PRIMARY,
};
this.dialogService.open(OrgUpgradeDialogComponent, {
data: {
orgId: this.organization.id,
dialogBodyText: dialogBodyText,
orgCanManageBilling: this.organization.canManageBilling,
},
if (this.organization.canManageBilling) {
orgUpgradeSimpleDialogOpts.acceptButtonText = this.i18nService.t("upgrade");
} else {
orgUpgradeSimpleDialogOpts.acceptButtonText = this.i18nService.t("ok");
orgUpgradeSimpleDialogOpts.cancelButtonText = null; // hide secondary btn
}
const simpleDialog = this.dialogService.openSimpleDialog(orgUpgradeSimpleDialogOpts);
firstValueFrom(simpleDialog.closed).then((result: SimpleDialogCloseType | undefined) => {
if (!result) {
return;
}
if (result == SimpleDialogCloseType.ACCEPT && this.organization.canManageBilling) {
this.router.navigate(
["/organizations", this.organization.id, "billing", "subscription"],
{ queryParams: { upgrade: true } }
);
}
});
return;
}

View File

@@ -1,33 +0,0 @@
<bit-simple-dialog>
<i
bit-dialog-icon
class="bwi bwi-business tw-text-5xl tw-text-primary-500"
aria-hidden="true"
></i>
<span bitDialogTitle class="font-bold">{{ "upgradeOrganization" | i18n }}</span>
<span bitDialogContent>
{{ data.dialogBodyText }}
</span>
<div bitDialogFooter class="tw-flex tw-flex-row tw-gap-2">
<ng-container *ngIf="data.orgCanManageBilling">
<button
bitButton
buttonType="primary"
[routerLink]="['/organizations', data.orgId, 'billing', 'subscription']"
[queryParams]="{ upgrade: true }"
(click)="dialogRef.close()"
>
{{ "upgrade" | i18n }}
</button>
<button bitButton buttonType="secondary" (click)="dialogRef.close()">
{{ "cancel" | i18n }}
</button>
</ng-container>
<ng-container *ngIf="!data.orgCanManageBilling">
<button bitButton buttonType="primary" (click)="dialogRef.close()">
{{ "ok" | i18n }}
</button>
</ng-container>
</div>
</bit-simple-dialog>

View File

@@ -1,19 +0,0 @@
import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
import { Component, Inject } from "@angular/core";
export interface OrgUpgradeDialogData {
orgId: string;
orgCanManageBilling: boolean;
dialogBodyText: string;
}
@Component({
selector: "app-org-upgrade-dialog",
templateUrl: "org-upgrade-dialog.component.html",
})
export class OrgUpgradeDialogComponent {
constructor(
public dialogRef: DialogRef,
@Inject(DIALOG_DATA) public data: OrgUpgradeDialogData
) {}
}

View File

@@ -1,6 +1,6 @@
import { Component, OnDestroy, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { combineLatest, concatMap, lastValueFrom, Subject, takeUntil } from "rxjs";
import { ActivatedRoute, Router } from "@angular/router";
import { combineLatest, concatMap, firstValueFrom, lastValueFrom, Subject, takeUntil } from "rxjs";
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
@@ -34,13 +34,17 @@ import { Organization } from "@bitwarden/common/models/domain/organization";
import { OrganizationKeysRequest } from "@bitwarden/common/models/request/organization-keys.request";
import { CollectionDetailsResponse } from "@bitwarden/common/models/response/collection.response";
import { ListResponse } from "@bitwarden/common/models/response/list.response";
import { DialogService } from "@bitwarden/components";
import {
DialogService,
SimpleDialogCloseType,
SimpleDialogOptions,
SimpleDialogType,
} from "@bitwarden/components";
import { BasePeopleComponent } from "../../common/base.people.component";
import { GroupService } from "../core";
import { OrganizationUserView } from "../core/views/organization-user.view";
import { EntityEventsComponent } from "../manage/entity-events.component";
import { OrgUpgradeDialogComponent } from "../manage/org-upgrade-dialog/org-upgrade-dialog.component";
import { BulkConfirmComponent } from "./components/bulk/bulk-confirm.component";
import { BulkRemoveComponent } from "./components/bulk/bulk-remove.component";
@@ -105,6 +109,7 @@ export class PeopleComponent
private organizationApiService: OrganizationApiServiceAbstraction,
private organizationUserService: OrganizationUserService,
private dialogService: DialogService,
private router: Router,
private groupService: GroupService,
private collectionService: CollectionService
) {
@@ -306,6 +311,40 @@ export class PeopleComponent
);
}
private async showFreeOrgUpgradeDialog(): Promise<void> {
const orgUpgradeSimpleDialogOpts: SimpleDialogOptions = {
title: this.i18nService.t("upgradeOrganization"),
content: this.i18nService.t(
this.organization.canManageBilling
? "freeOrgInvLimitReachedManageBilling"
: "freeOrgInvLimitReachedNoManageBilling",
this.organization.seats
),
type: SimpleDialogType.PRIMARY,
};
if (this.organization.canManageBilling) {
orgUpgradeSimpleDialogOpts.acceptButtonText = this.i18nService.t("upgrade");
} else {
orgUpgradeSimpleDialogOpts.acceptButtonText = this.i18nService.t("ok");
orgUpgradeSimpleDialogOpts.cancelButtonText = null; // hide secondary btn
}
const simpleDialog = this.dialogService.openSimpleDialog(orgUpgradeSimpleDialogOpts);
firstValueFrom(simpleDialog.closed).then((result: SimpleDialogCloseType | undefined) => {
if (!result) {
return;
}
if (result == SimpleDialogCloseType.ACCEPT && this.organization.canManageBilling) {
this.router.navigate(["/organizations", this.organization.id, "billing", "subscription"], {
queryParams: { upgrade: true },
});
}
});
}
async edit(user: OrganizationUserView, initialTab: MemberDialogTab = MemberDialogTab.Role) {
// Invite User: Add Flow
// Click on user email: Edit Flow
@@ -317,24 +356,7 @@ export class PeopleComponent
this.allUsers.length === this.organization.seats
) {
// Show org upgrade modal
const dialogBodyText = this.organization.canManageBilling
? this.i18nService.t(
"freeOrgInvLimitReachedManageBilling",
this.organization.seats.toString()
)
: this.i18nService.t(
"freeOrgInvLimitReachedNoManageBilling",
this.organization.seats.toString()
);
this.dialogService.open(OrgUpgradeDialogComponent, {
data: {
orgId: this.organization.id,
orgCanManageBilling: this.organization.canManageBilling,
dialogBodyText: dialogBodyText,
},
});
await this.showFreeOrgUpgradeDialog();
return;
}

View File

@@ -3,7 +3,6 @@ import { NgModule } from "@angular/core";
import { CoreOrganizationModule } from "./core";
import { GroupAddEditComponent } from "./manage/group-add-edit.component";
import { GroupsComponent } from "./manage/groups.component";
import { OrgUpgradeDialogComponent } from "./manage/org-upgrade-dialog/org-upgrade-dialog.component";
import { OrganizationsRoutingModule } from "./organization-routing.module";
import { SharedOrganizationModule } from "./shared";
import { AccessSelectorModule } from "./shared/components/access-selector";
@@ -15,6 +14,6 @@ import { AccessSelectorModule } from "./shared/components/access-selector";
CoreOrganizationModule,
OrganizationsRoutingModule,
],
declarations: [GroupsComponent, GroupAddEditComponent, OrgUpgradeDialogComponent],
declarations: [GroupsComponent, GroupAddEditComponent],
})
export class OrganizationModule {}

View File

@@ -20,14 +20,21 @@ import { OrganizationService } from "@bitwarden/common/abstractions/organization
import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { ProductType } from "@bitwarden/common/enums/productType";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { TreeNode } from "@bitwarden/common/models/domain/tree-node";
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
import { DialogService } from "@bitwarden/components";
import {
DialogService,
SimpleDialogCloseType,
SimpleDialogOptions,
SimpleDialogType,
} from "@bitwarden/components";
import { VaultFilterService } from "../../vault/vault-filter/services/abstractions/vault-filter.service";
import { VaultFilter } from "../../vault/vault-filter/shared/models/vault-filter.model";
import { CollectionFilter } from "../../vault/vault-filter/shared/models/vault-filter.type";
import { CollectionAdminService } from "../core";
import { EntityEventsComponent } from "../manage/entity-events.component";
import {
CollectionDialogResult,
@@ -79,7 +86,8 @@ export class VaultComponent implements OnInit, OnDestroy {
private ngZone: NgZone,
private platformUtilsService: PlatformUtilsService,
private cipherService: CipherService,
private passwordRepromptService: PasswordRepromptService
private passwordRepromptService: PasswordRepromptService,
private collectionAdminService: CollectionAdminService
) {}
async ngOnInit() {
@@ -168,7 +176,49 @@ export class VaultComponent implements OnInit, OnDestroy {
this.vaultItemsComponent.search(200);
}
private showFreeOrgUpgradeDialog(): void {
const orgUpgradeSimpleDialogOpts: SimpleDialogOptions = {
title: this.i18nService.t("upgradeOrganization"),
content: this.i18nService.t(
this.organization.canManageBilling
? "freeOrgMaxCollectionReachedManageBilling"
: "freeOrgMaxCollectionReachedNoManageBilling",
this.organization.maxCollections
),
type: SimpleDialogType.PRIMARY,
};
if (this.organization.canManageBilling) {
orgUpgradeSimpleDialogOpts.acceptButtonText = this.i18nService.t("upgrade");
} else {
orgUpgradeSimpleDialogOpts.acceptButtonText = this.i18nService.t("ok");
orgUpgradeSimpleDialogOpts.cancelButtonText = null; // hide secondary btn
}
const simpleDialog = this.dialogService.openSimpleDialog(orgUpgradeSimpleDialogOpts);
firstValueFrom(simpleDialog.closed).then((result: SimpleDialogCloseType | undefined) => {
if (!result) {
return;
}
if (result == SimpleDialogCloseType.ACCEPT && this.organization.canManageBilling) {
this.router.navigate(["/organizations", this.organization.id, "billing", "subscription"], {
queryParams: { upgrade: true },
});
}
});
}
async addCollection() {
if (this.organization.planProductType === ProductType.Free) {
const collections = await this.collectionAdminService.getAll(this.organization.id);
if (collections.length === this.organization.maxCollections) {
this.showFreeOrgUpgradeDialog();
return;
}
}
const dialog = openCollectionDialog(this.dialogService, {
data: {
organizationId: this.organization?.id,