1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-12 06:23:38 +00:00

Separate dialog events into invite and edit dialogs to eliminate the need for nullable fields.

This commit is contained in:
Jimmy Vo
2024-12-04 16:46:25 -05:00
parent fd2afb69ec
commit a5d9062420
5 changed files with 72 additions and 28 deletions

View File

@@ -75,6 +75,10 @@ export abstract class PeopleTableDataSource<T extends UserViewTypes> 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)

View File

@@ -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(

View File

@@ -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,

View File

@@ -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<OrganizationUserView>
}
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<OrganizationUserView>
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<OrganizationUserView>
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,

View File

@@ -94,10 +94,10 @@ export class MemberAccessReportComponent implements OnInit {
edit = async (user: MemberAccessReportView | null): Promise<void> => {
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,