1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-11 05:43:41 +00:00
Files
browser/libs/auth/src/angular/registration/registration-env-selector/registration-env-selector.component.ts
Alec Rippberger 9da80a6cba [PM-8115] Desktop, Extension UI Refresh: Self-hosted Setup Dialog (#11597)
* Reimplement RegistrationSelfHostedEnvConfigDialogComponent

* Update EnvironmentSelectorComponent text based on feature flag.

* Initialize RegistrationSelfHostedEnvConfigDialog with existing values if self hosted

* Cleanup debug

* Add comment

* Remove changes to home and login components

* Remove changes to desktop login component

* Remove changes to browser home component

* Simplify accessing string.

* Add environment selector service.

* Cleanup unused imports in environment-selector

* Launch new env selector dialog from desktop

* Fix lint errors

* Address PR feedback: move dialog component, remove EnvironmentSelectorService, remove unused translation string

* Remove changes to AnonLayout

* PM-8115 - Export Re-usable component from Libs/auth for clean import elsewhere in clients.

* Remove unused accessingString variable

* Add success toast

---------

Co-authored-by: Jared Snider <jsnider@bitwarden.com>
2024-10-28 16:12:57 -05:00

178 lines
6.4 KiB
TypeScript

import { CommonModule } from "@angular/common";
import { Component, EventEmitter, OnDestroy, OnInit, Output } from "@angular/core";
import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from "@angular/forms";
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 { 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 { SelfHostedEnvConfigDialogComponent } from "../../self-hosted-env-config-dialog/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",
templateUrl: "registration-env-selector.component.html",
imports: [CommonModule, JslibModule, ReactiveFormsModule, FormFieldModule, SelectModule],
})
export class RegistrationEnvSelectorComponent implements OnInit, OnDestroy {
@Output() selectedRegionChange = new EventEmitter<RegionConfig | Region.SelfHosted | null>();
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 selectedRegionFromEnv: RegionConfig | Region.SelfHosted;
hideEnvSelector = false;
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;
this.hideEnvSelector = clientType === ClientType.Web && this.platformUtilsService.isSelfHost();
}
async ngOnInit() {
if (this.hideEnvSelector) {
return;
}
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((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;
// Emit the initial value
this.selectedRegionChange.emit(selectedRegionFromEnv);
}),
takeUntil(this.destroy$),
)
.subscribe();
}
private listenForSelectedRegionChanges() {
this.selectedRegion.valueChanges
.pipe(
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 (selectedRegion === Region.SelfHosted) {
return from(SelfHostedEnvConfigDialogComponent.open(this.dialogService)).pipe(
tap((result: boolean | undefined) =>
this.handleSelfHostedEnvConfigDialogResult(result, prevSelectedRegion),
),
);
}
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();
}
}