diff --git a/apps/web/src/app/auth/register-form/register-form.component.html b/apps/web/src/app/auth/register-form/register-form.component.html deleted file mode 100644 index 19a7a95b298..00000000000 --- a/apps/web/src/app/auth/register-form/register-form.component.html +++ /dev/null @@ -1,158 +0,0 @@ - - -
-
-
- - {{ "emailAddress" | i18n }} - - {{ "emailAddressDesc" | i18n }} - -
- -
- - {{ "name" | i18n }} - - {{ "yourNameDesc" | i18n }} - -
- -
- - - - {{ "masterPass" | i18n }} - - - - {{ "important" | i18n }} - {{ "masterPassImportant" | i18n }} {{ characterMinimumMessage }} - - - - -
- -
- - {{ "reTypeMasterPass" | i18n }} - - - -
- -
- - {{ "masterPassHintLabel" | i18n }} - - {{ "masterPassHintDesc" | i18n }} - -
- -
- -
-
- - {{ "checkForBreaches" | i18n }} -
-
- - - - {{ "acceptPolicies" | i18n }}
- {{ - "termsOfService" | i18n - }}, - {{ - "privacyPolicy" | i18n - }} -
-
- -
- - - - - - -
-

- {{ "alreadyHaveAccount" | i18n }} - {{ "logIn" | i18n }} -

- -
-
diff --git a/apps/web/src/app/auth/register-form/register-form.component.ts b/apps/web/src/app/auth/register-form/register-form.component.ts deleted file mode 100644 index 7d3e6dbd00e..00000000000 --- a/apps/web/src/app/auth/register-form/register-form.component.ts +++ /dev/null @@ -1,121 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, Input, OnInit } from "@angular/core"; -import { UntypedFormBuilder } from "@angular/forms"; -import { Router } from "@angular/router"; - -import { RegisterComponent as BaseRegisterComponent } from "@bitwarden/angular/auth/components/register.component"; -import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service"; -import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { AuditService } from "@bitwarden/common/abstractions/audit.service"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; -import { ReferenceEventRequest } from "@bitwarden/common/models/request/reference-event.request"; -import { RegisterRequest } from "@bitwarden/common/models/request/register.request"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; -import { KeyService } from "@bitwarden/key-management"; - -import { AcceptOrganizationInviteService } from "../organization-invite/accept-organization.service"; - -@Component({ - selector: "app-register-form", - templateUrl: "./register-form.component.html", -}) -export class RegisterFormComponent extends BaseRegisterComponent implements OnInit { - @Input() queryParamEmail: string; - @Input() queryParamFromOrgInvite: boolean; - @Input() enforcedPolicyOptions: MasterPasswordPolicyOptions; - @Input() referenceDataValue: ReferenceEventRequest; - - showErrorSummary = false; - characterMinimumMessage: string; - - constructor( - formValidationErrorService: FormValidationErrorsService, - formBuilder: UntypedFormBuilder, - loginStrategyService: LoginStrategyServiceAbstraction, - router: Router, - i18nService: I18nService, - keyService: KeyService, - apiService: ApiService, - stateService: StateService, - platformUtilsService: PlatformUtilsService, - passwordGenerationService: PasswordGenerationServiceAbstraction, - private policyService: PolicyService, - environmentService: EnvironmentService, - logService: LogService, - auditService: AuditService, - dialogService: DialogService, - acceptOrgInviteService: AcceptOrganizationInviteService, - toastService: ToastService, - ) { - super( - formValidationErrorService, - formBuilder, - loginStrategyService, - router, - i18nService, - keyService, - apiService, - stateService, - platformUtilsService, - passwordGenerationService, - environmentService, - logService, - auditService, - dialogService, - toastService, - ); - this.modifyRegisterRequest = async (request: RegisterRequest) => { - // Org invites are deep linked. Non-existent accounts are redirected to the register page. - // Org user id and token are included here only for validation and two factor purposes. - const orgInvite = await acceptOrgInviteService.getOrganizationInvite(); - if (orgInvite != null) { - request.organizationUserId = orgInvite.organizationUserId; - request.token = orgInvite.token; - } - // Invite is accepted after login (on deep link redirect). - }; - } - - async ngOnInit() { - await super.ngOnInit(); - this.referenceData = this.referenceDataValue; - if (this.queryParamEmail) { - this.formGroup.get("email")?.setValue(this.queryParamEmail); - } - - if (this.enforcedPolicyOptions != null && this.enforcedPolicyOptions.minLength > 0) { - this.characterMinimumMessage = ""; - } else { - this.characterMinimumMessage = this.i18nService.t("characterMinimum", this.minimumLength); - } - } - - async submit() { - if ( - this.enforcedPolicyOptions != null && - !this.policyService.evaluateMasterPassword( - this.passwordStrengthResult.score, - this.formGroup.value.masterPassword, - this.enforcedPolicyOptions, - ) - ) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("masterPasswordPolicyRequirementsNotMet"), - }); - return; - } - - await super.submit(false); - } -} diff --git a/apps/web/src/app/auth/register-form/register-form.module.ts b/apps/web/src/app/auth/register-form/register-form.module.ts deleted file mode 100644 index b63cb18506d..00000000000 --- a/apps/web/src/app/auth/register-form/register-form.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { NgModule } from "@angular/core"; - -import { PasswordCalloutComponent } from "@bitwarden/auth/angular"; - -import { SharedModule } from "../../shared"; - -import { RegisterFormComponent } from "./register-form.component"; - -@NgModule({ - imports: [SharedModule, PasswordCalloutComponent], - declarations: [RegisterFormComponent], - exports: [RegisterFormComponent], -}) -export class RegisterFormModule {} diff --git a/apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.html b/apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.html index 0b6e44d4eb6..dddac598a46 100644 --- a/apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.html +++ b/apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.html @@ -1,17 +1,4 @@ - - - - - - - - - - -
-

