1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-10-06 05:28:51 +02:00

Auth/PM-8367 - Email Verification - Integrate Registration Self Hosted Env Selector + new Self Hosted Env Settings Dialog into Registration Start (#9361)

* PM-8367 - WIP - initial comp creation

* PM-8367 - Majority of new registration self hosted env config dialog working

* PM-8367 - RegistrationEnvSelectorComponent - add method handleSelfHostedEnvConfigDialogResult and add toast for happy path.

* PM-8367 - Add validation TODO

* PM-8367 - RegistrationSelfHostedEnvConfigDialogComponent - Add validator

* PM-8367 - RegEnvSelector - Only show self hosted if the client is browser or desktop since we will be using the selector on web as well.

* PM-8367 - Registration start comp - add env selector

* PM-8367 - Registration start - add proper import for standalone comps.

* PM-8367 - Registration Start - get storybook fixed with registration env selector

* PM-8367 - Add self hosted server to web translations only for storybook

* PM-8367 - Add more storybook examples and update docs (WIP - need to test self hosted selection)

* PM-8367 - Registration Start - update stories

* PM-8367 - Env Selector now emits selected region so that parent comps can listen to it if needed.

* PM-8367 - Registration Start - wire up handler for selectedRegionChange so that the parent comp can successfully track isSelfHost and hide / show the terms / privacy policy checkbox

* PM-8367 - TODO cleanup

* PM-8367 - Registration start docs - stage gate is two words.

* PM-8367 - Per working session with Will, move top level provided services to app level instead of module level to solve dialog null injector errors.

* PM-8367 - Storybook working for self hosted env dialog

* PM-8367 - Add dialog scroll feature to bitDialog and implement in self hosted env dialog.

* PM-8367 - Revert bit dialog changes and scroll implementation.

* PM-8367 - Tweak registration start docs

* PM-8367 - Remove unused changeDetectorRef

* PM-8367 - Add docs per PR feedback
This commit is contained in:
Jared Snider 2024-06-03 13:05:27 -04:00 committed by GitHub
parent f691854387
commit 9d35a8895e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 666 additions and 62 deletions

View File

@ -1110,6 +1110,15 @@
"selfHostedEnvironmentFooter": {
"message": "Specify the base URL of your on-premises hosted Bitwarden installation."
},
"selfHostedBaseUrlHint": {
"message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com"
},
"selfHostedCustomEnvHeader" :{
"message": "For advanced configuration, you can specify the base URL of each service independently."
},
"selfHostedEnvFormInvalid" :{
"message": "You must add either the base Server URL or at least one custom environment."
},
"customEnvironment": {
"message": "Custom environment"
},

View File

@ -695,6 +695,15 @@
"selfHostedEnvironmentFooter": {
"message": "Specify the base URL of your on-premises hosted Bitwarden installation."
},
"selfHostedBaseUrlHint": {
"message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com"
},
"selfHostedCustomEnvHeader" :{
"message": "For advanced configuration, you can specify the base URL of each service independently."
},
"selfHostedEnvFormInvalid" :{
"message": "You must add either the base Server URL or at least one custom environment."
},
"customEnvironment": {
"message": "Custom environment"
},

View File

@ -5595,6 +5595,39 @@
"rotateBillingSyncTokenTitle": {
"message": "Rotating the billing sync token will invalidate the previous token."
},
"selfHostedServer": {
"message": "self-hosted"
},
"customEnvironment": {
"message": "Custom environment"
},
"selfHostedBaseUrlHint": {
"message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com"
},
"selfHostedCustomEnvHeader" :{
"message": "For advanced configuration, you can specify the base URL of each service independently."
},
"selfHostedEnvFormInvalid" :{
"message": "You must add either the base Server URL or at least one custom environment."
},
"apiUrl": {
"message": "API server URL"
},
"webVaultUrl": {
"message": "Web vault server URL"
},
"identityUrl": {
"message": "Identity server URL"
},
"notificationsUrl": {
"message": "Notifications server URL"
},
"iconsUrl": {
"message": "Icons server URL"
},
"environmentSaved": {
"message": "Environment URLs saved"
},
"selfHostingTitle": {
"message": "Self-hosting"
},

View File

@ -8,6 +8,7 @@
[label]="regionConfig.domain"
></bit-option>
<bit-option
*ngIf="isDesktopOrBrowserExtension"
[value]="ServerEnvironmentType.SelfHosted"
[label]="'selfHostedServer' | i18n"
></bit-option>

View File

@ -1,17 +1,26 @@
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 { Subject, from, map, of, pairwise, startWith, switchMap, takeUntil, tap } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { ClientType } from "@bitwarden/common/enums";
import {
Environment,
EnvironmentService,
Region,
RegionConfig,
} from "@bitwarden/common/platform/abstractions/environment.service";
import { FormFieldModule, SelectModule } from "@bitwarden/components";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { DialogService, FormFieldModule, SelectModule, ToastService } from "@bitwarden/components";
import { RegistrationSelfHostedEnvConfigDialogComponent } from "./registration-self-hosted-env-config-dialog.component";
/**
* Component for selecting the environment to register with in the email verification registration flow.
* Outputs the selected region to the parent component so it can respond as necessary.
*/
@Component({
standalone: true,
selector: "auth-registration-env-selector",
@ -19,7 +28,7 @@ import { FormFieldModule, SelectModule } from "@bitwarden/components";
imports: [CommonModule, JslibModule, ReactiveFormsModule, FormFieldModule, SelectModule],
})
export class RegistrationEnvSelectorComponent implements OnInit, OnDestroy {
@Output() onOpenSelfHostedSettings = new EventEmitter();
@Output() selectedRegionChange = new EventEmitter<RegionConfig | Region.SelfHosted | null>();
ServerEnvironmentType = Region;
@ -33,12 +42,24 @@ export class RegistrationEnvSelectorComponent implements OnInit, OnDestroy {
availableRegionConfigs: RegionConfig[] = this.environmentService.availableRegions();
private selectedRegionFromEnv: RegionConfig | Region.SelfHosted;
isDesktopOrBrowserExtension = false;
private destroy$ = new Subject<void>();
constructor(
private formBuilder: FormBuilder,
private environmentService: EnvironmentService,
) {}
private dialogService: DialogService,
private i18nService: I18nService,
private toastService: ToastService,
private platformUtilsService: PlatformUtilsService,
) {
const clientType = platformUtilsService.getClientType();
this.isDesktopOrBrowserExtension =
clientType === ClientType.Desktop || clientType === ClientType.Browser;
}
async ngOnInit() {
await this.initSelectedRegionAndListenForEnvChanges();
@ -61,13 +82,17 @@ export class RegistrationEnvSelectorComponent implements OnInit, OnDestroy {
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 });
tap((selectedRegionFromEnv: RegionConfig | Region.SelfHosted) => {
// Only set the value if it is different from the current value.
if (selectedRegionFromEnv !== this.selectedRegion.value) {
// Don't emit to avoid triggering the selectedRegion valueChanges subscription
// which could loop back to this code.
this.selectedRegion.setValue(selectedRegionFromEnv, { emitEvent: false });
}
// Save this off so we can reset the value to the previously selected region
// if the self hosted settings are closed without saving.
this.selectedRegionFromEnv = selectedRegionFromEnv;
}),
takeUntil(this.destroy$),
)
@ -77,23 +102,66 @@ export class RegistrationEnvSelectorComponent implements OnInit, OnDestroy {
private listenForSelectedRegionChanges() {
this.selectedRegion.valueChanges
.pipe(
switchMap((selectedRegionConfig: RegionConfig | Region.SelfHosted | null) => {
if (selectedRegionConfig === null) {
startWith(null), // required so that first user choice is not ignored
pairwise(),
switchMap(
([prevSelectedRegion, selectedRegion]: [
RegionConfig | Region.SelfHosted | null,
RegionConfig | Region.SelfHosted | null,
]) => {
if (selectedRegion === null) {
this.selectedRegionChange.emit(selectedRegion);
return of(null);
}
if (selectedRegionConfig === Region.SelfHosted) {
this.onOpenSelfHostedSettings.emit();
return EMPTY;
if (selectedRegion === Region.SelfHosted) {
return from(
RegistrationSelfHostedEnvConfigDialogComponent.open(this.dialogService),
).pipe(
tap((result: boolean | undefined) =>
this.handleSelfHostedEnvConfigDialogResult(result, prevSelectedRegion),
),
);
}
return from(this.environmentService.setEnvironment(selectedRegionConfig.key));
}),
this.selectedRegionChange.emit(selectedRegion);
return from(this.environmentService.setEnvironment(selectedRegion.key));
},
),
takeUntil(this.destroy$),
)
.subscribe();
}
private handleSelfHostedEnvConfigDialogResult(
result: boolean | undefined,
prevSelectedRegion: RegionConfig | Region.SelfHosted | null,
) {
if (result === true) {
this.selectedRegionChange.emit(Region.SelfHosted);
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("environmentSaved"),
});
return;
}
// Reset the value to the previously selected region or the current env setting
// if the self hosted env settings dialog is closed without saving.
if (
(result === false || result === undefined) &&
prevSelectedRegion !== null &&
prevSelectedRegion !== Region.SelfHosted
) {
this.selectedRegionChange.emit(prevSelectedRegion);
this.selectedRegion.setValue(prevSelectedRegion, { emitEvent: false });
} else {
this.selectedRegionChange.emit(this.selectedRegionFromEnv);
this.selectedRegion.setValue(this.selectedRegionFromEnv, { emitEvent: false });
}
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();

View File

@ -0,0 +1,107 @@
<form [formGroup]="formGroup" [bitSubmit]="submit">
<bit-dialog>
<span bitDialogTitle> Self-hosted environment</span>
<ng-container bitDialogContent>
<bit-form-field>
<bit-label>{{ "baseUrl" | i18n }}</bit-label>
<input
id="self_hosted_env_settings_form_input_base_url"
bitInput
type="text"
formControlName="baseUrl"
appAutofocus
appInputVerbatim
/>
<bit-hint>{{ "selfHostedBaseUrlHint" | i18n }}</bit-hint>
</bit-form-field>
<button bitLink linkType="primary" type="button" (click)="showCustomEnv = !showCustomEnv">
<i
class="bwi bwi-fw bwi-sm"
[ngClass]="{ 'bwi-angle-right': !showCustomEnv, 'bwi-angle-down': showCustomEnv }"
aria-hidden="true"
></i>
{{ "customEnvironment" | i18n }}
</button>
<ng-container *ngIf="showCustomEnv">
<p bitTypography="body1" class="tw-text-muted tw-mt-3">
{{ "selfHostedCustomEnvHeader" | i18n }}
</p>
<bit-form-field>
<bit-label>{{ "webVaultUrl" | i18n }}</bit-label>
<input
id="self_hosted_env_settings_form_input_web_vault_url"
bitInput
type="text"
formControlName="webVaultUrl"
appInputVerbatim
/>
</bit-form-field>
<bit-form-field>
<bit-label>{{ "apiUrl" | i18n }}</bit-label>
<input
id="self_hosted_env_settings_form_input_api_url"
bitInput
type="text"
formControlName="apiUrl"
appInputVerbatim
/>
</bit-form-field>
<bit-form-field>
<bit-label>{{ "identityUrl" | i18n }}</bit-label>
<input
id="self_hosted_env_settings_form_input_identity_url"
bitInput
type="text"
formControlName="identityUrl"
appInputVerbatim
/>
</bit-form-field>
<bit-form-field>
<bit-label>{{ "notificationsUrl" | i18n }}</bit-label>
<input
id="self_hosted_env_settings_form_input_notifications_url"
bitInput
type="text"
formControlName="notificationsUrl"
appInputVerbatim
/>
</bit-form-field>
<bit-form-field>
<bit-label>{{ "iconsUrl" | i18n }}</bit-label>
<input
id="self_hosted_env_settings_form_input_icons_url"
bitInput
type="text"
formControlName="iconsUrl"
appInputVerbatim
/>
</bit-form-field>
</ng-container>
<span
*ngIf="showErrorSummary"
class="tw-block tw-text-danger tw-mt-2"
aria-live="assertive"
role="alert"
>
<i class="bwi bwi-error"></i> {{ "selfHostedEnvFormInvalid" | i18n }}
</span>
</ng-container>
<ng-container bitDialogFooter>
<button type="submit" bitButton bitFormButton buttonType="primary">
{{ "save" | i18n }}
</button>
<button type="button" bitButton bitFormButton buttonType="secondary" (click)="cancel()">
{{ "cancel" | i18n }}
</button>
</ng-container>
</bit-dialog>
</form>

View File

@ -0,0 +1,164 @@
import { DialogRef } from "@angular/cdk/dialog";
import { CommonModule } from "@angular/common";
import { Component, OnDestroy, OnInit } from "@angular/core";
import {
AbstractControl,
FormBuilder,
FormControl,
FormGroup,
ReactiveFormsModule,
ValidationErrors,
ValidatorFn,
} from "@angular/forms";
import { Subject, firstValueFrom } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import {
EnvironmentService,
Region,
} from "@bitwarden/common/platform/abstractions/environment.service";
import {
AsyncActionsModule,
ButtonModule,
DialogModule,
DialogService,
FormFieldModule,
LinkModule,
TypographyModule,
} from "@bitwarden/components";
/**
* Validator for self-hosted environment settings form.
* It enforces that at least one URL is provided.
*/
function selfHostedEnvSettingsFormValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const formGroup = control as FormGroup;
const baseUrl = formGroup.get("baseUrl")?.value;
const webVaultUrl = formGroup.get("webVaultUrl")?.value;
const apiUrl = formGroup.get("apiUrl")?.value;
const identityUrl = formGroup.get("identityUrl")?.value;
const iconsUrl = formGroup.get("iconsUrl")?.value;
const notificationsUrl = formGroup.get("notificationsUrl")?.value;
if (baseUrl || webVaultUrl || apiUrl || identityUrl || iconsUrl || notificationsUrl) {
return null; // valid
} else {
return { atLeastOneUrlIsRequired: true }; // invalid
}
};
}
/**
* Dialog for configuring self-hosted environment settings.
*/
@Component({
standalone: true,
selector: "auth-registration-self-hosted-env-config-dialog",
templateUrl: "registration-self-hosted-env-config-dialog.component.html",
imports: [
CommonModule,
JslibModule,
DialogModule,
ButtonModule,
LinkModule,
TypographyModule,
ReactiveFormsModule,
FormFieldModule,
AsyncActionsModule,
],
})
export class RegistrationSelfHostedEnvConfigDialogComponent implements OnInit, OnDestroy {
/**
* Opens the dialog.
* @param dialogService - Dialog service.
* @returns Promise that resolves to true if the dialog was closed with a successful result, false otherwise.
*/
static async open(dialogService: DialogService): Promise<boolean> {
const dialogRef = dialogService.open<boolean>(RegistrationSelfHostedEnvConfigDialogComponent, {
disableClose: false,
});
const dialogResult = await firstValueFrom(dialogRef.closed);
return dialogResult;
}
formGroup = this.formBuilder.group(
{
baseUrl: [null],
webVaultUrl: [null],
apiUrl: [null],
identityUrl: [null],
iconsUrl: [null],
notificationsUrl: [null],
},
{ validators: selfHostedEnvSettingsFormValidator() },
);
get baseUrl(): FormControl {
return this.formGroup.get("baseUrl") as FormControl;
}
get webVaultUrl(): FormControl {
return this.formGroup.get("webVaultUrl") as FormControl;
}
get apiUrl(): FormControl {
return this.formGroup.get("apiUrl") as FormControl;
}
get identityUrl(): FormControl {
return this.formGroup.get("identityUrl") as FormControl;
}
get iconsUrl(): FormControl {
return this.formGroup.get("iconsUrl") as FormControl;
}
get notificationsUrl(): FormControl {
return this.formGroup.get("notificationsUrl") as FormControl;
}
showCustomEnv = false;
showErrorSummary = false;
private destroy$ = new Subject<void>();
constructor(
private dialogRef: DialogRef<boolean>,
private formBuilder: FormBuilder,
private environmentService: EnvironmentService,
) {}
ngOnInit() {}
submit = async () => {
this.showErrorSummary = false;
if (this.formGroup.invalid) {
this.showErrorSummary = true;
return;
}
await this.environmentService.setEnvironment(Region.SelfHosted, {
base: this.baseUrl.value,
api: this.apiUrl.value,
identity: this.identityUrl.value,
webVault: this.webVaultUrl.value,
icons: this.iconsUrl.value,
notifications: this.notificationsUrl.value,
});
this.dialogRef.close(true);
};
async cancel() {
this.dialogRef.close(false);
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}

View File

@ -1,5 +1,9 @@
<ng-container *ngIf="state === RegistrationStartState.USER_DATA_ENTRY">
<form [formGroup]="formGroup" [bitSubmit]="submit">
<auth-registration-env-selector
(selectedRegionChange)="handleSelectedRegionChange($event)"
></auth-registration-env-selector>
<bit-form-field>
<bit-label>{{ "emailAddress" | i18n }}</bit-label>
<input

View File

@ -12,6 +12,7 @@ import { ActivatedRoute } from "@angular/router";
import { Subject, takeUntil } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { RegionConfig, Region } from "@bitwarden/common/platform/abstractions/environment.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import {
AsyncActionsModule,
@ -23,6 +24,7 @@ import {
} from "@bitwarden/components";
import { RegistrationCheckEmailIcon } from "../../icons/registration-check-email.icon";
import { RegistrationEnvSelectorComponent } from "../registration-env-selector/registration-env-selector.component";
export enum RegistrationStartState {
USER_DATA_ENTRY = "UserDataEntry",
@ -43,6 +45,7 @@ export enum RegistrationStartState {
ButtonModule,
LinkModule,
IconModule,
RegistrationEnvSelectorComponent,
],
})
export class RegistrationStartComponent implements OnInit, OnDestroy {
@ -84,6 +87,7 @@ export class RegistrationStartComponent implements OnInit, OnDestroy {
private route: ActivatedRoute,
private platformUtilsService: PlatformUtilsService,
) {
// TODO: this needs to update if user selects self hosted
this.isSelfHost = platformUtilsService.isSelfHost();
}
@ -116,6 +120,10 @@ export class RegistrationStartComponent implements OnInit, OnDestroy {
this.registrationStartStateChange.emit(this.state);
};
handleSelectedRegionChange(region: RegionConfig | Region.SelfHosted | null) {
this.isSelfHost = region === Region.SelfHosted;
}
private validateForm(): boolean {
this.formGroup.markAllAsTouched();

View File

@ -7,22 +7,72 @@ import * as stories from "./registration-start.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.
verification stage gated registration process. It collects the environment (required), 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
## Web Examples
<Story of={stories.CloudExample} />
Note that the self hosted option is not present in the environment selector.
### Self Hosted Example
### US Region
<Story of={stories.SelfHostExample} />
<Story of={stories.WebUSRegionExample} />
### Query Param Example
### EU Region
<Story of={stories.WebEURegionExample} />
### Query Params
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} />
<Story of={stories.WebUSRegionQueryParamsExample} />
## Desktop
Behavior to note:
- The self hosted option is present in the environment selector.
- If you go from non-self hosted to self hosted, the terms of service and privacy policy checkbox
will disappear.
### US Region
<Story of={stories.DesktopUSRegionExample} />
### EU Region
<Story of={stories.DesktopEURegionExample} />
### Self Hosted
Note the fact that the terms of service and privacy policy checkbox is not present when the
environment is self hosted.
<Story of={stories.DesktopSelfHostExample} />
## Browser Extension
Behavior to note:
- The self hosted option is present in the environment selector.
- If you go from non-self hosted to self hosted, the terms of service and privacy policy checkbox
will disappear.
### US Region
<Story of={stories.BrowserExtensionUSRegionExample} />
### EU Region
<Story of={stories.BrowserExtensionEURegionExample} />
### Self Hosted
Note the fact that the terms of service and privacy policy checkbox is not present when the
environment is self hosted.
<Story of={stories.BrowserExtensionSelfHostExample} />

View File

@ -1,10 +1,30 @@
import { importProvidersFrom } from "@angular/core";
import { ReactiveFormsModule } from "@angular/forms";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
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 { ClientType } from "@bitwarden/common/enums";
import {
Environment,
EnvironmentService,
Region,
Urls,
} from "@bitwarden/common/platform/abstractions/environment.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import {
AsyncActionsModule,
ButtonModule,
DialogModule,
FormFieldModule,
LinkModule,
SelectModule,
ToastOptions,
ToastService,
TypographyModule,
} from "@bitwarden/components";
import { PreloadedEnglishI18nModule } from "../../../../../../apps/web/src/app/core/tests";
@ -15,52 +35,70 @@ export default {
component: RegistrationStartComponent,
} as Meta;
const decorators = (options: { isSelfHost: boolean; queryParams: Params }) => {
const decorators = (options: {
isSelfHost?: boolean;
queryParams?: Params;
clientType?: ClientType;
defaultRegion?: Region;
}) => {
return [
moduleMetadata({
imports: [RouterTestingModule],
imports: [
RouterTestingModule,
DialogModule,
ReactiveFormsModule,
FormFieldModule,
SelectModule,
ButtonModule,
LinkModule,
TypographyModule,
AsyncActionsModule,
BrowserAnimationsModule,
],
providers: [
{
provide: ActivatedRoute,
useValue: { queryParams: of(options.queryParams) },
},
{
provide: PlatformUtilsService,
useValue: {
isSelfHost: () => options.isSelfHost,
} as Partial<PlatformUtilsService>,
useValue: { queryParams: of(options.queryParams || {}) },
},
],
}),
applicationConfig({
providers: [importProvidersFrom(PreloadedEnglishI18nModule)],
providers: [
importProvidersFrom(PreloadedEnglishI18nModule),
{
provide: EnvironmentService,
useValue: {
environment$: of({
getRegion: () => options.defaultRegion || Region.US,
} as Partial<Environment>),
availableRegions: () => [
{ key: Region.US, domain: "bitwarden.com", urls: {} },
{ key: Region.EU, domain: "bitwarden.eu", urls: {} },
],
setEnvironment: (region: Region, urls?: Urls) => Promise.resolve({}),
} as Partial<EnvironmentService>,
},
{
provide: PlatformUtilsService,
useValue: {
isSelfHost: () => options.isSelfHost || false,
getClientType: () => options.clientType || ClientType.Web,
} as Partial<PlatformUtilsService>,
},
{
provide: ToastService,
useValue: {
showToast: (options: ToastOptions) => {},
} as Partial<ToastService>,
},
],
}),
];
};
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 = {
export const WebUSRegionExample: Story = {
render: (args) => ({
props: args,
template: `
@ -68,7 +106,120 @@ export const QueryParamsExample: Story = {
`,
}),
decorators: decorators({
isSelfHost: false,
clientType: ClientType.Web,
queryParams: {},
defaultRegion: Region.US,
}),
};
export const WebEURegionExample: Story = {
render: (args) => ({
props: args,
template: `
<auth-registration-start></auth-registration-start>
`,
}),
decorators: decorators({
clientType: ClientType.Web,
queryParams: {},
defaultRegion: Region.EU,
}),
};
export const WebUSRegionQueryParamsExample: Story = {
render: (args) => ({
props: args,
template: `
<auth-registration-start></auth-registration-start>
`,
}),
decorators: decorators({
clientType: ClientType.Web,
defaultRegion: Region.US,
queryParams: { email: "jaredWasHere@bitwarden.com", emailReadonly: "true" },
}),
};
export const DesktopUSRegionExample: Story = {
render: (args) => ({
props: args,
template: `
<auth-registration-start></auth-registration-start>
`,
}),
decorators: decorators({
clientType: ClientType.Desktop,
defaultRegion: Region.US,
isSelfHost: false,
}),
};
export const DesktopEURegionExample: Story = {
render: (args) => ({
props: args,
template: `
<auth-registration-start></auth-registration-start>
`,
}),
decorators: decorators({
clientType: ClientType.Desktop,
defaultRegion: Region.EU,
isSelfHost: false,
}),
};
export const DesktopSelfHostExample: Story = {
render: (args) => ({
props: args,
template: `
<auth-registration-start></auth-registration-start>
`,
}),
decorators: decorators({
clientType: ClientType.Desktop,
isSelfHost: true,
defaultRegion: Region.SelfHosted,
}),
};
export const BrowserExtensionUSRegionExample: Story = {
render: (args) => ({
props: args,
template: `
<auth-registration-start></auth-registration-start>
`,
}),
decorators: decorators({
clientType: ClientType.Browser,
defaultRegion: Region.US,
isSelfHost: false,
}),
};
export const BrowserExtensionEURegionExample: Story = {
render: (args) => ({
props: args,
template: `
<auth-registration-start></auth-registration-start>
`,
}),
decorators: decorators({
clientType: ClientType.Browser,
defaultRegion: Region.EU,
isSelfHost: false,
}),
};
export const BrowserExtensionSelfHostExample: Story = {
render: (args) => ({
props: args,
template: `
<auth-registration-start></auth-registration-start>
`,
}),
decorators: decorators({
clientType: ClientType.Browser,
isSelfHost: true,
defaultRegion: Region.SelfHosted,
}),
};