mirror of
https://github.com/bitwarden/browser
synced 2025-12-23 11:43:46 +00:00
Merge Feature/trial initiation (#3036)
* [SG-74] Trial Initiation Component with Vertical Stepper (#2913) * Vertical stepper PoC * Convert stepper css to tailwind * trial component start * trial component params * tailwind-ify header * Support teams, enterprise, and families layout param and more layout ui work * Some more theming fixes * Rename TrialModule to TrialInitiationModule * Stepper fixes, plus more functionality demo * Cleanup * layout params and placeholders * Only allow trial route to be hit if not logged in * fix typo * Use background-alt2 color for header * Move vertical stepper out of trial-initiation * Create components for the different plan types * Remove width on steps * Remove content projection for label * Tailwind style fixes * Extract step content into a component * Remove layout param for now * Remove step tags * remove pointer classes from step button * Remove most tailwind important designations * Update apps/web/src/app/modules/vertical-stepper/vertical-step.component.ts Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com> * Tailwind and layout fixes * Remove container * lint & prettier fixes * Remove extra CdkStep declaration * Styles fixes * Style logo directly * Remove 0 margin on image * Fix tiling and responsiveness * Minor padding fixes for org pages * Update apps/web/src/app/modules/trial-initiation/trial-initiation.component.html Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com> * prettier fix Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com> * [SG-65] Reusable Registration Form (#2946) * created reusable registration form * fixed conflicts * replicated reactive form changes in other clients * removed comments * client template cleanup * client template cleanup * removed comments in template file * changed to component suffix * switched show password to use component * comments resolution * comments resolution * added toast disable functionality * removed unused locale * mode custom input validator generic * fixed button * fixed linter * removed horizontal rule * switched to button component Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com> Co-authored-by: gbubemismith <gsmithwalter@gmail.com>
This commit is contained in:
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-7" *ngIf="layout">
|
||||
<div class="mt-5">
|
||||
@@ -112,156 +112,10 @@
|
||||
>
|
||||
{{ "createOrganizationCreatePersonalAccount" | i18n }}
|
||||
</app-callout>
|
||||
<div class="form-group">
|
||||
<label for="email">{{ "emailAddress" | i18n }}</label>
|
||||
<input
|
||||
id="email"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="Email"
|
||||
[(ngModel)]="email"
|
||||
required
|
||||
[appAutofocus]="email === ''"
|
||||
inputmode="email"
|
||||
appInputVerbatim="false"
|
||||
/>
|
||||
<small class="form-text text-muted">{{ "emailAddressDesc" | i18n }}</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="name">{{ "yourName" | i18n }}</label>
|
||||
<input
|
||||
id="name"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="Name"
|
||||
[(ngModel)]="name"
|
||||
[appAutofocus]="email !== ''"
|
||||
/>
|
||||
<small class="form-text text-muted">{{ "yourNameDesc" | i18n }}</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<app-callout
|
||||
type="info"
|
||||
[enforcedPolicyOptions]="enforcedPolicyOptions"
|
||||
*ngIf="enforcedPolicyOptions"
|
||||
>
|
||||
</app-callout>
|
||||
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
|
||||
<div class="d-flex">
|
||||
<div class="w-100">
|
||||
<input
|
||||
id="masterPassword"
|
||||
type="{{ showPassword ? 'text' : 'password' }}"
|
||||
name="MasterPassword"
|
||||
class="text-monospace form-control mb-1"
|
||||
[(ngModel)]="masterPassword"
|
||||
(input)="updatePasswordStrength()"
|
||||
required
|
||||
appInputVerbatim
|
||||
/>
|
||||
<app-password-strength [score]="masterPasswordScore" [showText]="true">
|
||||
</app-password-strength>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="ml-1 btn btn-link"
|
||||
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||
(click)="togglePassword(false)"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-lg"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{
|
||||
'bwi-eye': !showPassword,
|
||||
'bwi-eye-slash': showPassword
|
||||
}"
|
||||
></i>
|
||||
</button>
|
||||
<div class="progress-bar invisible"></div>
|
||||
</div>
|
||||
</div>
|
||||
<small class="form-text text-muted">{{ "masterPassDesc" | i18n }}</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
|
||||
<div class="d-flex">
|
||||
<input
|
||||
id="masterPasswordRetype"
|
||||
type="{{ showPassword ? 'text' : 'password' }}"
|
||||
name="MasterPasswordRetype"
|
||||
class="text-monospace form-control"
|
||||
[(ngModel)]="confirmMasterPassword"
|
||||
required
|
||||
appInputVerbatim
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="ml-1 btn btn-link"
|
||||
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||
(click)="togglePassword(true)"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-lg"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="hint">{{ "masterPassHint" | i18n }}</label>
|
||||
<input
|
||||
id="hint"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="Hint"
|
||||
[(ngModel)]="hint"
|
||||
/>
|
||||
<small class="form-text text-muted">{{ "masterPassHintDesc" | i18n }}</small>
|
||||
</div>
|
||||
<div [hidden]="!showCaptcha()">
|
||||
<iframe id="hcaptcha_iframe" height="80"></iframe>
|
||||
</div>
|
||||
<div class="form-group" *ngIf="showTerms">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="acceptPolicies"
|
||||
[(ngModel)]="acceptPolicies"
|
||||
name="AcceptPolicies"
|
||||
/>
|
||||
<label class="form-check-label small text-muted" for="acceptPolicies">
|
||||
{{ "acceptPolicies" | i18n }}<br />
|
||||
<a href="https://bitwarden.com/terms/" target="_blank" rel="noopener">{{
|
||||
"termsOfService" | i18n
|
||||
}}</a
|
||||
>,
|
||||
<a href="https://bitwarden.com/privacy/" target="_blank" rel="noopener">{{
|
||||
"privacyPolicy" | i18n
|
||||
}}</a>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="d-flex mb-2">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-block btn-submit"
|
||||
[disabled]="form.loading"
|
||||
>
|
||||
<span>{{ "submit" | i18n }}</span>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
||||
{{ "cancel" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
<app-register-form
|
||||
[queryParamEmail]="email"
|
||||
[enforcedPolicyOptions]="enforcedPolicyOptions"
|
||||
></app-register-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -351,5 +205,5 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
@@ -7,6 +8,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
||||
import { FormValidationErrorsService } from "@bitwarden/common/abstractions/formValidationErrors.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
|
||||
@@ -25,6 +27,7 @@ import { RouterService } from "../services/router.service";
|
||||
templateUrl: "register.component.html",
|
||||
})
|
||||
export class RegisterComponent extends BaseRegisterComponent {
|
||||
email = "";
|
||||
showCreateOrgMessage = false;
|
||||
layout = "";
|
||||
enforcedPolicyOptions: MasterPasswordPolicyOptions;
|
||||
@@ -32,6 +35,8 @@ export class RegisterComponent extends BaseRegisterComponent {
|
||||
private policies: Policy[];
|
||||
|
||||
constructor(
|
||||
formValidationErrorService: FormValidationErrorsService,
|
||||
formBuilder: FormBuilder,
|
||||
authService: AuthService,
|
||||
router: Router,
|
||||
i18nService: I18nService,
|
||||
@@ -47,6 +52,8 @@ export class RegisterComponent extends BaseRegisterComponent {
|
||||
private routerService: RouterService
|
||||
) {
|
||||
super(
|
||||
formValidationErrorService,
|
||||
formBuilder,
|
||||
authService,
|
||||
router,
|
||||
i18nService,
|
||||
@@ -126,24 +133,4 @@ export class RegisterComponent extends BaseRegisterComponent {
|
||||
|
||||
await super.ngOnInit();
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (
|
||||
this.enforcedPolicyOptions != null &&
|
||||
!this.policyService.evaluateMasterPassword(
|
||||
this.masterPasswordScore,
|
||||
this.masterPassword,
|
||||
this.enforcedPolicyOptions
|
||||
)
|
||||
) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("masterPasswordPolicyRequirementsNotMet")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await super.submit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import { VerifyRecoverDeleteComponent } from "../accounts/verify-recover-delete.
|
||||
import { NestedCheckboxComponent } from "../components/nested-checkbox.component";
|
||||
import { OrganizationSwitcherComponent } from "../components/organization-switcher.component";
|
||||
import { PasswordRepromptComponent } from "../components/password-reprompt.component";
|
||||
import { PasswordStrengthComponent } from "../components/password-strength.component";
|
||||
import { PremiumBadgeComponent } from "../components/premium-badge.component";
|
||||
import { FooterComponent } from "../layouts/footer.component";
|
||||
import { FrontendLayoutComponent } from "../layouts/frontend-layout.component";
|
||||
@@ -158,6 +157,7 @@ import { FolderAddEditComponent } from "../vault/folder-add-edit.component";
|
||||
import { ShareComponent } from "../vault/share.component";
|
||||
|
||||
import { PipesModule } from "./pipes/pipes.module";
|
||||
import { RegisterFormModule } from "./register-form/register-form.module";
|
||||
import { SharedModule } from "./shared.module";
|
||||
import { VaultFilterModule } from "./vault-filter/vault-filter.module";
|
||||
import { OrganizationBadgeModule } from "./vault/modules/organization-badge/organization-badge.module";
|
||||
@@ -165,7 +165,13 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga
|
||||
// Please do not add to this list of declarations - we should refactor these into modules when doing so makes sense until there are none left.
|
||||
// If you are building new functionality, please create or extend a feature module instead.
|
||||
@NgModule({
|
||||
imports: [SharedModule, VaultFilterModule, OrganizationBadgeModule, PipesModule],
|
||||
imports: [
|
||||
SharedModule,
|
||||
VaultFilterModule,
|
||||
OrganizationBadgeModule,
|
||||
PipesModule,
|
||||
RegisterFormModule,
|
||||
],
|
||||
declarations: [
|
||||
PremiumBadgeComponent,
|
||||
AcceptEmergencyComponent,
|
||||
@@ -263,7 +269,6 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga
|
||||
PasswordGeneratorHistoryComponent,
|
||||
PasswordGeneratorPolicyComponent,
|
||||
PasswordRepromptComponent,
|
||||
PasswordStrengthComponent,
|
||||
PaymentComponent,
|
||||
PaymentMethodComponent,
|
||||
PersonalOwnershipPolicyComponent,
|
||||
@@ -418,7 +423,6 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga
|
||||
PasswordGeneratorHistoryComponent,
|
||||
PasswordGeneratorPolicyComponent,
|
||||
PasswordRepromptComponent,
|
||||
PasswordStrengthComponent,
|
||||
PaymentComponent,
|
||||
PaymentMethodComponent,
|
||||
PersonalOwnershipPolicyComponent,
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
<form
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
[appApiAction]="formPromise"
|
||||
class="tw-container tw-mx-auto"
|
||||
[formGroup]="formGroup"
|
||||
>
|
||||
<div>
|
||||
<div class="tw-mb-3">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "emailAddress" | i18n }}</bit-label>
|
||||
<input bitInput type="email" formControlName="email" />
|
||||
<bit-hint>{{ "emailAddressDesc" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
|
||||
<div class="tw-mb-3">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "name" | i18n }}</bit-label>
|
||||
<input bitInput type="text" formControlName="name" />
|
||||
<bit-hint>{{ "yourNameDesc" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
|
||||
<div class="tw-mb-3">
|
||||
<app-callout
|
||||
type="info"
|
||||
[enforcedPolicyOptions]="enforcedPolicyOptions"
|
||||
*ngIf="enforcedPolicyOptions"
|
||||
>
|
||||
</app-callout>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "masterPass" | i18n }}</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
(input)="updatePasswordStrength()"
|
||||
type="{{ showPassword ? 'text' : 'password' }}"
|
||||
formControlName="masterPassword"
|
||||
/>
|
||||
<button type="button" bitSuffix bitButton (click)="togglePassword()">
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="bwi bwi-lg bwi-eye"
|
||||
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
|
||||
></i>
|
||||
</button>
|
||||
<bit-hint>
|
||||
<span class="tw-font-semibold">Important:</span>
|
||||
{{ "masterPassImportant" | i18n }}
|
||||
</bit-hint>
|
||||
</bit-form-field>
|
||||
<app-password-strength [score]="masterPasswordScore" [showText]="true">
|
||||
</app-password-strength>
|
||||
</div>
|
||||
|
||||
<div class="tw-mb-3">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "reTypeMasterPass" | i18n }}</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
type="{{ showPassword ? 'text' : 'password' }}"
|
||||
formControlName="confirmMasterPassword"
|
||||
/>
|
||||
<button type="button" bitSuffix bitButton (click)="togglePassword()">
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="bwi bwi-lg bwi-eye"
|
||||
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
|
||||
></i>
|
||||
</button>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
|
||||
<div class="tw-mb-3">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "masterPassHint" | i18n }}</bit-label>
|
||||
<input bitInput type="text" formControlName="hint" />
|
||||
<bit-hint>{{ "masterPassHintDesc" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
|
||||
<div [hidden]="!showCaptcha()">
|
||||
<iframe id="hcaptcha_iframe" height="80"></iframe>
|
||||
</div>
|
||||
|
||||
<div class="tw-flex tw-items-start tw-mb-3" *ngIf="showTerms">
|
||||
<div class="tw-flex tw-items-center tw-h-6">
|
||||
<input
|
||||
class="tw-w-4 tw-rounded tw-border tw-border-gray-300"
|
||||
bitInput
|
||||
type="checkbox"
|
||||
formControlName="acceptPolicies"
|
||||
/>
|
||||
</div>
|
||||
<bit-label class="ml-2">
|
||||
{{ "acceptPolicies" | i18n }}<br />
|
||||
<a href="https://bitwarden.com/terms/" target="_blank" rel="noopener">{{
|
||||
"termsOfService" | i18n
|
||||
}}</a
|
||||
>,
|
||||
<a href="https://bitwarden.com/privacy/" target="_blank" rel="noopener">{{
|
||||
"privacyPolicy" | i18n
|
||||
}}</a>
|
||||
</bit-label>
|
||||
</div>
|
||||
|
||||
<div class="tw-flex tw-mb-3">
|
||||
<bit-submit-button [loading]="form.loading">{{ "createAccount" | i18n }}</bit-submit-button>
|
||||
<a
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
routerLink="/login"
|
||||
class="tw-inline-flex tw-items-center tw-ml-3 tw-px-3"
|
||||
>
|
||||
<i class="bwi bwi-sign-in tw-mr-2"></i>
|
||||
{{ "logIn" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
<bit-error-summary *ngIf="showErrorSummary" [formGroup]="formGroup"></bit-error-summary>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,87 @@
|
||||
import { Component, Input } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { RegisterComponent as BaseRegisterComponent } from "@bitwarden/angular/components/register.component";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
||||
import { FormValidationErrorsService } from "@bitwarden/common/abstractions/formValidationErrors.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { PolicyService } from "@bitwarden/common/abstractions/policy.service";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/models/domain/masterPasswordPolicyOptions";
|
||||
|
||||
@Component({
|
||||
selector: "app-register-form",
|
||||
templateUrl: "./register-form.component.html",
|
||||
})
|
||||
export class RegisterFormComponent extends BaseRegisterComponent {
|
||||
@Input() queryParamEmail: string;
|
||||
@Input() enforcedPolicyOptions: MasterPasswordPolicyOptions;
|
||||
|
||||
showErrorSummary = false;
|
||||
|
||||
constructor(
|
||||
formValidationErrorService: FormValidationErrorsService,
|
||||
formBuilder: FormBuilder,
|
||||
authService: AuthService,
|
||||
router: Router,
|
||||
i18nService: I18nService,
|
||||
cryptoService: CryptoService,
|
||||
apiService: ApiService,
|
||||
stateService: StateService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
passwordGenerationService: PasswordGenerationService,
|
||||
private policyService: PolicyService,
|
||||
environmentService: EnvironmentService,
|
||||
logService: LogService
|
||||
) {
|
||||
super(
|
||||
formValidationErrorService,
|
||||
formBuilder,
|
||||
authService,
|
||||
router,
|
||||
i18nService,
|
||||
cryptoService,
|
||||
apiService,
|
||||
stateService,
|
||||
platformUtilsService,
|
||||
passwordGenerationService,
|
||||
environmentService,
|
||||
logService
|
||||
);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
await super.ngOnInit();
|
||||
|
||||
if (this.queryParamEmail) {
|
||||
this.formGroup.get("email")?.setValue(this.queryParamEmail);
|
||||
}
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (
|
||||
this.enforcedPolicyOptions != null &&
|
||||
!this.policyService.evaluateMasterPassword(
|
||||
this.masterPasswordScore,
|
||||
this.formGroup.get("masterPassword")?.value,
|
||||
this.enforcedPolicyOptions
|
||||
)
|
||||
) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("masterPasswordPolicyRequirementsNotMet")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await super.submit(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { SharedModule } from "../shared.module";
|
||||
|
||||
import { RegisterFormComponent } from "./register-form.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule],
|
||||
declarations: [RegisterFormComponent],
|
||||
exports: [RegisterFormComponent],
|
||||
})
|
||||
export class RegisterFormModule {}
|
||||
@@ -61,10 +61,13 @@ import {
|
||||
BadgeModule,
|
||||
ButtonModule,
|
||||
CalloutModule,
|
||||
MenuModule,
|
||||
FormFieldModule,
|
||||
SubmitButtonModule,
|
||||
MenuModule,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
import { PasswordStrengthComponent } from "../components/password-strength.component";
|
||||
|
||||
registerLocaleData(localeAf, "af");
|
||||
registerLocaleData(localeAz, "az");
|
||||
registerLocaleData(localeBe, "be");
|
||||
@@ -117,6 +120,7 @@ registerLocaleData(localeZhCn, "zh-CN");
|
||||
registerLocaleData(localeZhTw, "zh-TW");
|
||||
|
||||
@NgModule({
|
||||
declarations: [PasswordStrengthComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
DragDropModule,
|
||||
@@ -132,6 +136,7 @@ registerLocaleData(localeZhTw, "zh-TW");
|
||||
BadgeModule,
|
||||
ButtonModule,
|
||||
MenuModule,
|
||||
FormFieldModule,
|
||||
SubmitButtonModule,
|
||||
],
|
||||
exports: [
|
||||
@@ -149,6 +154,8 @@ registerLocaleData(localeZhTw, "zh-TW");
|
||||
BadgeModule,
|
||||
ButtonModule,
|
||||
MenuModule,
|
||||
FormFieldModule,
|
||||
PasswordStrengthComponent,
|
||||
SubmitButtonModule,
|
||||
],
|
||||
providers: [DatePipe],
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<h1 class="!tw-text-alt2">You've chosen Bitwarden for Enterprise</h1>
|
||||
<div class="tw-pt-24">
|
||||
<h2>What you can do with Bitwarden for Enterprise</h2>
|
||||
</div>
|
||||
|
||||
<div class="tw-text-3xl tw-text-main tw-mt-12">
|
||||
<p class="tw-mt-2.5 tw-mb-20">Collaborate and share securely</p>
|
||||
<p class="tw-mt-2.5 tw-mb-20">Deploy and manage quickly and easily</p>
|
||||
<p class="tw-mt-2.5 tw-mb-20">Access anywhere on any device</p>
|
||||
<p class="tw-mt-2.5 tw-mb-20">Create your account to get started</p>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "app-enterprise-content",
|
||||
templateUrl: "enterprise-content.component.html",
|
||||
})
|
||||
export class EnterpriseContentComponent {}
|
||||
@@ -0,0 +1,13 @@
|
||||
<h1 class="!tw-text-alt2">You've chosen Bitwarden for Families</h1>
|
||||
<div class="tw-pt-24">
|
||||
<h2>
|
||||
Trusted by millions of individuals, teams, and organizations worldwide for secure password
|
||||
storage and sharing.
|
||||
</h2>
|
||||
</div>
|
||||
<div class="tw-text-3xl tw-text-main tw-mt-12">
|
||||
<p class="tw-mt-2.5 tw-mb-20">Collaborate and share securely</p>
|
||||
<p class="tw-mt-2.5 tw-mb-20">Deploy and manage quickly and easily</p>
|
||||
<p class="tw-mt-2.5 tw-mb-20">Access anywhere on any device</p>
|
||||
<p class="tw-mt-2.5 tw-mb-20">Create your account to get started</p>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "app-families-content",
|
||||
templateUrl: "families-content.component.html",
|
||||
})
|
||||
export class FamiliesContentComponent {}
|
||||
@@ -0,0 +1,10 @@
|
||||
<h1 class="!tw-text-alt2">You've chosen Bitwarden for Teams</h1>
|
||||
<div class="tw-pt-24">
|
||||
<h2>What you can do with Btiwarden for Teams</h2>
|
||||
</div>
|
||||
<div class="tw-text-3xl tw-text-main tw-mt-12">
|
||||
<p class="tw-mt-2.5 tw-mb-20">Collaborate and share securely</p>
|
||||
<p class="tw-mt-2.5 tw-mb-20">Deploy and manage quickly and easily</p>
|
||||
<p class="tw-mt-2.5 tw-mb-20">Access anywhere on any device</p>
|
||||
<p class="tw-mt-2.5 tw-mb-20">Create your account to get started</p>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "app-teams-content",
|
||||
templateUrl: "teams-content.component.html",
|
||||
})
|
||||
export class TeamsContentComponent {}
|
||||
@@ -0,0 +1,58 @@
|
||||
<div
|
||||
class="tw-bg-background-alt2 tw-h-96 tw--mt-48 tw-absolute tw--skew-y-3 tw-w-full tw--z-10"
|
||||
></div>
|
||||
|
||||
<div class="tw-flex tw-max-w-screen-xl tw-min-w-4xl tw-mx-auto tw-px-4">
|
||||
<div class="tw-w-1/2">
|
||||
<img
|
||||
alt="Bitwarden"
|
||||
style="height: 50px; width: 335px"
|
||||
class="tw-mt-6"
|
||||
src="../../images/register-layout/logo-horizontal-white.svg"
|
||||
/>
|
||||
<!-- This is to for illustrative purposes and content will be replaced by marketing -->
|
||||
<div class="tw-pt-12">
|
||||
<!-- Teams Body -->
|
||||
<app-teams-content *ngIf="org === 'teams'"></app-teams-content>
|
||||
<!-- Enterprise Body -->
|
||||
<app-enterprise-content *ngIf="org === 'enterprise'"></app-enterprise-content>
|
||||
<!-- Families Body -->
|
||||
<app-families-content *ngIf="org === 'families'"></app-families-content>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tw-w-1/2">
|
||||
<div class="tw-pt-56">
|
||||
<div class="tw-rounded tw-border tw-border-solid tw-bg-background tw-border-secondary-300">
|
||||
<div class="tw-h-12 tw-flex tw-items-center tw-rounded-t tw-bg-secondary-100 tw-w-full">
|
||||
<h2 class="tw-uppercase tw-pl-4 tw-text-base tw-mb-0 tw-font-bold">
|
||||
Start your 7-Day free trial of Bitwarden for {{ org }}
|
||||
</h2>
|
||||
</div>
|
||||
<app-vertical-stepper linear>
|
||||
<!-- Content is for demo purposes. Replace with form components for each step-->
|
||||
<app-vertical-step label="Create Account" [editable]="false">
|
||||
<!-- Replace content with Registration step -->
|
||||
<p>This is content of "Step 1" that has editable set to false</p>
|
||||
<button bitButton buttonType="primary" cdkStepperNext>Complete step</button>
|
||||
</app-vertical-step>
|
||||
<app-vertical-step label="Create Organization" subLabel="It better be a good org">
|
||||
<!-- Replace with Org creation step -->
|
||||
<p>This is content of "Step 2"</p>
|
||||
<button bitButton buttonType="primary" cdkStepperNext>Complete step</button>
|
||||
</app-vertical-step>
|
||||
<app-vertical-step label="Billing">
|
||||
<!-- Replace with Billing step -->
|
||||
<p>This is content of "Step 3"</p>
|
||||
<button bitButton buttonType="secondary" cdkStepperPrevious>Back</button>
|
||||
<button bitButton buttonType="primary" cdkStepperNext>Complete step</button>
|
||||
</app-vertical-step>
|
||||
<app-vertical-step label="Confirmation Details" subLabel="Fancy sub label">
|
||||
<!-- Replace with Confirmation details step -->
|
||||
<p>This is any content of "Step 4"</p>
|
||||
<button bitButton buttonType="primary" cdkStepperNext>Complete</button>
|
||||
</app-vertical-step>
|
||||
</app-vertical-stepper>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { first } from "rxjs";
|
||||
|
||||
@Component({
|
||||
selector: "app-trial",
|
||||
templateUrl: "trial-initiation.component.html",
|
||||
})
|
||||
export class TrialInitiationComponent implements OnInit {
|
||||
email = "";
|
||||
org = "teams";
|
||||
|
||||
constructor(private route: ActivatedRoute) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.route.queryParams.pipe(first()).subscribe((qParams) => {
|
||||
if (qParams.email != null && qParams.email.indexOf("@") > -1) {
|
||||
this.email = qParams.email;
|
||||
}
|
||||
if (qParams.org) {
|
||||
this.org = qParams.org;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { CdkStepperModule } from "@angular/cdk/stepper";
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { FormFieldModule } from "@bitwarden/components";
|
||||
|
||||
import { SharedModule } from "../shared.module";
|
||||
import { VerticalStepperModule } from "../vertical-stepper/vertical-stepper.module";
|
||||
|
||||
import { EnterpriseContentComponent } from "./enterprise-content.component";
|
||||
import { FamiliesContentComponent } from "./families-content.component";
|
||||
import { TeamsContentComponent } from "./teams-content.component";
|
||||
import { TrialInitiationComponent } from "./trial-initiation.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule, CdkStepperModule, VerticalStepperModule, FormFieldModule],
|
||||
declarations: [
|
||||
TrialInitiationComponent,
|
||||
EnterpriseContentComponent,
|
||||
FamiliesContentComponent,
|
||||
TeamsContentComponent,
|
||||
],
|
||||
exports: [TrialInitiationComponent],
|
||||
})
|
||||
export class TrialInitiationModule {}
|
||||
@@ -0,0 +1,45 @@
|
||||
<div class="tw-m-2.5 tw-text-center tw-h-16">
|
||||
<button
|
||||
(click)="selectStep()"
|
||||
[disabled]="disabled"
|
||||
class="tw-w-full tw-flex tw-border-none tw-bg-transparent tw-items-center"
|
||||
[ngClass]="{
|
||||
'hover:tw-bg-secondary-100': !disabled && step.editable
|
||||
}"
|
||||
[attr.aria-expanded]="selected"
|
||||
>
|
||||
<span
|
||||
class="tw-rounded-full tw-font-bold tw-leading-9 tw-mr-3.5 tw-w-9"
|
||||
*ngIf="!step.completed"
|
||||
[ngClass]="{
|
||||
'tw-text-contrast tw-bg-primary-500': selected,
|
||||
'tw-text-main tw-bg-secondary-300': !selected && !disabled && step.editable,
|
||||
'tw-text-muted tw-bg-transparent': disabled
|
||||
}"
|
||||
>
|
||||
{{ stepNumber }}
|
||||
</span>
|
||||
<span
|
||||
class="tw-text-contrast tw-bg-primary-500 tw-rounded-full tw-font-bold tw-leading-9 tw-mr-3.5 tw-w-9"
|
||||
*ngIf="step.completed"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-check tw-p-1" aria-hidden="true"></i>
|
||||
</span>
|
||||
<div
|
||||
class="tw-text-left tw-txt-main tw-leading-snug tw-h-12 tw-mt-3.5"
|
||||
[ngClass]="{
|
||||
'tw-font-bold': selected
|
||||
}"
|
||||
>
|
||||
<p
|
||||
class="main-label text tw-text-main tw-mb-1"
|
||||
[ngClass]="{
|
||||
'tw-mt-1': !step.subLabel
|
||||
}"
|
||||
>
|
||||
{{ step.label }}
|
||||
</p>
|
||||
<p class="sub-label small tw-text-muted">{{ step.subLabel }}</p>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
|
||||
import { VerticalStep } from "./vertical-step.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-vertical-step-content",
|
||||
templateUrl: "vertical-step-content.component.html",
|
||||
})
|
||||
export class VerticalStepContentComponent {
|
||||
@Output() onSelectStep = new EventEmitter<void>();
|
||||
|
||||
@Input() disabled = false;
|
||||
@Input() selected = false;
|
||||
@Input() step: VerticalStep;
|
||||
@Input() stepNumber: number;
|
||||
|
||||
selectStep() {
|
||||
this.onSelectStep.emit();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<ng-template>
|
||||
<div
|
||||
class="tw-pl-7 tw-inline-block tw-border-0 tw-border-l tw-border-solid tw-border-secondary-300 tw-w-10/12"
|
||||
>
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
</ng-template>
|
||||
@@ -0,0 +1,11 @@
|
||||
import { CdkStep } from "@angular/cdk/stepper";
|
||||
import { Component, Input } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "app-vertical-step",
|
||||
templateUrl: "vertical-step.component.html",
|
||||
providers: [{ provide: CdkStep, useExisting: VerticalStep }],
|
||||
})
|
||||
export class VerticalStep extends CdkStep {
|
||||
@Input() subLabel = "";
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<div>
|
||||
<ul class="tw-flex tw-list-none tw-flex-col tw-flex-wrap tw-p-5">
|
||||
<li *ngFor="let step of steps; let i = index; let isLast = last">
|
||||
<app-vertical-step-content
|
||||
[disabled]="isStepDisabled(i)"
|
||||
[selected]="selectedIndex === i"
|
||||
[step]="step"
|
||||
[stepNumber]="i + 1"
|
||||
(onSelectStep)="selectStepByIndex(i)"
|
||||
></app-vertical-step-content>
|
||||
<div
|
||||
class="tw-pl-7 tw-inline-block"
|
||||
*ngIf="selectedIndex === i"
|
||||
[ngTemplateOutlet]="selected ? selected.content : null"
|
||||
></div>
|
||||
<div
|
||||
class="tw-h-6 tw-ml-8 tw-border-0 tw-border-l tw-border-solid tw-border-secondary-300"
|
||||
*ngIf="!isLast && !(selectedIndex === i)"
|
||||
></div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -0,0 +1,29 @@
|
||||
import { CdkStepper } from "@angular/cdk/stepper";
|
||||
import { Component, Input } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "app-vertical-stepper",
|
||||
templateUrl: "vertical-stepper.component.html",
|
||||
providers: [{ provide: CdkStepper, useExisting: VerticalStepperComponent }],
|
||||
})
|
||||
export class VerticalStepperComponent extends CdkStepper {
|
||||
@Input()
|
||||
activeClass = "active";
|
||||
|
||||
isNextButtonHidden() {
|
||||
return !(this.steps.length === this.selectedIndex + 1);
|
||||
}
|
||||
|
||||
isStepDisabled(index: number) {
|
||||
if (this.selectedIndex !== index) {
|
||||
return this.selectedIndex === index - 1
|
||||
? !this.steps.find((_, i) => i == index - 1)?.completed
|
||||
: true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
selectStepByIndex(index: number): void {
|
||||
this.selectedIndex = index;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { SharedModule } from "../shared.module";
|
||||
|
||||
import { VerticalStepContentComponent } from "./vertical-step-content.component";
|
||||
import { VerticalStep } from "./vertical-step.component";
|
||||
import { VerticalStepperComponent } from "./vertical-stepper.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule],
|
||||
declarations: [VerticalStepperComponent, VerticalStep, VerticalStepContentComponent],
|
||||
exports: [VerticalStepperComponent, VerticalStep],
|
||||
})
|
||||
export class VerticalStepperModule {}
|
||||
@@ -24,6 +24,7 @@ import { VerifyRecoverDeleteComponent } from "./accounts/verify-recover-delete.c
|
||||
import { HomeGuard } from "./guards/home.guard";
|
||||
import { FrontendLayoutComponent } from "./layouts/frontend-layout.component";
|
||||
import { UserLayoutComponent } from "./layouts/user-layout.component";
|
||||
import { TrialInitiationComponent } from "./modules/trial-initiation/trial-initiation.component";
|
||||
import { IndividualVaultModule } from "./modules/vault/modules/individual-vault/individual-vault.module";
|
||||
import { OrganizationsRoutingModule } from "./organizations/organization-routing.module";
|
||||
import { AcceptFamilySponsorshipComponent } from "./organizations/sponsorships/accept-family-sponsorship.component";
|
||||
@@ -64,6 +65,12 @@ const routes: Routes = [
|
||||
canActivate: [UnauthGuard],
|
||||
data: { titleId: "createAccount" },
|
||||
},
|
||||
{
|
||||
path: "trial",
|
||||
component: TrialInitiationComponent,
|
||||
canActivate: [UnauthGuard],
|
||||
data: { titleId: "startTrial" },
|
||||
},
|
||||
{
|
||||
path: "sso",
|
||||
component: SsoComponent,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { OrganizationManageModule } from "./modules/organizations/manage/organiz
|
||||
import { OrganizationUserModule } from "./modules/organizations/users/organization-user.module";
|
||||
import { PipesModule } from "./modules/pipes/pipes.module";
|
||||
import { SharedModule } from "./modules/shared.module";
|
||||
import { TrialInitiationModule } from "./modules/trial-initiation/trial-initiation.module";
|
||||
import { VaultFilterModule } from "./modules/vault-filter/vault-filter.module";
|
||||
import { OrganizationBadgeModule } from "./modules/vault/modules/organization-badge/organization-badge.module";
|
||||
|
||||
@@ -12,6 +13,7 @@ import { OrganizationBadgeModule } from "./modules/vault/modules/organization-ba
|
||||
imports: [
|
||||
SharedModule,
|
||||
LooseComponentsModule,
|
||||
TrialInitiationModule,
|
||||
VaultFilterModule,
|
||||
OrganizationBadgeModule,
|
||||
PipesModule,
|
||||
@@ -21,6 +23,7 @@ import { OrganizationBadgeModule } from "./modules/vault/modules/organization-ba
|
||||
exports: [
|
||||
SharedModule,
|
||||
LooseComponentsModule,
|
||||
TrialInitiationModule,
|
||||
VaultFilterModule,
|
||||
OrganizationBadgeModule,
|
||||
PipesModule,
|
||||
|
||||
Reference in New Issue
Block a user