mirror of
https://github.com/bitwarden/browser
synced 2026-01-08 03:23:50 +00:00
[PS-1092] Organization Service Observables (#3462)
* Update imports * Implement observables in a few places * Add tests * Get all clients working * Use _destroy * Address PR feedback * Address PR feedback * Address feedback
This commit is contained in:
@@ -11,7 +11,7 @@ import {
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { PayPalConfig } from "@bitwarden/common/abstractions/environment.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { PaymentMethodType } from "@bitwarden/common/enums/paymentMethodType";
|
||||
|
||||
@@ -10,8 +10,8 @@ import { FolderService } from "@bitwarden/common/abstractions/folder/folder.serv
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector.service";
|
||||
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization-api.service.abstraction";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
|
||||
|
||||
@@ -7,7 +7,7 @@ import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { EmergencyAccessStatusType } from "@bitwarden/common/enums/emergencyAccessStatusType";
|
||||
|
||||
@@ -8,7 +8,7 @@ import { FolderService } from "@bitwarden/common/abstractions/folder/folder.serv
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
|
||||
import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
|
||||
@@ -7,8 +7,8 @@ import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization-api.service.abstraction";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
|
||||
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Component, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.service";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { TokenService } from "@bitwarden/common/abstractions/token.service";
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
[appApiAction]="formPromise"
|
||||
[formGroup]="sponsorshipForm"
|
||||
ngNativeValidate
|
||||
*ngIf="anyOrgsAvailable"
|
||||
*ngIf="anyOrgsAvailable$ | async"
|
||||
>
|
||||
<div class="form-group col-7">
|
||||
<label for="availableSponsorshipOrg">{{ "familiesSponsoringOrgSelect" | i18n }}</label>
|
||||
@@ -34,7 +34,9 @@
|
||||
required
|
||||
>
|
||||
<option disabled="true" value="">-- {{ "select" | i18n }} --</option>
|
||||
<option *ngFor="let o of availableSponsorshipOrgs" [ngValue]="o.id">{{ o.name }}</option>
|
||||
<option *ngFor="let o of availableSponsorshipOrgs$ | async" [ngValue]="o.id">
|
||||
{{ o.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group col-7">
|
||||
@@ -74,7 +76,7 @@
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<ng-container *ngIf="anyActiveSponsorships">
|
||||
<ng-container *ngIf="anyActiveSponsorships$ | async">
|
||||
<div class="border-bottom">
|
||||
<table class="table table-hover table-list">
|
||||
<thead>
|
||||
@@ -86,12 +88,12 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<ng-container *ngFor="let o of activeSponsorshipOrgs">
|
||||
<ng-container *ngFor="let o of activeSponsorshipOrgs$ | async">
|
||||
<tr
|
||||
sponsoring-org-row
|
||||
[sponsoringOrg]="o"
|
||||
[isSelfHosted]="isSelfHosted"
|
||||
(sponsorshipRemoved)="load(true)"
|
||||
(sponsorshipRemoved)="forceReload()"
|
||||
></tr>
|
||||
</ng-container>
|
||||
</tbody>
|
||||
|
||||
@@ -1,30 +1,40 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms";
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { FormBuilder, FormControl, FormGroup, Validators } from "@angular/forms";
|
||||
import { map, Observable, Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { notAllowedValueAsync } from "@bitwarden/angular/validators/notAllowedValueAsync.validator";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
|
||||
import { PlanSponsorshipType } from "@bitwarden/common/enums/planSponsorshipType";
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
|
||||
interface RequestSponsorshipForm {
|
||||
selectedSponsorshipOrgId: FormControl<string>;
|
||||
sponsorshipEmail: FormControl<string>;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "app-sponsored-families",
|
||||
templateUrl: "sponsored-families.component.html",
|
||||
})
|
||||
export class SponsoredFamiliesComponent implements OnInit {
|
||||
export class SponsoredFamiliesComponent implements OnInit, OnDestroy {
|
||||
loading = false;
|
||||
|
||||
availableSponsorshipOrgs: Organization[] = [];
|
||||
activeSponsorshipOrgs: Organization[] = [];
|
||||
availableSponsorshipOrgs$: Observable<Organization[]>;
|
||||
activeSponsorshipOrgs$: Observable<Organization[]>;
|
||||
anyOrgsAvailable$: Observable<boolean>;
|
||||
anyActiveSponsorships$: Observable<boolean>;
|
||||
|
||||
// Conditional display properties
|
||||
formPromise: Promise<any>;
|
||||
formPromise: Promise<void>;
|
||||
|
||||
sponsorshipForm: UntypedFormGroup;
|
||||
sponsorshipForm: FormGroup<RequestSponsorshipForm>;
|
||||
|
||||
private _destroy = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
@@ -32,31 +42,50 @@ export class SponsoredFamiliesComponent implements OnInit {
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private syncService: SyncService,
|
||||
private organizationService: OrganizationService,
|
||||
private formBuilder: UntypedFormBuilder,
|
||||
private formBuilder: FormBuilder,
|
||||
private stateService: StateService
|
||||
) {
|
||||
this.sponsorshipForm = this.formBuilder.group({
|
||||
selectedSponsorshipOrgId: [
|
||||
"",
|
||||
{
|
||||
validators: [Validators.required],
|
||||
},
|
||||
],
|
||||
sponsorshipEmail: [
|
||||
"",
|
||||
{
|
||||
validators: [Validators.email],
|
||||
asyncValidators: [
|
||||
notAllowedValueAsync(async () => await this.stateService.getEmail(), true),
|
||||
],
|
||||
updateOn: "blur",
|
||||
},
|
||||
],
|
||||
this.sponsorshipForm = this.formBuilder.group<RequestSponsorshipForm>({
|
||||
selectedSponsorshipOrgId: new FormControl("", {
|
||||
validators: [Validators.required],
|
||||
}),
|
||||
sponsorshipEmail: new FormControl("", {
|
||||
validators: [Validators.email],
|
||||
asyncValidators: [
|
||||
notAllowedValueAsync(async () => await this.stateService.getEmail(), true),
|
||||
],
|
||||
updateOn: "blur",
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
await this.load();
|
||||
this.availableSponsorshipOrgs$ = this.organizationService.organizations$.pipe(
|
||||
map((orgs) => orgs.filter((o) => o.familySponsorshipAvailable))
|
||||
);
|
||||
|
||||
this.availableSponsorshipOrgs$.pipe(takeUntil(this._destroy)).subscribe((orgs) => {
|
||||
if (orgs.length === 1) {
|
||||
this.sponsorshipForm.patchValue({
|
||||
selectedSponsorshipOrgId: orgs[0].id,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.anyOrgsAvailable$ = this.availableSponsorshipOrgs$.pipe(map((orgs) => orgs.length > 0));
|
||||
|
||||
this.activeSponsorshipOrgs$ = this.organizationService.organizations$.pipe(
|
||||
map((orgs) => orgs.filter((o) => o.familySponsorshipFriendlyName !== null))
|
||||
);
|
||||
|
||||
this.anyActiveSponsorships$ = this.activeSponsorshipOrgs$.pipe(map((orgs) => orgs.length > 0));
|
||||
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this._destroy.next();
|
||||
this._destroy.complete();
|
||||
}
|
||||
|
||||
async submit() {
|
||||
@@ -73,50 +102,23 @@ export class SponsoredFamiliesComponent implements OnInit {
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("sponsorshipCreated"));
|
||||
this.formPromise = null;
|
||||
this.resetForm();
|
||||
await this.load(true);
|
||||
await this.forceReload();
|
||||
}
|
||||
|
||||
async load(forceReload = false) {
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
async forceReload() {
|
||||
this.loading = true;
|
||||
if (forceReload) {
|
||||
await this.syncService.fullSync(true);
|
||||
}
|
||||
|
||||
const allOrgs = await this.organizationService.getAll();
|
||||
this.availableSponsorshipOrgs = allOrgs.filter((org) => org.familySponsorshipAvailable);
|
||||
|
||||
this.activeSponsorshipOrgs = allOrgs.filter(
|
||||
(org) => org.familySponsorshipFriendlyName !== null
|
||||
);
|
||||
|
||||
if (this.availableSponsorshipOrgs.length === 1) {
|
||||
this.sponsorshipForm.patchValue({
|
||||
selectedSponsorshipOrgId: this.availableSponsorshipOrgs[0].id,
|
||||
});
|
||||
}
|
||||
await this.syncService.fullSync(true);
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
get sponsorshipEmailControl() {
|
||||
return this.sponsorshipForm.controls["sponsorshipEmail"];
|
||||
return this.sponsorshipForm.controls.sponsorshipEmail;
|
||||
}
|
||||
|
||||
private async resetForm() {
|
||||
this.sponsorshipForm.reset();
|
||||
}
|
||||
|
||||
get anyActiveSponsorships(): boolean {
|
||||
return this.activeSponsorshipOrgs.length > 0;
|
||||
}
|
||||
|
||||
get anyOrgsAvailable(): boolean {
|
||||
return this.availableSponsorshipOrgs.length > 0;
|
||||
}
|
||||
|
||||
get isSelfHosted(): boolean {
|
||||
return this.platformUtilsService.isSelfHost();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user