{{ "createAccount" | i18n }}

-
- -
-
-
-
-
-
- Bitwarden - -
- - - - - - - - - - - - - - -
-
-
-
-
- -
-
-
-
-
-

- {{ freeTrialText }} -

- -
- - - - - - - - - - - - - - -
- - -
-
-
-
-
-
-
-
-
diff --git a/apps/web/src/app/auth/trial-initiation/trial-initiation.component.spec.ts b/apps/web/src/app/auth/trial-initiation/trial-initiation.component.spec.ts deleted file mode 100644 index 61fc7a60035..00000000000 --- a/apps/web/src/app/auth/trial-initiation/trial-initiation.component.spec.ts +++ /dev/null @@ -1,336 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { StepperSelectionEvent } from "@angular/cdk/stepper"; -import { TitleCasePipe } from "@angular/common"; -import { NO_ERRORS_SCHEMA } from "@angular/core"; -import { ComponentFixture, fakeAsync, TestBed, tick } from "@angular/core/testing"; -import { FormBuilder, UntypedFormBuilder } from "@angular/forms"; -import { ActivatedRoute, Router } from "@angular/router"; -import { RouterTestingModule } from "@angular/router/testing"; -import { mock, MockProxy } from "jest-mock-extended"; -import { BehaviorSubject, of } from "rxjs"; - -import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; -import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; -import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; -import { OrganizationBillingServiceAbstraction as OrganizationBillingService } from "@bitwarden/common/billing/abstractions/organization-billing.service"; -import { PlanType } from "@bitwarden/common/billing/enums"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; - -import { RouterService } from "../../core"; -import { SharedModule } from "../../shared"; -import { AcceptOrganizationInviteService } from "../organization-invite/accept-organization.service"; -import { OrganizationInvite } from "../organization-invite/organization-invite"; - -import { TrialInitiationComponent } from "./trial-initiation.component"; -import { VerticalStepperComponent } from "./vertical-stepper/vertical-stepper.component"; - -describe("TrialInitiationComponent", () => { - let component: TrialInitiationComponent; - let fixture: ComponentFixture; - const mockQueryParams = new BehaviorSubject({ org: "enterprise" }); - const testOrgId = "91329456-5b9f-44b3-9279-6bb9ee6a0974"; - const formBuilder: FormBuilder = new FormBuilder(); - let routerSpy: jest.SpyInstance; - - let stateServiceMock: MockProxy; - let policyApiServiceMock: MockProxy; - let policyServiceMock: MockProxy; - let routerServiceMock: MockProxy; - let acceptOrgInviteServiceMock: MockProxy; - let organizationBillingServiceMock: MockProxy; - let configServiceMock: MockProxy; - - beforeEach(() => { - // only define services directly that we want to mock return values in this component - stateServiceMock = mock(); - policyApiServiceMock = mock(); - policyServiceMock = mock(); - routerServiceMock = mock(); - acceptOrgInviteServiceMock = mock(); - organizationBillingServiceMock = mock(); - configServiceMock = mock(); - - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - TestBed.configureTestingModule({ - imports: [ - SharedModule, - RouterTestingModule.withRoutes([ - { path: "trial", component: TrialInitiationComponent }, - { - path: `organizations/${testOrgId}/vault`, - component: BlankComponent, - }, - { - path: `organizations/${testOrgId}/members`, - component: BlankComponent, - }, - ]), - ], - declarations: [TrialInitiationComponent, I18nPipe], - providers: [ - UntypedFormBuilder, - { - provide: ActivatedRoute, - useValue: { - queryParams: mockQueryParams.asObservable(), - }, - }, - { provide: StateService, useValue: stateServiceMock }, - { provide: PolicyService, useValue: policyServiceMock }, - { provide: PolicyApiServiceAbstraction, useValue: policyApiServiceMock }, - { provide: LogService, useValue: mock() }, - { provide: I18nService, useValue: mock() }, - { provide: TitleCasePipe, useValue: mock() }, - { - provide: VerticalStepperComponent, - useClass: VerticalStepperStubComponent, - }, - { - provide: RouterService, - useValue: routerServiceMock, - }, - { - provide: AcceptOrganizationInviteService, - useValue: acceptOrgInviteServiceMock, - }, - { - provide: OrganizationBillingService, - useValue: organizationBillingServiceMock, - }, - { - provide: ConfigService, - useValue: configServiceMock, - }, - ], - schemas: [NO_ERRORS_SCHEMA], // Allows child components to be ignored (such as register component) - }).compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(TrialInitiationComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it("should create", () => { - expect(component).toBeTruthy(); - }); - - // These tests demonstrate mocking service calls - describe("onInit() enforcedPolicyOptions", () => { - it("should not set enforcedPolicyOptions if there isn't an org invite in deep linked url", async () => { - acceptOrgInviteServiceMock.getOrganizationInvite.mockResolvedValueOnce(null); - // Need to recreate component with new service mock - fixture = TestBed.createComponent(TrialInitiationComponent); - component = fixture.componentInstance; - await component.ngOnInit(); - - expect(component.enforcedPolicyOptions).toBe(undefined); - }); - it("should set enforcedPolicyOptions if the deep linked url has an org invite", async () => { - // Set up service method mocks - acceptOrgInviteServiceMock.getOrganizationInvite.mockResolvedValueOnce({ - organizationId: testOrgId, - token: "token", - email: "testEmail", - organizationUserId: "123", - } as OrganizationInvite); - policyApiServiceMock.getPoliciesByToken.mockReturnValueOnce( - Promise.resolve([ - { - id: "345", - organizationId: testOrgId, - type: 1, - data: { - minComplexity: 4, - minLength: 10, - requireLower: null, - requireNumbers: null, - requireSpecial: null, - requireUpper: null, - }, - enabled: true, - }, - ] as Policy[]), - ); - policyServiceMock.masterPasswordPolicyOptions$.mockReturnValue( - of({ - minComplexity: 4, - minLength: 10, - requireLower: null, - requireNumbers: null, - requireSpecial: null, - requireUpper: null, - } as MasterPasswordPolicyOptions), - ); - - // Need to recreate component with new service mocks - fixture = TestBed.createComponent(TrialInitiationComponent); - component = fixture.componentInstance; - await component.ngOnInit(); - expect(component.enforcedPolicyOptions).toMatchObject({ - minComplexity: 4, - minLength: 10, - requireLower: null, - requireNumbers: null, - requireSpecial: null, - requireUpper: null, - }); - }); - }); - - // These tests demonstrate route params - describe("Route params", () => { - it("should set org variable to be enterprise and plan to EnterpriseAnnually if org param is enterprise", fakeAsync(() => { - mockQueryParams.next({ org: "enterprise" }); - tick(); // wait for resolution - fixture = TestBed.createComponent(TrialInitiationComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - expect(component.org).toBe("enterprise"); - expect(component.plan).toBe(PlanType.EnterpriseAnnually); - })); - it("should not set org variable if no org param is provided", fakeAsync(() => { - mockQueryParams.next({}); - tick(); // wait for resolution - fixture = TestBed.createComponent(TrialInitiationComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - expect(component.org).toBe(""); - expect(component.accountCreateOnly).toBe(true); - })); - it("should not set the org if org param is invalid ", fakeAsync(async () => { - mockQueryParams.next({ org: "hahahaha" }); - tick(); // wait for resolution - fixture = TestBed.createComponent(TrialInitiationComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - expect(component.org).toBe(""); - expect(component.accountCreateOnly).toBe(true); - })); - it("should set the layout variable if layout param is valid ", fakeAsync(async () => { - mockQueryParams.next({ layout: "teams1" }); - tick(); // wait for resolution - fixture = TestBed.createComponent(TrialInitiationComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - expect(component.layout).toBe("teams1"); - expect(component.accountCreateOnly).toBe(false); - })); - it("should not set the layout variable and leave as 'default' if layout param is invalid ", fakeAsync(async () => { - mockQueryParams.next({ layout: "asdfasdf" }); - tick(); // wait for resolution - fixture = TestBed.createComponent(TrialInitiationComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - component.ngOnInit(); - expect(component.layout).toBe("default"); - expect(component.accountCreateOnly).toBe(true); - })); - }); - - // These tests demonstrate the use of a stub component - describe("createAccount()", () => { - beforeEach(() => { - component.verticalStepper = TestBed.createComponent(VerticalStepperStubComponent) - .componentInstance as VerticalStepperComponent; - }); - - it("should set email and call verticalStepper.next()", fakeAsync(() => { - const verticalStepperNext = jest.spyOn(component.verticalStepper, "next"); - component.createdAccount("test@email.com"); - expect(verticalStepperNext).toHaveBeenCalled(); - expect(component.email).toBe("test@email.com"); - })); - }); - - describe("billingSuccess()", () => { - beforeEach(() => { - component.verticalStepper = TestBed.createComponent(VerticalStepperStubComponent) - .componentInstance as VerticalStepperComponent; - }); - - it("should set orgId and call verticalStepper.next()", () => { - const verticalStepperNext = jest.spyOn(component.verticalStepper, "next"); - component.billingSuccess({ orgId: testOrgId }); - expect(verticalStepperNext).toHaveBeenCalled(); - expect(component.orgId).toBe(testOrgId); - }); - }); - - describe("stepSelectionChange()", () => { - beforeEach(() => { - component.verticalStepper = TestBed.createComponent(VerticalStepperStubComponent) - .componentInstance as VerticalStepperComponent; - }); - - it("on step 2 should show organization copy text", () => { - component.stepSelectionChange({ - selectedIndex: 1, - previouslySelectedIndex: 0, - } as StepperSelectionEvent); - - expect(component.orgInfoSubLabel).toContain("Enter your"); - expect(component.orgInfoSubLabel).toContain(" organization information"); - }); - it("going from step 2 to 3 should set the orgInforSubLabel to be the Org name from orgInfoFormGroup", () => { - component.orgInfoFormGroup = formBuilder.group({ - name: ["Hooli"], - email: [""], - }); - component.stepSelectionChange({ - selectedIndex: 2, - previouslySelectedIndex: 1, - } as StepperSelectionEvent); - - expect(component.orgInfoSubLabel).toContain("Hooli"); - }); - }); - - describe("previousStep()", () => { - beforeEach(() => { - component.verticalStepper = TestBed.createComponent(VerticalStepperStubComponent) - .componentInstance as VerticalStepperComponent; - }); - - it("should call verticalStepper.previous()", fakeAsync(() => { - const verticalStepperPrevious = jest.spyOn(component.verticalStepper, "previous"); - component.previousStep(); - expect(verticalStepperPrevious).toHaveBeenCalled(); - })); - }); - - // These tests demonstrate router navigation - describe("navigation methods", () => { - beforeEach(() => { - component.orgId = testOrgId; - const router = TestBed.inject(Router); - fixture.detectChanges(); - routerSpy = jest.spyOn(router, "navigate"); - }); - describe("navigateToOrgVault", () => { - it("should call verticalStepper.previous()", fakeAsync(() => { - component.navigateToOrgVault(); - expect(routerSpy).toHaveBeenCalledWith(["organizations", testOrgId, "vault"]); - })); - }); - describe("navigateToOrgVault", () => { - it("should call verticalStepper.previous()", fakeAsync(() => { - component.navigateToOrgInvite(); - expect(routerSpy).toHaveBeenCalledWith(["organizations", testOrgId, "members"]); - })); - }); - }); -}); - -export class VerticalStepperStubComponent extends VerticalStepperComponent {} -export class BlankComponent {} // For router tests diff --git a/apps/web/src/app/auth/trial-initiation/trial-initiation.component.ts b/apps/web/src/app/auth/trial-initiation/trial-initiation.component.ts deleted file mode 100644 index fbe3eb7aa6d..00000000000 --- a/apps/web/src/app/auth/trial-initiation/trial-initiation.component.ts +++ /dev/null @@ -1,353 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { StepperSelectionEvent } from "@angular/cdk/stepper"; -import { TitleCasePipe } from "@angular/common"; -import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core"; -import { UntypedFormBuilder, Validators } from "@angular/forms"; -import { ActivatedRoute, Router } from "@angular/router"; -import { Subject, takeUntil } from "rxjs"; - -import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; -import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; -import { - OrganizationInformation, - PlanInformation, - OrganizationBillingServiceAbstraction as OrganizationBillingService, -} from "@bitwarden/common/billing/abstractions/organization-billing.service"; -import { PlanType, ProductTierType } from "@bitwarden/common/billing/enums"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ReferenceEventRequest } from "@bitwarden/common/models/request/reference-event.request"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; - -import { - OrganizationCreatedEvent, - SubscriptionProduct, - TrialOrganizationType, -} from "../../billing/accounts/trial-initiation/trial-billing-step.component"; -import { AcceptOrganizationInviteService } from "../organization-invite/accept-organization.service"; -import { OrganizationInvite } from "../organization-invite/organization-invite"; - -import { RouterService } from "./../../core/router.service"; -import { VerticalStepperComponent } from "./vertical-stepper/vertical-stepper.component"; - -export enum ValidOrgParams { - families = "families", - enterprise = "enterprise", - teams = "teams", - teamsStarter = "teamsStarter", - individual = "individual", - premium = "premium", - free = "free", -} - -enum ValidLayoutParams { - default = "default", - teams = "teams", - teams1 = "teams1", - teams2 = "teams2", - teams3 = "teams3", - enterprise = "enterprise", - enterprise1 = "enterprise1", - enterprise2 = "enterprise2", - cnetcmpgnent = "cnetcmpgnent", - cnetcmpgnind = "cnetcmpgnind", - cnetcmpgnteams = "cnetcmpgnteams", - abmenterprise = "abmenterprise", - abmteams = "abmteams", - secretsManager = "secretsManager", -} - -@Component({ - selector: "app-trial", - templateUrl: "trial-initiation.component.html", -}) -export class TrialInitiationComponent implements OnInit, OnDestroy { - email = ""; - fromOrgInvite = false; - org = ""; - orgInfoSubLabel = ""; - orgId = ""; - orgLabel = ""; - billingSubLabel = ""; - layout = "default"; - plan: PlanType; - productTier: ProductTierType; - accountCreateOnly = true; - useTrialStepper = false; - loading = false; - policies: Policy[]; - enforcedPolicyOptions: MasterPasswordPolicyOptions; - trialFlowOrgs: string[] = [ - ValidOrgParams.teams, - ValidOrgParams.teamsStarter, - ValidOrgParams.enterprise, - ValidOrgParams.families, - ]; - routeFlowOrgs: string[] = [ - ValidOrgParams.free, - ValidOrgParams.premium, - ValidOrgParams.individual, - ]; - layouts = ValidLayoutParams; - referenceData: ReferenceEventRequest; - @ViewChild("stepper", { static: false }) verticalStepper: VerticalStepperComponent; - - orgInfoFormGroup = this.formBuilder.group({ - name: ["", { validators: [Validators.required, Validators.maxLength(50)], updateOn: "change" }], - email: [""], - }); - - private set referenceDataId(referenceId: string) { - if (referenceId != null) { - this.referenceData.id = referenceId; - } else { - this.referenceData.id = ("; " + document.cookie) - .split("; reference=") - .pop() - .split(";") - .shift(); - } - - if (this.referenceData.id === "") { - this.referenceData.id = null; - } else { - // Matches "_ga_QBRN562QQQ=value1.value2.session" and captures values and session. - const regex = /_ga_QBRN562QQQ=([^.]+)\.([^.]+)\.(\d+)/; - const match = document.cookie.match(regex); - if (match) { - this.referenceData.session = match[3]; - } - } - } - - private destroy$ = new Subject(); - protected enableTrialPayment$ = this.configService.getFeatureFlag$( - FeatureFlag.TrialPaymentOptional, - ); - - constructor( - private route: ActivatedRoute, - protected router: Router, - private formBuilder: UntypedFormBuilder, - private titleCasePipe: TitleCasePipe, - private logService: LogService, - private policyApiService: PolicyApiServiceAbstraction, - private policyService: PolicyService, - private i18nService: I18nService, - private routerService: RouterService, - private acceptOrgInviteService: AcceptOrganizationInviteService, - private organizationBillingService: OrganizationBillingService, - private configService: ConfigService, - ) {} - - async ngOnInit(): Promise { - this.route.queryParams.pipe(takeUntil(this.destroy$)).subscribe((qParams) => { - this.referenceData = new ReferenceEventRequest(); - if (qParams.email != null && qParams.email.indexOf("@") > -1) { - this.email = qParams.email; - this.fromOrgInvite = qParams.fromOrgInvite === "true"; - } - - this.referenceDataId = qParams.reference; - - if (Object.values(ValidLayoutParams).includes(qParams.layout)) { - this.layout = qParams.layout; - this.accountCreateOnly = false; - } - - if (this.trialFlowOrgs.includes(qParams.org)) { - this.org = qParams.org; - this.orgLabel = this.titleCasePipe.transform(this.orgDisplayName); - this.useTrialStepper = true; - this.referenceData.flow = qParams.org; - - if (this.org === ValidOrgParams.families) { - this.plan = PlanType.FamiliesAnnually; - this.productTier = ProductTierType.Families; - } else if (this.org === ValidOrgParams.teamsStarter) { - this.plan = PlanType.TeamsStarter; - this.productTier = ProductTierType.TeamsStarter; - } else if (this.org === ValidOrgParams.teams) { - this.plan = PlanType.TeamsAnnually; - this.productTier = ProductTierType.Teams; - } else if (this.org === ValidOrgParams.enterprise) { - this.plan = PlanType.EnterpriseAnnually; - this.productTier = ProductTierType.Enterprise; - } - } else if (this.routeFlowOrgs.includes(qParams.org)) { - this.referenceData.flow = qParams.org; - const route = this.router.createUrlTree(["create-organization"], { - queryParams: { plan: qParams.org }, - }); - this.routerService.setPreviousUrl(route.toString()); - } - - // Are they coming from an email for sponsoring a families organization - // After logging in redirect them to setup the families sponsorship - this.setupFamilySponsorship(qParams.sponsorshipToken); - - this.referenceData.initiationPath = this.accountCreateOnly - ? "Registration form" - : "Password Manager trial from marketing website"; - }); - - // If there's a deep linked org invite, use it to get the password policies - const orgInvite = await this.acceptOrgInviteService.getOrganizationInvite(); - if (orgInvite != null) { - await this.initPasswordPolicies(orgInvite); - } - - this.orgInfoFormGroup.controls.name.valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(() => { - this.orgInfoFormGroup.controls.name.markAsTouched(); - }); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - stepSelectionChange(event: StepperSelectionEvent) { - // Set org info sub label - if (event.selectedIndex === 1 && this.orgInfoFormGroup.controls.name.value === "") { - this.orgInfoSubLabel = - "Enter your " + - this.titleCasePipe.transform(this.orgDisplayName) + - " organization information"; - } else if (event.previouslySelectedIndex === 1) { - this.orgInfoSubLabel = this.orgInfoFormGroup.controls.name.value; - } - - //set billing sub label - if (event.selectedIndex === 2) { - this.billingSubLabel = this.i18nService.t("billingTrialSubLabel"); - } - } - - async createOrganizationOnTrial() { - this.loading = true; - const organization: OrganizationInformation = { - name: this.orgInfoFormGroup.get("name").value, - billingEmail: this.orgInfoFormGroup.get("email").value, - initiationPath: "Password Manager trial from marketing website", - }; - - const plan: PlanInformation = { - type: this.plan, - passwordManagerSeats: 1, - }; - - const response = await this.organizationBillingService.purchaseSubscriptionNoPaymentMethod({ - organization, - plan, - }); - - this.orgId = response?.id; - this.billingSubLabel = `${this.i18nService.t("annual")} ($0/${this.i18nService.t("yr")})`; - this.loading = false; - this.verticalStepper.next(); - } - - createdAccount(email: string) { - this.email = email; - this.orgInfoFormGroup.get("email")?.setValue(email); - this.verticalStepper.next(); - } - - billingSuccess(event: any) { - this.orgId = event?.orgId; - this.billingSubLabel = event?.subLabelText; - this.verticalStepper.next(); - } - - createdOrganization(event: OrganizationCreatedEvent) { - this.orgId = event.organizationId; - this.billingSubLabel = event.planDescription; - this.verticalStepper.next(); - } - - navigateToOrgVault() { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["organizations", this.orgId, "vault"]); - } - - navigateToOrgInvite() { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["organizations", this.orgId, "members"]); - } - - previousStep() { - this.verticalStepper.previous(); - } - - get orgDisplayName() { - if (this.org === "teamsStarter") { - return "Teams Starter"; - } - - return this.org; - } - - get freeTrialText() { - const translationKey = - this.layout === this.layouts.secretsManager - ? "startYour7DayFreeTrialOfBitwardenSecretsManagerFor" - : "startYour7DayFreeTrialOfBitwardenFor"; - - return this.i18nService.t(translationKey, this.org); - } - - get trialOrganizationType(): TrialOrganizationType { - switch (this.productTier) { - case ProductTierType.Free: - return null; - default: - return this.productTier; - } - } - - private setupFamilySponsorship(sponsorshipToken: string) { - if (sponsorshipToken != null) { - const route = this.router.createUrlTree(["setup/families-for-enterprise"], { - queryParams: { plan: sponsorshipToken }, - }); - this.routerService.setPreviousUrl(route.toString()); - } - } - - private async initPasswordPolicies(invite: OrganizationInvite): Promise { - if (invite == null) { - return; - } - - try { - this.policies = await this.policyApiService.getPoliciesByToken( - invite.organizationId, - invite.token, - invite.email, - invite.organizationUserId, - ); - } catch (e) { - this.logService.error(e); - } - - if (this.policies != null) { - this.policyService - .masterPasswordPolicyOptions$(this.policies) - .pipe(takeUntil(this.destroy$)) - .subscribe((enforcedPasswordPolicyOptions) => { - this.enforcedPolicyOptions = enforcedPasswordPolicyOptions; - }); - } - } - - protected readonly SubscriptionProduct = SubscriptionProduct; -} diff --git a/apps/web/src/app/auth/trial-initiation/trial-initiation.module.ts b/apps/web/src/app/auth/trial-initiation/trial-initiation.module.ts index 464c00c4a3a..687cdfe4df6 100644 --- a/apps/web/src/app/auth/trial-initiation/trial-initiation.module.ts +++ b/apps/web/src/app/auth/trial-initiation/trial-initiation.module.ts @@ -6,7 +6,6 @@ import { InputPasswordComponent } from "@bitwarden/auth/angular"; import { FormFieldModule } from "@bitwarden/components"; import { OrganizationCreateModule } from "../../admin-console/organizations/create/organization-create.module"; -import { RegisterFormModule } from "../../auth/register-form/register-form.module"; import { SecretsManagerTrialFreeStepperComponent } from "../../auth/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component"; import { SecretsManagerTrialPaidStepperComponent } from "../../auth/trial-initiation/secrets-manager/secrets-manager-trial-paid-stepper.component"; import { SecretsManagerTrialComponent } from "../../auth/trial-initiation/secrets-manager/secrets-manager-trial.component"; @@ -39,7 +38,6 @@ import { TeamsContentComponent } from "./content/teams-content.component"; import { Teams1ContentComponent } from "./content/teams1-content.component"; import { Teams2ContentComponent } from "./content/teams2-content.component"; import { Teams3ContentComponent } from "./content/teams3-content.component"; -import { TrialInitiationComponent } from "./trial-initiation.component"; import { VerticalStepperModule } from "./vertical-stepper/vertical-stepper.module"; @NgModule({ @@ -48,7 +46,6 @@ import { VerticalStepperModule } from "./vertical-stepper/vertical-stepper.modul CdkStepperModule, VerticalStepperModule, FormFieldModule, - RegisterFormModule, OrganizationCreateModule, EnvironmentSelectorModule, PaymentComponent, @@ -57,7 +54,6 @@ import { VerticalStepperModule } from "./vertical-stepper/vertical-stepper.modul InputPasswordComponent, ], declarations: [ - TrialInitiationComponent, CompleteTrialInitiationComponent, EnterpriseContentComponent, TeamsContentComponent, @@ -88,7 +84,7 @@ import { VerticalStepperModule } from "./vertical-stepper/vertical-stepper.modul SecretsManagerTrialFreeStepperComponent, SecretsManagerTrialPaidStepperComponent, ], - exports: [TrialInitiationComponent, CompleteTrialInitiationComponent], + exports: [CompleteTrialInitiationComponent], providers: [TitleCasePipe], }) export class TrialInitiationModule {} diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index fadcc28f832..108c7a7f2e5 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -71,7 +71,6 @@ import { SecurityRoutingModule } from "./auth/settings/security/security-routing import { SsoComponentV1 } from "./auth/sso-v1.component"; import { CompleteTrialInitiationComponent } from "./auth/trial-initiation/complete-trial-initiation/complete-trial-initiation.component"; import { freeTrialTextResolver } from "./auth/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver"; -import { TrialInitiationComponent } from "./auth/trial-initiation/trial-initiation.component"; import { TwoFactorAuthComponent } from "./auth/two-factor-auth.component"; import { TwoFactorComponent } from "./auth/two-factor.component"; import { UpdatePasswordComponent } from "./auth/update-password.component"; @@ -96,6 +95,11 @@ import { SendComponent } from "./tools/send/send.component"; import { VaultModule } from "./vault/individual-vault/vault.module"; const routes: Routes = [ + { + path: "register", + redirectTo: "signup", + pathMatch: "full", + }, { path: "", component: FrontendLayoutComponent, @@ -112,15 +116,6 @@ const routes: Routes = [ component: LoginViaWebAuthnComponent, data: { titleId: "logInWithPasskey" } satisfies RouteDataProperties, }, - { - path: "register", - component: TrialInitiationComponent, - canActivate: [ - canAccessFeature(FeatureFlag.EmailVerification, false, "/signup", false), - unauthGuardFn(), - ], - data: { titleId: "createAccount" } satisfies RouteDataProperties, - }, { path: "trial", redirectTo: "register", diff --git a/apps/web/src/app/shared/loose-components.module.ts b/apps/web/src/app/shared/loose-components.module.ts index 3176ac81c1a..d6ba5a1990c 100644 --- a/apps/web/src/app/shared/loose-components.module.ts +++ b/apps/web/src/app/shared/loose-components.module.ts @@ -20,7 +20,6 @@ import { WeakPasswordsReportComponent as OrgWeakPasswordsReportComponent } from import { HintComponent } from "../auth/hint.component"; import { RecoverDeleteComponent } from "../auth/recover-delete.component"; import { RecoverTwoFactorComponent } from "../auth/recover-two-factor.component"; -import { RegisterFormModule } from "../auth/register-form/register-form.module"; import { RemovePasswordComponent } from "../auth/remove-password.component"; import { SetPasswordComponent } from "../auth/set-password.component"; import { AccountComponent } from "../auth/settings/account/account.component"; @@ -90,7 +89,6 @@ import { SharedModule } from "./shared.module"; @NgModule({ imports: [ SharedModule, - RegisterFormModule, ProductSwitcherModule, UserVerificationModule, ChangeKdfModule,