From a5d9062420120a6fd7a80f8269ca8ec33a7ca100 Mon Sep 17 00:00:00 2001 From: Jimmy Vo Date: Wed, 4 Dec 2024 16:46:25 -0500 Subject: [PATCH] Separate dialog events into invite and edit dialogs to eliminate the need for nullable fields. --- .../common/people-table-data-source.ts | 4 ++ .../member-dialog/member-dialog.component.ts | 48 ++++++++++++------- .../org-seat-limit-reached.validator.ts | 29 ++++++++--- .../members/members.component.ts | 17 +++++-- .../member-access-report.component.ts | 2 +- 5 files changed, 72 insertions(+), 28 deletions(-) diff --git a/apps/web/src/app/admin-console/common/people-table-data-source.ts b/apps/web/src/app/admin-console/common/people-table-data-source.ts index 5ce7e7bda7d..f49a6eac050 100644 --- a/apps/web/src/app/admin-console/common/people-table-data-source.ts +++ b/apps/web/src/app/admin-console/common/people-table-data-source.ts @@ -75,6 +75,10 @@ export abstract class PeopleTableDataSource extends Tab return super.data; } + get occupiedSeatCount(): number { + return this.activeUserCount; + } + /** * Check or uncheck a user in the table * @param select check the user (true), uncheck the user (false), or toggle the current state (null) diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts index fe83acf0bc3..53ac8164f59 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts @@ -58,11 +58,24 @@ export enum MemberDialogTab { Collections = 2, } -export interface MemberDialogParams { +export interface EditMemberDialogParams { + kind: "EditMemberDialogParams"; name: string; organizationId: string; organizationUserId: string; - activeUserCount?: number; + usesKeyConnector: boolean; + isOnSecretsManagerStandalone: boolean; + initialTab?: MemberDialogTab; + numConfirmedMembers: number; + managedByOrganization?: boolean; +} + +export interface InviteMemberDialogParams { + kind: "InviteMemberDialogParams"; + name: string; + organizationId: string; + organizationUserId: string; + occupiedSeatCount: number; allOrganizationUserEmails: string[]; usesKeyConnector: boolean; isOnSecretsManagerStandalone: boolean; @@ -71,6 +84,8 @@ export interface MemberDialogParams { managedByOrganization?: boolean; } +export type MemberDialogParams = InviteMemberDialogParams | EditMemberDialogParams; + export enum MemberDialogResult { Saved = "saved", Canceled = "canceled", @@ -262,20 +277,21 @@ export class MemberDialogComponent implements OnDestroy { } private setFormValidators(organization: Organization) { - const emailsControlValidators = [ - Validators.required, - commaSeparatedEmails, - orgSeatLimitReachedValidator( - organization, - this.params.allOrganizationUserEmails, - this.i18nService.t("subscriptionUpgrade", organization.seats), - this.params.activeUserCount, - ), - ]; - - const emailsControl = this.formGroup.get("emails"); - emailsControl.setValidators(emailsControlValidators); - emailsControl.updateValueAndValidity(); + if (this.params.kind === "InviteMemberDialogParams") { + const emailsControlValidators = [ + Validators.required, + commaSeparatedEmails, + orgSeatLimitReachedValidator( + organization, + this.params.allOrganizationUserEmails, + this.i18nService.t("subscriptionUpgrade", organization.seats), + this.params.occupiedSeatCount, + ), + ]; + const emailsControl = this.formGroup.get("emails"); + emailsControl.setValidators(emailsControlValidators); + emailsControl.updateValueAndValidity(); + } } private loadOrganizationUser( diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/validators/org-seat-limit-reached.validator.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/validators/org-seat-limit-reached.validator.ts index 5dc695043fb..87f6d7bc456 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/validators/org-seat-limit-reached.validator.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/validators/org-seat-limit-reached.validator.ts @@ -16,19 +16,14 @@ export function orgSeatLimitReachedValidator( organization: Organization, allOrganizationUserEmails: string[], errorMessage: string, - activeUserCount?: number, + activeUserCount: number, ): ValidatorFn { return (control: AbstractControl): ValidationErrors | null => { if (control.value === "" || !control.value) { return null; } - const productHasAdditionalSeatsOption = - organization.productTierType !== ProductTierType.Free && - organization.productTierType !== ProductTierType.Families && - organization.productTierType !== ProductTierType.TeamsStarter; - - if (productHasAdditionalSeatsOption || !activeUserCount) { + if (isDynamicSeatPlan(organization.productTierType)) { return null; } @@ -43,6 +38,26 @@ export function orgSeatLimitReachedValidator( }; } +export function isDynamicSeatPlan(productTierType: ProductTierType): boolean { + switch (productTierType) { + case ProductTierType.Enterprise: + return true; + default: + return false; + } +} + +export function isFixedSeatPlan(productTierType: ProductTierType): boolean { + switch (productTierType) { + case ProductTierType.Free: + case ProductTierType.Families: + case ProductTierType.TeamsStarter: + return true; + default: + return false; + } +} + function getUniqueNewEmailCount( allOrganizationUserEmails: string[], control: AbstractControl, diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.ts b/apps/web/src/app/admin-console/organizations/members/members.component.ts index e0239709e23..0b771fa6730 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/members.component.ts @@ -71,6 +71,7 @@ import { MemberDialogTab, openUserAddEditDialog, } from "./components/member-dialog"; +import { isFixedSeatPlan } from "./components/member-dialog/validators/org-seat-limit-reached.validator"; import { ResetPasswordComponent, ResetPasswordDialogResult, @@ -463,6 +464,15 @@ export class MembersComponent extends BaseMembersComponent } async edit(user: OrganizationUserView, initialTab: MemberDialogTab = MemberDialogTab.Role) { + console.log("Jimmy showToast"); + // Jimmy this is what they mean by toast. + // Jimmy It sounds like we need to display a different message depending on the if it's a reseller. + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("seatLimitReached"), + message: this.i18nService.t("contactYourProvider"), + }); + if ( !user && this.organization.hasReseller && @@ -483,9 +493,7 @@ export class MembersComponent extends BaseMembersComponent if ( !user && this.dataSource.activeUserCount === this.organization.seats && - (this.organization.productTierType === ProductTierType.Free || - this.organization.productTierType === ProductTierType.TeamsStarter || - this.organization.productTierType === ProductTierType.Families) + isFixedSeatPlan(this.organization.productTierType) ) { const reference = openChangePlanDialog(this.dialogService, { data: { @@ -505,10 +513,11 @@ export class MembersComponent extends BaseMembersComponent const dialog = openUserAddEditDialog(this.dialogService, { data: { + kind: "InviteMemberDialogParams", name: this.userNamePipe.transform(user), organizationId: this.organization.id, organizationUserId: user != null ? user.id : null, - activeUserCount: this.dataSource.activeUserCount, + occupiedSeatCount: this.dataSource.occupiedSeatCount, allOrganizationUserEmails: this.dataSource.data?.map((user) => user.email) ?? [], usesKeyConnector: user?.usesKeyConnector, isOnSecretsManagerStandalone: this.orgIsOnSecretsManagerStandalone, diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts index c547c53d739..f4f50a6dd3e 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts @@ -94,10 +94,10 @@ export class MemberAccessReportComponent implements OnInit { edit = async (user: MemberAccessReportView | null): Promise => { const dialog = openUserAddEditDialog(this.dialogService, { data: { + kind: "EditMemberDialogParams", name: this.userNamePipe.transform(user), organizationId: this.organizationId, organizationUserId: user != null ? user.userGuid : null, - allOrganizationUserEmails: this.dataSource.data?.map((user) => user.email) ?? [], usesKeyConnector: user?.usesKeyConnector, isOnSecretsManagerStandalone: this.orgIsOnSecretsManagerStandalone, initialTab: MemberDialogTab.Role,