1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-12 14:34:02 +00:00

[PM-13755] Use active user count for seat validation instead of total users.

This commit is contained in:
Jimmy Vo
2024-12-03 15:18:38 -05:00
parent 7d6da0a68d
commit ea6b64dcdb
4 changed files with 38 additions and 20 deletions

View File

@@ -62,6 +62,7 @@ export interface MemberDialogParams {
name: string;
organizationId: string;
organizationUserId: string;
activeUserCount: number;
allOrganizationUserEmails: string[];
usesKeyConnector: boolean;
isOnSecretsManagerStandalone: boolean;
@@ -267,6 +268,7 @@ export class MemberDialogComponent implements OnDestroy {
orgSeatLimitReachedValidator(
organization,
this.params.allOrganizationUserEmails,
this.params.activeUserCount,
this.i18nService.t("subscriptionUpgrade", organization.seats),
),
];

View File

@@ -8,12 +8,14 @@ import { ProductTierType } from "@bitwarden/common/billing/enums";
* new users
* @param organization An object representing the organization
* @param allOrganizationUserEmails An array of strings with existing user email addresses
* @param activeUserCount The current count of active users occupying the organization's seats.
* @param errorMessage A localized string to display if validation fails
* @returns A function that validates an `AbstractControl` and returns `ValidationErrors` or `null`
*/
export function orgSeatLimitReachedValidator(
organization: Organization,
allOrganizationUserEmails: string[],
activeUserCount: number,
errorMessage: string,
): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
@@ -21,29 +23,40 @@ export function orgSeatLimitReachedValidator(
return null;
}
const newEmailsToAdd = Array.from(
new Set(
control.value
.split(",")
.filter(
(newEmailToAdd: string) =>
newEmailToAdd &&
newEmailToAdd.trim() !== "" &&
!allOrganizationUserEmails.some(
(existingEmail) => existingEmail === newEmailToAdd.trim(),
),
),
),
);
const productHasAdditionalSeatsOption =
organization.productTierType !== ProductTierType.Free &&
organization.productTierType !== ProductTierType.Families &&
organization.productTierType !== ProductTierType.TeamsStarter;
return !productHasAdditionalSeatsOption &&
allOrganizationUserEmails.length + newEmailsToAdd.length > organization.seats
? { seatLimitReached: { message: errorMessage } }
: null;
const newTotalCount =
activeUserCount + getUniqueNewEmailCount(allOrganizationUserEmails, control);
if (!productHasAdditionalSeatsOption && newTotalCount > organization.seats) {
return { seatLimitReached: { message: errorMessage } };
}
return null;
};
}
function getUniqueNewEmailCount(
allOrganizationUserEmails: string[],
control: AbstractControl,
): number {
const newEmailsToAdd = Array.from(
new Set(
control.value
.split(",")
.filter(
(newEmailToAdd: string) =>
newEmailToAdd &&
newEmailToAdd.trim() !== "" &&
!allOrganizationUserEmails.some(
(existingEmail) => existingEmail === newEmailToAdd.trim(),
),
),
),
);
return newEmailsToAdd.length;
}

View File

@@ -482,7 +482,7 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
// User attempting to invite new users in a free org with max users
if (
!user &&
this.dataSource.data.length === this.organization.seats &&
this.dataSource.activeUserCount === this.organization.seats &&
(this.organization.productTierType === ProductTierType.Free ||
this.organization.productTierType === ProductTierType.TeamsStarter ||
this.organization.productTierType === ProductTierType.Families)
@@ -508,6 +508,7 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
name: this.userNamePipe.transform(user),
organizationId: this.organization.id,
organizationUserId: user != null ? user.id : null,
activeUserCount: this.dataSource.activeUserCount,
allOrganizationUserEmails: this.dataSource.data?.map((user) => user.email) ?? [],
usesKeyConnector: user?.usesKeyConnector,
isOnSecretsManagerStandalone: this.orgIsOnSecretsManagerStandalone,