mirror of
https://github.com/bitwarden/browser
synced 2025-12-17 00:33:44 +00:00
[AC-1218] Add ability to delete Provider Portals (#8685)
* initial commit * add changes from running prettier * resolve the linx issue * resolve the lint issue * resolving lint error * correct the redirect issue * resolve pr commit * Add a feature flag * move the new component to adminconsole * resolve some pr comments * move the endpoint from ApiService to providerApiService * move provider endpoints to the provider-api class * change the header * resolve some pr comments
This commit is contained in:
@@ -8,6 +8,7 @@ import { OrganizationPlansComponent, TaxInfoComponent } from "@bitwarden/web-vau
|
||||
import { PaymentMethodWarningsModule } from "@bitwarden/web-vault/app/billing/shared";
|
||||
import { OssModule } from "@bitwarden/web-vault/app/oss.module";
|
||||
|
||||
import { DangerZoneComponent } from "../../../../../../apps/web/src/app/auth/settings/account/danger-zone.component";
|
||||
import { ManageClientOrganizationSubscriptionComponent } from "../../billing/providers/clients/manage-client-organization-subscription.component";
|
||||
import { ManageClientOrganizationsComponent } from "../../billing/providers/clients/manage-client-organizations.component";
|
||||
|
||||
@@ -40,6 +41,7 @@ import { SetupComponent } from "./setup/setup.component";
|
||||
ProvidersLayoutComponent,
|
||||
PaymentMethodWarningsModule,
|
||||
TaxInfoComponent,
|
||||
DangerZoneComponent,
|
||||
],
|
||||
declarations: [
|
||||
AcceptProviderComponent,
|
||||
|
||||
@@ -1,51 +1,58 @@
|
||||
<app-header></app-header>
|
||||
|
||||
<div *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<form
|
||||
*ngIf="provider && !loading"
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
[appApiAction]="formPromise"
|
||||
ngNativeValidate
|
||||
>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<label for="name">{{ "providerName" | i18n }}</label>
|
||||
<input
|
||||
id="name"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="Name"
|
||||
[(ngModel)]="provider.name"
|
||||
[disabled]="selfHosted"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="billingEmail">{{ "billingEmail" | i18n }}</label>
|
||||
<input
|
||||
id="billingEmail"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="BillingEmail"
|
||||
[(ngModel)]="provider.billingEmail"
|
||||
[disabled]="selfHosted"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<bit-avatar [text]="provider.name" [id]="provider.id" size="large"></bit-avatar>
|
||||
</div>
|
||||
<bit-container>
|
||||
<div *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "save" | i18n }}</span>
|
||||
</button>
|
||||
</form>
|
||||
<form
|
||||
*ngIf="provider && !loading"
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
[appApiAction]="formPromise"
|
||||
ngNativeValidate
|
||||
>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<label for="name">{{ "providerName" | i18n }}</label>
|
||||
<input
|
||||
id="name"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="Name"
|
||||
[(ngModel)]="provider.name"
|
||||
[disabled]="selfHosted"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="billingEmail">{{ "billingEmail" | i18n }}</label>
|
||||
<input
|
||||
id="billingEmail"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="BillingEmail"
|
||||
[(ngModel)]="provider.billingEmail"
|
||||
[disabled]="selfHosted"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<bit-avatar [text]="provider.name" [id]="provider.id" size="large"></bit-avatar>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "save" | i18n }}</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<app-danger-zone *ngIf="enableDeleteProvider$ | async">
|
||||
<button type="button" bitButton buttonType="danger" (click)="deleteProvider()">
|
||||
{{ "deleteProvider" | i18n }}
|
||||
</button>
|
||||
</app-danger-zone>
|
||||
</bit-container>
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
|
||||
import { UserVerificationDialogComponent } from "@bitwarden/auth/angular";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction";
|
||||
import { ProviderUpdateRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-update.request";
|
||||
import { ProviderResponse } from "@bitwarden/common/admin-console/models/response/provider/provider.response";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
@Component({
|
||||
selector: "provider-account",
|
||||
@@ -23,6 +28,11 @@ export class AccountComponent {
|
||||
|
||||
private providerId: string;
|
||||
|
||||
protected enableDeleteProvider$ = this.configService.getFeatureFlag$(
|
||||
FeatureFlag.EnableDeleteProvider,
|
||||
false,
|
||||
);
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private i18nService: I18nService,
|
||||
@@ -30,6 +40,9 @@ export class AccountComponent {
|
||||
private syncService: SyncService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private logService: LogService,
|
||||
private dialogService: DialogService,
|
||||
private configService: ConfigService,
|
||||
private providerApiService: ProviderApiServiceAbstraction,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -38,7 +51,7 @@ export class AccountComponent {
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.providerId = params.providerId;
|
||||
try {
|
||||
this.provider = await this.apiService.getProvider(this.providerId);
|
||||
this.provider = await this.providerApiService.getProvider(this.providerId);
|
||||
} catch (e) {
|
||||
this.logService.error(`Handled exception: ${e}`);
|
||||
}
|
||||
@@ -53,7 +66,7 @@ export class AccountComponent {
|
||||
request.businessName = this.provider.businessName;
|
||||
request.billingEmail = this.provider.billingEmail;
|
||||
|
||||
this.formPromise = this.apiService.putProvider(this.providerId, request).then(() => {
|
||||
this.formPromise = this.providerApiService.putProvider(this.providerId, request).then(() => {
|
||||
return this.syncService.fullSync(true);
|
||||
});
|
||||
await this.formPromise;
|
||||
@@ -62,4 +75,60 @@ export class AccountComponent {
|
||||
this.logService.error(`Handled exception: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteProvider() {
|
||||
const providerClients = await this.apiService.getProviderClients(this.providerId);
|
||||
if (providerClients.data != null && providerClients.data.length > 0) {
|
||||
await this.dialogService.openSimpleDialog({
|
||||
title: { key: "deleteProviderName", placeholders: [this.provider.name] },
|
||||
content: { key: "deleteProviderWarningDesc", placeholders: [this.provider.name] },
|
||||
acceptButtonText: { key: "ok" },
|
||||
type: "danger",
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const userVerified = await this.verifyUser();
|
||||
if (!userVerified) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.formPromise = this.providerApiService.deleteProvider(this.providerId);
|
||||
try {
|
||||
await this.formPromise;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
this.i18nService.t("providerDeleted"),
|
||||
this.i18nService.t("providerDeletedDesc"),
|
||||
);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
this.formPromise = null;
|
||||
}
|
||||
|
||||
private async verifyUser(): Promise<boolean> {
|
||||
const confirmDescription = "deleteProviderConfirmation";
|
||||
const result = await UserVerificationDialogComponent.open(this.dialogService, {
|
||||
title: "deleteProvider",
|
||||
bodyText: confirmDescription,
|
||||
confirmButtonOptions: {
|
||||
text: "deleteProvider",
|
||||
type: "danger",
|
||||
},
|
||||
});
|
||||
|
||||
// Handle the result of the dialog based on user action and verification success
|
||||
if (result.userAction === "cancel") {
|
||||
// User cancelled the dialog
|
||||
return false;
|
||||
}
|
||||
|
||||
// User confirmed the dialog so check verification success
|
||||
if (!result.verificationSuccess) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction";
|
||||
import { ProviderSetupRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-setup.request";
|
||||
import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
@@ -50,10 +50,10 @@ export class SetupComponent implements OnInit {
|
||||
private i18nService: I18nService,
|
||||
private route: ActivatedRoute,
|
||||
private cryptoService: CryptoService,
|
||||
private apiService: ApiService,
|
||||
private syncService: SyncService,
|
||||
private validationService: ValidationService,
|
||||
private configService: ConfigService,
|
||||
private providerApiService: ProviderApiServiceAbstraction,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -80,7 +80,7 @@ export class SetupComponent implements OnInit {
|
||||
|
||||
// Check if provider exists, redirect if it does
|
||||
try {
|
||||
const provider = await this.apiService.getProvider(this.providerId);
|
||||
const provider = await this.providerApiService.getProvider(this.providerId);
|
||||
if (provider.name != null) {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
@@ -128,7 +128,7 @@ export class SetupComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
const provider = await this.apiService.postProviderSetup(this.providerId, request);
|
||||
const provider = await this.providerApiService.postProviderSetup(this.providerId, request);
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("providerSetup"));
|
||||
await this.syncService.fullSync(true);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user