-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[AC-1708] Teams Starter Plan (#6740)
* Added support for the teams starter plan * Plans now respect display sort order. Updated teams starter to be in its own product * Remove upgrade button and show new copy instead -- wip copy * Added upgrade dialog for teams starter plan when adding an 11th user * Updated the add user validator to check if plan is teams starter. Updated to not count duplicated emails in the overall count * Renamed validator to be more descriptive and added additional unit tests * Added validator for org types that require customer support to upgrade * Updated small localization for teams plan to account for new starter plan * Removed invalid tests * Resolved issues around free trial flow for teams starter * Added new layout for teams starter free trial flow * Updated copy following demo. Resolved display issues discovered during demo * Removed temporary copy for testing * Updated the second step of free trial flow to use org display name * Updated invite user modal to display 10 instead of 20 as the invite limit for Teams Starter --------- Co-authored-by: cyprain-okeke <[email protected]>
- Loading branch information
1 parent
197059d
commit 9f5226f
Showing
25 changed files
with
417 additions
and
101 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,7 @@ import { OrganizationUserType } from "@bitwarden/common/admin-console/enums"; | |
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; | ||
import { ProductType } from "@bitwarden/common/enums"; | ||
|
||
import { freeOrgSeatLimitReachedValidator } from "./free-org-inv-limit-reached.validator"; | ||
import { orgWithoutAdditionalSeatLimitReachedWithUpgradePathValidator } from "./org-without-additional-seat-limit-reached-with-upgrade-path.validator"; | ||
|
||
const orgFactory = (props: Partial<Organization> = {}) => | ||
Object.assign( | ||
|
@@ -17,7 +17,7 @@ const orgFactory = (props: Partial<Organization> = {}) => | |
props | ||
); | ||
|
||
describe("freeOrgSeatLimitReachedValidator", () => { | ||
describe("orgWithoutAdditionalSeatLimitReachedWithUpgradePathValidator", () => { | ||
let organization: Organization; | ||
let allOrganizationUserEmails: string[]; | ||
let validatorFn: (control: AbstractControl) => ValidationErrors | null; | ||
|
@@ -27,7 +27,7 @@ describe("freeOrgSeatLimitReachedValidator", () => { | |
}); | ||
|
||
it("should return null when control value is empty", () => { | ||
validatorFn = freeOrgSeatLimitReachedValidator( | ||
validatorFn = orgWithoutAdditionalSeatLimitReachedWithUpgradePathValidator( | ||
organization, | ||
allOrganizationUserEmails, | ||
"You cannot invite more than 2 members without upgrading your plan." | ||
|
@@ -40,7 +40,7 @@ describe("freeOrgSeatLimitReachedValidator", () => { | |
}); | ||
|
||
it("should return null when control value is null", () => { | ||
validatorFn = freeOrgSeatLimitReachedValidator( | ||
validatorFn = orgWithoutAdditionalSeatLimitReachedWithUpgradePathValidator( | ||
organization, | ||
allOrganizationUserEmails, | ||
"You cannot invite more than 2 members without upgrading your plan." | ||
|
@@ -57,7 +57,7 @@ describe("freeOrgSeatLimitReachedValidator", () => { | |
planProductType: ProductType.Free, | ||
seats: 2, | ||
}); | ||
validatorFn = freeOrgSeatLimitReachedValidator( | ||
validatorFn = orgWithoutAdditionalSeatLimitReachedWithUpgradePathValidator( | ||
organization, | ||
allOrganizationUserEmails, | ||
"You cannot invite more than 2 members without upgrading your plan." | ||
|
@@ -69,13 +69,40 @@ describe("freeOrgSeatLimitReachedValidator", () => { | |
expect(result).toBeNull(); | ||
}); | ||
|
||
it("should return null when max seats are not exceeded on teams starter plan", () => { | ||
organization = orgFactory({ | ||
planProductType: ProductType.TeamsStarter, | ||
seats: 10, | ||
}); | ||
validatorFn = orgWithoutAdditionalSeatLimitReachedWithUpgradePathValidator( | ||
organization, | ||
allOrganizationUserEmails, | ||
"You cannot invite more than 10 members without upgrading your plan." | ||
); | ||
const control = new FormControl( | ||
"[email protected]," + | ||
"[email protected]," + | ||
"[email protected]," + | ||
"[email protected]," + | ||
"[email protected]," + | ||
"[email protected]," + | ||
"[email protected]," + | ||
"[email protected]," + | ||
"[email protected]" | ||
); | ||
|
||
const result = validatorFn(control); | ||
|
||
expect(result).toBeNull(); | ||
}); | ||
|
||
it("should return validation error when max seats are exceeded on free plan", () => { | ||
organization = orgFactory({ | ||
planProductType: ProductType.Free, | ||
seats: 2, | ||
}); | ||
const errorMessage = "You cannot invite more than 2 members without upgrading your plan."; | ||
validatorFn = freeOrgSeatLimitReachedValidator( | ||
validatorFn = orgWithoutAdditionalSeatLimitReachedWithUpgradePathValidator( | ||
organization, | ||
allOrganizationUserEmails, | ||
"You cannot invite more than 2 members without upgrading your plan." | ||
|
@@ -93,7 +120,7 @@ describe("freeOrgSeatLimitReachedValidator", () => { | |
planProductType: ProductType.Enterprise, | ||
seats: 100, | ||
}); | ||
validatorFn = freeOrgSeatLimitReachedValidator( | ||
validatorFn = orgWithoutAdditionalSeatLimitReachedWithUpgradePathValidator( | ||
organization, | ||
allOrganizationUserEmails, | ||
"You cannot invite more than 2 members without upgrading your plan." | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
45 changes: 45 additions & 0 deletions
45
...og/validators/org-without-additional-seat-limit-reached-without-upgrade-path.validator.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { AbstractControl, ValidationErrors, ValidatorFn } from "@angular/forms"; | ||
|
||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; | ||
import { ProductType } from "@bitwarden/common/enums"; | ||
|
||
/** | ||
* If the organization doesn't allow additional seat options, this checks if the seat limit has been reached when adding | ||
* new users | ||
* @param organization An object representing the organization | ||
* @param allOrganizationUserEmails An array of strings with existing user email addresses | ||
* @param errorMessage A localized string to display if validation fails | ||
* @returns A function that validates an `AbstractControl` and returns `ValidationErrors` or `null` | ||
*/ | ||
export function orgWithoutAdditionalSeatLimitReachedWithoutUpgradePathValidator( | ||
organization: Organization, | ||
allOrganizationUserEmails: string[], | ||
errorMessage: string | ||
): ValidatorFn { | ||
return (control: AbstractControl): ValidationErrors | null => { | ||
if (control.value === "" || !control.value) { | ||
return null; | ||
} | ||
|
||
const newEmailsToAdd = Array.from( | ||
new Set( | ||
control.value | ||
.split(",") | ||
.filter( | ||
(newEmailToAdd: string) => | ||
newEmailToAdd && | ||
newEmailToAdd.trim() !== "" && | ||
!allOrganizationUserEmails.some( | ||
(existingEmail) => existingEmail === newEmailToAdd.trim() | ||
) | ||
) | ||
) | ||
); | ||
|
||
return (organization.planProductType === ProductType.Families || | ||
organization.planProductType === ProductType.TeamsStarter) && | ||
allOrganizationUserEmails.length + newEmailsToAdd.length > organization.seats | ||
? { orgSeatLimitReachedWithoutUpgradePath: { message: errorMessage } } | ||
: null; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.