mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-21 11:35:34 +01:00
Auth/PM-5086 - Email Verification - Registration Start + Environment Selector components (#9342)
* PM-5086 - WIP start on registration start component * PM-5086 - more WIP progress on registration start comp * PM-5086 - Setup secondary component * PM-5086 - (1) Validation working (2) States implemented (3) 2nd state for check email mostly completed except for correct icon * PM-5086 - Registration Start - check email state - update icon to be correct from figma. * PM-5086 - Refactor self hosted conditional + actually hide the checkbox if it is self hosted. * PM-5086 - WIP good progress on getting browser & desktop creating account on logic working. * PM-5086 - Accessibility pass + WIP on region selector * PM-5086 - Accessibility pass with Danielle * PM-5086 - Migrate env selector logic to own component. * PM-5086 - Update AnonLayoutWrapperComp import * PM-5086 - Remove unncessary focus. * PM-5086 - WIP first draft of registration env selector; name might change to differentiate it from existing env selector. * PM-5086 - Rename env selector to be more clear and use registration-env-selector instead. * PM-5086 - (1) Export registration env selector (2) Change comp name not just file name. * PM-5086 - Create new registration page stub * PM-5086 - Fix build issue where select module was missing from new registration env selector. * PM-5086 - Registration --> registration start. * PM-5086 - Add missing translation from registration-start-secondary-component to desktop & browser. * PM-5086 - Add missing translations * PM-5086 - Registration Env Selector - forms require form groups. duh. * PM-5086 - Registration Env Selector - working now. * PM-5086 - Registration Start desktop mostly working with env selector issues. * PM-5086 - Registration start - get self hosted env dialog to close on close click. Backdrop click doesn't work but escape does still. * PM-5086 - TODO: figure out if there is a better way to get the dialog to close. * PM-5086 - Registration start - get goBack working to properly re-show env selector * PM-5086 - Self Hosted Env Comp - re-emit current env on close so that select based env selectors can reset * PM-5086 - RegistrationEnvSelector - Refactor init logic to also listen for env updates so that the user's choices on the self hosted settings dialog get communicated to this comp * PM-5086 - Registration Start Desktop - Don't allow users to close dialog via escape as we need them to either close or save to get the env service to set the env correctly. * PM-5086 - Browser Registration Start Page stub * PM-5086 - Registration Start comp - storybook added * PM-5086 - Remove links to start-registration as we aren't ready to implement that yet. * PM-5086 - Revert environment comp changes. * PM-5086 - Delete registration start pages. * PM-5086 - Test removing PreloadedEnglishI18nModule to see if it fixes test failures * PM-5086 - Try to resolve issues w/ importing PreloadedEnglishI18nModule into RegistrationStartComponent storybook stories file. * PM-5086 - Allow translations to be imported for all libs. * PM-5086 - Remove comment from JSON * PM-5086 - TODO cleanup * PM-5086 - Per PR feedback, fix display issues by using correct classes. * PM-5086 - Fix SVG per PR feedback * PM-5086 - Remove unnecessary methods * PM-5086 - RegistrationEnvSelectorComponent - per PR feedback, properly type null in form group
This commit is contained in:
parent
34a766f346
commit
89d7e96b25
@ -1606,6 +1606,9 @@
|
||||
"restoredItem": {
|
||||
"message": "Item restored"
|
||||
},
|
||||
"alreadyHaveAccount": {
|
||||
"message": "Already have an account?"
|
||||
},
|
||||
"vaultTimeoutLogOutConfirmation": {
|
||||
"message": "Logging out will remove all access to your vault and requires online authentication after the timeout period. Are you sure you want to use this setting?"
|
||||
},
|
||||
@ -2541,6 +2544,27 @@
|
||||
"ssoIdentifierRequired": {
|
||||
"message": "Organization SSO identifier is required."
|
||||
},
|
||||
"creatingAccountOn": {
|
||||
"message": "Creating account on"
|
||||
},
|
||||
"checkYourEmail": {
|
||||
"message": "Check your email"
|
||||
},
|
||||
"followTheLinkInTheEmailSentTo": {
|
||||
"message": "Follow the link in the email sent to"
|
||||
},
|
||||
"andContinueCreatingYourAccount": {
|
||||
"message": "and continue creating your account."
|
||||
},
|
||||
"noEmail": {
|
||||
"message": "No email?"
|
||||
},
|
||||
"goBack": {
|
||||
"message": "Go back"
|
||||
},
|
||||
"toEditYourEmailAddress": {
|
||||
"message": "to edit your email address."
|
||||
},
|
||||
"eu": {
|
||||
"message": "EU",
|
||||
"description": "European Union"
|
||||
|
@ -2050,6 +2050,9 @@
|
||||
"switchAccount": {
|
||||
"message": "Switch account"
|
||||
},
|
||||
"alreadyHaveAccount": {
|
||||
"message": "Already have an account?"
|
||||
},
|
||||
"options": {
|
||||
"message": "Options"
|
||||
},
|
||||
@ -2390,6 +2393,27 @@
|
||||
"logInRequested": {
|
||||
"message": "Log in requested"
|
||||
},
|
||||
"creatingAccountOn": {
|
||||
"message": "Creating account on"
|
||||
},
|
||||
"checkYourEmail": {
|
||||
"message": "Check your email"
|
||||
},
|
||||
"followTheLinkInTheEmailSentTo": {
|
||||
"message": "Follow the link in the email sent to"
|
||||
},
|
||||
"andContinueCreatingYourAccount": {
|
||||
"message": "and continue creating your account."
|
||||
},
|
||||
"noEmail": {
|
||||
"message": "No email?"
|
||||
},
|
||||
"goBack": {
|
||||
"message": "Go back"
|
||||
},
|
||||
"toEditYourEmailAddress": {
|
||||
"message": "to edit your email address."
|
||||
},
|
||||
"exposedMasterPassword": {
|
||||
"message": "Exposed Master Password"
|
||||
},
|
||||
|
@ -3244,6 +3244,27 @@
|
||||
"device": {
|
||||
"message": "Device"
|
||||
},
|
||||
"creatingAccountOn": {
|
||||
"message": "Creating account on"
|
||||
},
|
||||
"checkYourEmail": {
|
||||
"message": "Check your email"
|
||||
},
|
||||
"followTheLinkInTheEmailSentTo": {
|
||||
"message": "Follow the link in the email sent to"
|
||||
},
|
||||
"andContinueCreatingYourAccount": {
|
||||
"message": "and continue creating your account."
|
||||
},
|
||||
"noEmail": {
|
||||
"message": "No email?"
|
||||
},
|
||||
"goBack": {
|
||||
"message": "Go back"
|
||||
},
|
||||
"toEditYourEmailAddress": {
|
||||
"message": "to edit your email address."
|
||||
},
|
||||
"view": {
|
||||
"message": "View"
|
||||
},
|
||||
|
12
libs/auth/src/angular/icons/registration-check-email.icon.ts
Normal file
12
libs/auth/src/angular/icons/registration-check-email.icon.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { svgIcon } from "@bitwarden/components";
|
||||
|
||||
export const RegistrationCheckEmailIcon = svgIcon`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="144" height="113" fill="none">
|
||||
<g fill="#175DDC" clip-path="url(#a)">
|
||||
<path class="tw-fill-primary-600" fill-rule="evenodd" d="M39.19 26.64c0-.765.62-1.384 1.383-1.384h18.785a1.383 1.383 0 0 1 0 2.766H40.573c-.764 0-1.383-.619-1.383-1.383ZM39.19 37.819c0-.764.62-1.383 1.383-1.383H58.81a1.383 1.383 0 0 1 0 2.766H40.573c-.764 0-1.383-.62-1.383-1.383ZM39.19 48.998c0-.764.62-1.383 1.383-1.383H61.82a1.383 1.383 0 0 1 0 2.766H40.573c-.764 0-1.383-.62-1.383-1.383Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-primary-600" d="M94.576 60.504c0 .382.31.692.691.692 14.892 0 26.977-11.935 26.977-26.675a.692.692 0 0 0-1.383 0c0 13.96-11.451 25.292-25.594 25.292a.691.691 0 0 0-.691.691ZM68.982 35.213c.382 0 .692-.31.692-.692 0-13.96 11.45-25.291 25.593-25.291a.691.691 0 1 0 0-1.383c-14.89 0-26.976 11.935-26.976 26.674 0 .382.31.692.691.692Z"/>
|
||||
<path class="tw-fill-primary-600" fill-rule="evenodd" d="M95.387 2.926c-6.295 0-12.16 1.84-17.086 5.013l-1.136-.877a9.68 9.68 0 0 0-11.705-.096l-8.172 6.098a1.33 1.33 0 0 0-.064.05H33.393a4.149 4.149 0 0 0-4.149 4.15v16.753c-.07.035-.14.078-.205.127l-8.464 6.315a9.68 9.68 0 0 0-3.89 7.759v51.696c0 5.346 4.333 9.68 9.68 9.68h90.733c5.346 0 9.68-4.334 9.68-9.68v-26.48l8.099 8.098a4.84 4.84 0 0 0 6.845 0l.721-.721a4.84 4.84 0 0 0 0-6.845l-15.665-15.665V44.056c0-.587-.366-1.089-.882-1.29a31.666 31.666 0 0 0 1.086-8.245c0-17.45-14.146-31.595-31.595-31.595Zm28.625 52.61v-7.624a31.463 31.463 0 0 1-2.803 4.82l2.803 2.803Zm-4.51-.6a31.84 31.84 0 0 1-3.651 3.659l20.981 20.982c.81.81 2.124.81 2.934 0l.721-.722a2.073 2.073 0 0 0 0-2.933l-20.985-20.986Zm-5.851 5.37a31.434 31.434 0 0 1-16.653 5.77l-.023.014-9.463 5.427a15.205 15.205 0 0 1 2.167 1.666l33.149 30.601a6.876 6.876 0 0 0 1.184-3.87V70.668l-10.361-10.362ZM29.244 37.442l-7.014 5.234a6.914 6.914 0 0 0-2.588 3.925c.062.025.123.056.183.091l9.42 5.626V37.442Zm2.766 16.527 8.172 4.881c.124-.036.255-.056.39-.056h27.813a1.383 1.383 0 0 1 0 2.766H44.72l13.656 8.156a15.208 15.208 0 0 1 4.116-.567H79.36c1.801 0 3.571.32 5.233.928.095-.103.206-.193.334-.267l6.805-3.902C76.004 64.097 63.791 50.735 63.791 34.52a31.454 31.454 0 0 1 6.082-18.64h-36.48c-.764 0-1.383.619-1.383 1.383v36.705Zm40.135-40.851a31.78 31.78 0 0 1 3.8-3.504l-.47-.363a6.914 6.914 0 0 0-8.36-.068l-5.27 3.932h10.212c.03 0 .059 0 .088.003ZM66.557 34.52c0-15.922 12.907-28.83 28.83-28.83 15.922 0 28.829 12.908 28.829 28.83 0 15.922-12.907 28.83-28.83 28.83-15.922 0-28.83-12.908-28.83-28.83ZM19.45 49.691v50.223c0 1.418.428 2.738 1.16 3.835l31.298-30.315a15.21 15.21 0 0 1 3.266-2.41L19.45 49.69Zm3.124 56.007a6.888 6.888 0 0 0 3.79 1.13h90.734a6.879 6.879 0 0 0 3.752-1.106L87.803 75.216a12.446 12.446 0 0 0-8.442-3.301H62.49a12.446 12.446 0 0 0-8.658 3.506l-31.259 30.277Z" clip-rule="evenodd"/></g><defs><clipPath id="a">
|
||||
<path class="tw-fill-primary-600" fill="#fff" d="M.09 0h143.82v112.705H.09z"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>`;
|
@ -14,3 +14,8 @@ export * from "./password-callout/password-callout.component";
|
||||
export * from "./user-verification/user-verification-dialog.component";
|
||||
export * from "./user-verification/user-verification-dialog.types";
|
||||
export * from "./user-verification/user-verification-form-input.component";
|
||||
|
||||
// registration
|
||||
export * from "./registration/registration-start/registration-start.component";
|
||||
export * from "./registration/registration-start/registration-start-secondary.component";
|
||||
export * from "./registration/registration-env-selector/registration-env-selector.component";
|
||||
|
@ -0,0 +1,16 @@
|
||||
<form [formGroup]="formGroup">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "creatingAccountOn" | i18n }}</bit-label>
|
||||
<bit-select formControlName="selectedRegion">
|
||||
<bit-option
|
||||
*ngFor="let regionConfig of availableRegionConfigs"
|
||||
[value]="regionConfig"
|
||||
[label]="regionConfig.domain"
|
||||
></bit-option>
|
||||
<bit-option
|
||||
[value]="ServerEnvironmentType.SelfHosted"
|
||||
[label]="'selfHostedServer' | i18n"
|
||||
></bit-option>
|
||||
</bit-select>
|
||||
</bit-form-field>
|
||||
</form>
|
@ -0,0 +1,101 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, EventEmitter, OnDestroy, OnInit, Output } from "@angular/core";
|
||||
import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||
import { EMPTY, Subject, from, map, of, switchMap, takeUntil, tap } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import {
|
||||
Environment,
|
||||
EnvironmentService,
|
||||
Region,
|
||||
RegionConfig,
|
||||
} from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { FormFieldModule, SelectModule } from "@bitwarden/components";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "auth-registration-env-selector",
|
||||
templateUrl: "registration-env-selector.component.html",
|
||||
imports: [CommonModule, JslibModule, ReactiveFormsModule, FormFieldModule, SelectModule],
|
||||
})
|
||||
export class RegistrationEnvSelectorComponent implements OnInit, OnDestroy {
|
||||
@Output() onOpenSelfHostedSettings = new EventEmitter();
|
||||
|
||||
ServerEnvironmentType = Region;
|
||||
|
||||
formGroup = this.formBuilder.group({
|
||||
selectedRegion: [null as RegionConfig | Region.SelfHosted | null, Validators.required],
|
||||
});
|
||||
|
||||
get selectedRegion(): FormControl {
|
||||
return this.formGroup.get("selectedRegion") as FormControl;
|
||||
}
|
||||
|
||||
availableRegionConfigs: RegionConfig[] = this.environmentService.availableRegions();
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private formBuilder: FormBuilder,
|
||||
private environmentService: EnvironmentService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
await this.initSelectedRegionAndListenForEnvChanges();
|
||||
this.listenForSelectedRegionChanges();
|
||||
}
|
||||
|
||||
private async initSelectedRegionAndListenForEnvChanges() {
|
||||
this.environmentService.environment$
|
||||
.pipe(
|
||||
map((env: Environment) => {
|
||||
const region: Region = env.getRegion();
|
||||
const regionConfig: RegionConfig = this.availableRegionConfigs.find(
|
||||
(availableRegionConfig) => availableRegionConfig.key === region,
|
||||
);
|
||||
|
||||
if (regionConfig === undefined) {
|
||||
// Self hosted does not have a region config.
|
||||
return Region.SelfHosted;
|
||||
}
|
||||
|
||||
return regionConfig;
|
||||
}),
|
||||
tap((selectedRegionInitialValue: RegionConfig | Region.SelfHosted) => {
|
||||
// This inits the form control with the selected region, but
|
||||
// it also sets the value to self hosted if the self hosted settings are saved successfully
|
||||
// in the client specific implementation managed by the parent component.
|
||||
// It also resets the value to the previously selected region if the self hosted
|
||||
// settings are closed without saving. We don't emit the event to avoid a loop.
|
||||
this.selectedRegion.setValue(selectedRegionInitialValue, { emitEvent: false });
|
||||
}),
|
||||
takeUntil(this.destroy$),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
private listenForSelectedRegionChanges() {
|
||||
this.selectedRegion.valueChanges
|
||||
.pipe(
|
||||
switchMap((selectedRegionConfig: RegionConfig | Region.SelfHosted | null) => {
|
||||
if (selectedRegionConfig === null) {
|
||||
return of(null);
|
||||
}
|
||||
|
||||
if (selectedRegionConfig === Region.SelfHosted) {
|
||||
this.onOpenSelfHostedSettings.emit();
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
return from(this.environmentService.setEnvironment(selectedRegionConfig.key));
|
||||
}),
|
||||
takeUntil(this.destroy$),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
<span
|
||||
>{{ "alreadyHaveAccount" | i18n }} <a routerLink="/login">{{ "logIn" | i18n }}</a></span
|
||||
>
|
@ -0,0 +1,15 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component } from "@angular/core";
|
||||
import { RouterModule } from "@angular/router";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "auth-registration-start-secondary",
|
||||
templateUrl: "./registration-start-secondary.component.html",
|
||||
imports: [CommonModule, JslibModule, RouterModule],
|
||||
})
|
||||
export class RegistrationStartSecondaryComponent {
|
||||
constructor() {}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
<ng-container *ngIf="state === RegistrationStartState.USER_DATA_ENTRY">
|
||||
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "emailAddress" | i18n }}</bit-label>
|
||||
<input
|
||||
id="register-start_form_input_email"
|
||||
bitInput
|
||||
type="email"
|
||||
formControlName="email"
|
||||
[attr.readonly]="emailReadonly ? true : null"
|
||||
appAutofocus
|
||||
/>
|
||||
</bit-form-field>
|
||||
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "name" | i18n }}</bit-label>
|
||||
<input id="register-start_form_input_name" bitInput type="text" formControlName="name" />
|
||||
</bit-form-field>
|
||||
|
||||
<bit-form-control *ngIf="!isSelfHost">
|
||||
<input
|
||||
id="register-start-form-input-accept-policies"
|
||||
type="checkbox"
|
||||
bitCheckbox
|
||||
formControlName="acceptPolicies"
|
||||
/>
|
||||
<bit-label for="register-start-form-input-accept-policies">
|
||||
{{ "acceptPolicies" | i18n }}
|
||||
<a
|
||||
bitLink
|
||||
linkType="primary"
|
||||
href="https://bitwarden.com/terms/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>{{ "termsOfService" | i18n }}</a
|
||||
>,
|
||||
<a
|
||||
bitLink
|
||||
linkType="primary"
|
||||
href="https://bitwarden.com/privacy/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>{{ "privacyPolicy" | i18n }}</a
|
||||
>
|
||||
</bit-label>
|
||||
</bit-form-control>
|
||||
|
||||
<button [block]="true" type="submit" buttonType="primary" bitButton bitFormButton>
|
||||
{{ "continue" | i18n }}
|
||||
</button>
|
||||
|
||||
<bit-error-summary *ngIf="showErrorSummary" [formGroup]="formGroup"></bit-error-summary></form
|
||||
></ng-container>
|
||||
<ng-container *ngIf="state === RegistrationStartState.CHECK_EMAIL">
|
||||
<div class="tw-flex tw-flex-col tw-items-center tw-justify-center">
|
||||
<bit-icon [icon]="Icons.RegistrationCheckEmailIcon" class="tw-mb-6"></bit-icon>
|
||||
|
||||
<h2
|
||||
bitTypography="h2"
|
||||
id="check_your_email_heading"
|
||||
class="tw-font-bold tw-mb-3 tw-text-main"
|
||||
tabindex="0"
|
||||
appAutofocus
|
||||
aria-describedby="follow_the_link_body"
|
||||
>
|
||||
{{ "checkYourEmail" | i18n }}
|
||||
</h2>
|
||||
|
||||
<p bitTypography="body1" class="tw-text-center tw-mb-3 tw-text-main" id="follow_the_link_body">
|
||||
{{ "followTheLinkInTheEmailSentTo" | i18n }}
|
||||
<span class="tw-font-bold">{{ email.value }}</span>
|
||||
{{ "andContinueCreatingYourAccount" | i18n }}
|
||||
</p>
|
||||
|
||||
<p bitTypography="helper" class="tw-text-center tw-text-main">
|
||||
{{ "noEmail" | i18n }}
|
||||
<a bitLink linkType="primary" class="tw-cursor-pointer" tabindex="0" (click)="goBack()">
|
||||
{{ "goBack" | i18n }}
|
||||
</a>
|
||||
{{ "toEditYourEmailAddress" | i18n }}
|
||||
</p>
|
||||
</div>
|
||||
</ng-container>
|
@ -0,0 +1,146 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, EventEmitter, OnDestroy, OnInit, Output } from "@angular/core";
|
||||
import {
|
||||
AbstractControl,
|
||||
FormBuilder,
|
||||
FormControl,
|
||||
ReactiveFormsModule,
|
||||
ValidatorFn,
|
||||
Validators,
|
||||
} from "@angular/forms";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import {
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
CheckboxModule,
|
||||
FormFieldModule,
|
||||
IconModule,
|
||||
LinkModule,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
import { RegistrationCheckEmailIcon } from "../../icons/registration-check-email.icon";
|
||||
|
||||
export enum RegistrationStartState {
|
||||
USER_DATA_ENTRY = "UserDataEntry",
|
||||
CHECK_EMAIL = "CheckEmail",
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "auth-registration-start",
|
||||
templateUrl: "./registration-start.component.html",
|
||||
imports: [
|
||||
CommonModule,
|
||||
ReactiveFormsModule,
|
||||
JslibModule,
|
||||
FormFieldModule,
|
||||
AsyncActionsModule,
|
||||
CheckboxModule,
|
||||
ButtonModule,
|
||||
LinkModule,
|
||||
IconModule,
|
||||
],
|
||||
})
|
||||
export class RegistrationStartComponent implements OnInit, OnDestroy {
|
||||
@Output() registrationStartStateChange = new EventEmitter<RegistrationStartState>();
|
||||
|
||||
state: RegistrationStartState = RegistrationStartState.USER_DATA_ENTRY;
|
||||
RegistrationStartState = RegistrationStartState;
|
||||
readonly Icons = { RegistrationCheckEmailIcon };
|
||||
|
||||
isSelfHost = false;
|
||||
|
||||
formGroup = this.formBuilder.group({
|
||||
email: ["", [Validators.required, Validators.email]],
|
||||
name: [""],
|
||||
acceptPolicies: [false, [this.acceptPoliciesValidator()]],
|
||||
selectedRegion: [null],
|
||||
});
|
||||
|
||||
get email(): FormControl {
|
||||
return this.formGroup.get("email") as FormControl;
|
||||
}
|
||||
|
||||
get name(): FormControl {
|
||||
return this.formGroup.get("name") as FormControl;
|
||||
}
|
||||
|
||||
get acceptPolicies(): FormControl {
|
||||
return this.formGroup.get("acceptPolicies") as FormControl;
|
||||
}
|
||||
|
||||
emailReadonly: boolean = false;
|
||||
|
||||
showErrorSummary = false;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private formBuilder: FormBuilder,
|
||||
private route: ActivatedRoute,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
) {
|
||||
this.isSelfHost = platformUtilsService.isSelfHost();
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
// Emit the initial state
|
||||
this.registrationStartStateChange.emit(this.state);
|
||||
|
||||
this.listenForQueryParamChanges();
|
||||
}
|
||||
|
||||
private listenForQueryParamChanges() {
|
||||
this.route.queryParams.pipe(takeUntil(this.destroy$)).subscribe((qParams) => {
|
||||
if (qParams.email != null && qParams.email.indexOf("@") > -1) {
|
||||
this.email?.setValue(qParams.email);
|
||||
this.emailReadonly = qParams.emailReadonly === "true";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
submit = async () => {
|
||||
const valid = this.validateForm();
|
||||
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Implement registration logic
|
||||
|
||||
this.state = RegistrationStartState.CHECK_EMAIL;
|
||||
this.registrationStartStateChange.emit(this.state);
|
||||
};
|
||||
|
||||
private validateForm(): boolean {
|
||||
this.formGroup.markAllAsTouched();
|
||||
|
||||
if (this.formGroup.invalid) {
|
||||
this.showErrorSummary = true;
|
||||
}
|
||||
|
||||
return this.formGroup.valid;
|
||||
}
|
||||
|
||||
goBack() {
|
||||
this.state = RegistrationStartState.USER_DATA_ENTRY;
|
||||
this.registrationStartStateChange.emit(this.state);
|
||||
}
|
||||
|
||||
private acceptPoliciesValidator(): ValidatorFn {
|
||||
return (control: AbstractControl) => {
|
||||
const ctrlValue = control.value;
|
||||
|
||||
return !ctrlValue && !this.isSelfHost ? { required: true } : null;
|
||||
};
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
import { Meta, Story, Controls } from "@storybook/addon-docs";
|
||||
|
||||
import * as stories from "./registration-start.stories";
|
||||
|
||||
<Meta of={stories} />
|
||||
|
||||
# RegistrationStart Component
|
||||
|
||||
The Auth-owned RegistrationStartComponent is to be used for the first step in the new email
|
||||
verification stagegated registration process. It collects the user's email address (required) and
|
||||
optionally their name. On cloud environments, it requires acceptance of the terms of service and the
|
||||
privacy policy; the checkbox is hidden on self hosted environments.
|
||||
|
||||
### Cloud Example
|
||||
|
||||
<Story of={stories.CloudExample} />
|
||||
|
||||
### Self Hosted Example
|
||||
|
||||
<Story of={stories.SelfHostExample} />
|
||||
|
||||
### Query Param Example
|
||||
|
||||
The component accepts two query parameters: `email` and `emailReadonly`. If an email is provided, it
|
||||
will be pre-filled in the email input field. If `emailReadonly` is set to `true`, the email input
|
||||
field will be set to readonly. `emailReadonly` is primarily for the organization invite flow.
|
||||
|
||||
<Story of={stories.QueryParamsExample} />
|
@ -0,0 +1,74 @@
|
||||
import { importProvidersFrom } from "@angular/core";
|
||||
import { ActivatedRoute, Params } from "@angular/router";
|
||||
import { RouterTestingModule } from "@angular/router/testing";
|
||||
import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular";
|
||||
import { of } from "rxjs";
|
||||
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
import { PreloadedEnglishI18nModule } from "../../../../../../apps/web/src/app/core/tests";
|
||||
|
||||
import { RegistrationStartComponent } from "./registration-start.component";
|
||||
|
||||
export default {
|
||||
title: "Auth/Registration/Registration Start",
|
||||
component: RegistrationStartComponent,
|
||||
} as Meta;
|
||||
|
||||
const decorators = (options: { isSelfHost: boolean; queryParams: Params }) => {
|
||||
return [
|
||||
moduleMetadata({
|
||||
imports: [RouterTestingModule],
|
||||
providers: [
|
||||
{
|
||||
provide: ActivatedRoute,
|
||||
useValue: { queryParams: of(options.queryParams) },
|
||||
},
|
||||
{
|
||||
provide: PlatformUtilsService,
|
||||
useValue: {
|
||||
isSelfHost: () => options.isSelfHost,
|
||||
} as Partial<PlatformUtilsService>,
|
||||
},
|
||||
],
|
||||
}),
|
||||
applicationConfig({
|
||||
providers: [importProvidersFrom(PreloadedEnglishI18nModule)],
|
||||
}),
|
||||
];
|
||||
};
|
||||
|
||||
type Story = StoryObj<RegistrationStartComponent>;
|
||||
|
||||
export const CloudExample: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<auth-registration-start></auth-registration-start>
|
||||
`,
|
||||
}),
|
||||
decorators: decorators({ isSelfHost: false, queryParams: {} }),
|
||||
};
|
||||
|
||||
export const SelfHostExample: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<auth-registration-start></auth-registration-start>
|
||||
`,
|
||||
}),
|
||||
decorators: decorators({ isSelfHost: true, queryParams: {} }),
|
||||
};
|
||||
|
||||
export const QueryParamsExample: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<auth-registration-start></auth-registration-start>
|
||||
`,
|
||||
}),
|
||||
decorators: decorators({
|
||||
isSelfHost: false,
|
||||
queryParams: { email: "jaredWasHere@bitwarden.com", emailReadonly: "true" },
|
||||
}),
|
||||
};
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"extends": "./tsconfig",
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true,
|
||||
"paths": {
|
||||
"@bitwarden/admin-console": ["../admin-console/src"],
|
||||
"@bitwarden/angular/*": ["../angular/src/*"],
|
||||
|
Loading…
Reference in New Issue
Block a user