1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-07-06 12:16:10 +02:00

[PM-2411] Update billing-sync-api-key to use Dialog (#6537)

* Update billing-sync-api-key dialog

* Oscar's feedback
This commit is contained in:
Alex Morask 2023-10-16 12:10:04 -04:00 committed by GitHub
parent c3856ce821
commit b1a92ba04b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 124 additions and 166 deletions

View File

@ -1,117 +1,75 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="billingSyncApiKeyTitle"> <form [formGroup]="formGroup" [bitSubmit]="submit">
<div class="modal-dialog modal-dialog-scrollable" role="document"> <bit-dialog>
<form <h1 bitDialogTitle>
class="modal-content" {{ (hasBillingToken ? "viewBillingSyncToken" : "generateBillingSyncToken") | i18n }}
#form </h1>
(ngSubmit)="submit()" <div bitDialogContent>
[appApiAction]="formPromise" <app-user-verification formControlName="verification" *ngIf="!clientSecret">
ngNativeValidate </app-user-verification>
>
<div class="modal-header">
<h1 class="modal-title" id="billingSyncApiKeyTitle">
{{ (hasBillingToken ? "viewBillingSyncToken" : "generateBillingSyncToken") | i18n }}
</h1>
<button
type="button"
class="close"
data-dismiss="modal"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<app-user-verification
[(ngModel)]="masterPassword"
ngDefaultControl
name="secret"
*ngIf="!clientSecret"
>
</app-user-verification>
<ng-container *ngIf="clientSecret && showRotateScreen">
<p>{{ "rotateBillingSyncTokenTitle" | i18n }}</p>
<app-callout type="warning">
{{ "rotateBillingSyncTokenWarning" | i18n }}
</app-callout>
</ng-container>
<div *ngIf="clientSecret && !showRotateScreen"> <ng-container *ngIf="clientSecret && showRotateScreen">
<p>{{ "copyPasteBillingSync" | i18n }}</p> <p>{{ "rotateBillingSyncTokenTitle" | i18n }}</p>
<label for="clientSecret">{{ "billingSyncKey" | i18n }}</label> <bit-callout type="warning">
<div class="input-group"> {{ "rotateBillingSyncTokenWarning" | i18n }}
<input </bit-callout>
id="clientSecret" </ng-container>
class="form-control text-monospace"
type="text" <div *ngIf="clientSecret && !showRotateScreen">
[(ngModel)]="clientSecret" <p>{{ "copyPasteBillingSync" | i18n }}</p>
name="clientSecret" <bit-form-field>
disabled <bit-label>{{ "billingSyncKey" | i18n }}</bit-label>
/> <input
<div class="input-group-append"> bitInput
<button id="clientSecret"
type="button" type="text"
class="btn btn-outline-secondary" [value]="clientSecret"
(click)="copy()" class="tw-font-mono"
[appA11yTitle]="'copy' | i18n" disabled
> />
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i> <button
</button> bitIconButton="bwi-clone"
</div> bitSuffix
</div> type="button"
<div class="small text-muted mt-2" *ngIf="showLastSyncText"> [appCopyClick]="clientSecret"
<b class="font-weight-semibold">{{ "lastSync" | i18n }}:</b> [appA11yTitle]="'copyValue' | i18n"
{{ lastSyncDate | date : "medium" }} ></button>
</div> </bit-form-field>
<div class="small text-danger mt-2" *ngIf="showAwaitingSyncText"> <div class="tw-mt-2 tw-text-sm tw-text-muted" *ngIf="showLastSyncText">
<i class="bwi bwi-error"></i> <b class="tw-font-semibold">{{ "lastSync" | i18n }}:</b>
{{ {{ lastSyncDate | date : "medium" }}
(daysBetween === 1 ? "awaitingSyncSingular" : "awaitingSyncPlural") </div>
| i18n : daysBetween <div class="tw-mt-2 tw-text-sm tw-text-danger" *ngIf="showAwaitingSyncText">
}} <i class="bwi bwi-error"></i>
</div> {{
(daysBetween === 1 ? "awaitingSyncSingular" : "awaitingSyncPlural") | i18n : daysBetween
}}
</div> </div>
</div> </div>
<div class="modal-footer"> </div>
<button <ng-container bitDialogFooter>
type="submit" <button
class="btn btn-primary btn-submit" type="submit"
[disabled]="form.loading" bitButton
*ngIf="!clientSecret || showRotateScreen" bitFormButton
> buttonType="primary"
<i *ngIf="!clientSecret || showRotateScreen"
class="bwi bwi-spinner bwi-spin" >
title="{{ 'loading' | i18n }}" {{ submitButtonText }}
*ngIf="form.loading" </button>
></i> <button bitButton bitDialogClose type="button" *ngIf="!showRotateScreen">
<span> {{ "close" | i18n }}
{{ submitButtonText }} </button>
</span> <button bitButton type="button" *ngIf="showRotateScreen" (click)="cancelRotate()">
</button> {{ "cancel" | i18n }}
<button </button>
type="button" <button
class="btn btn-outline-secondary" bitButton
data-dismiss="modal" type="button"
*ngIf="!showRotateScreen" *ngIf="clientSecret && !showRotateScreen"
> (click)="rotateToken()"
{{ "close" | i18n }} >
</button> {{ "rotateToken" | i18n }}
<button </button>
type="button" </ng-container>
class="btn btn-outline-secondary" </bit-dialog>
*ngIf="showRotateScreen" </form>
(click)="cancelRotate()"
>
{{ "cancel" | i18n }}
</button>
<button
type="button"
class="btn btn-outline-secondary"
*ngIf="clientSecret && !showRotateScreen"
(click)="rotateToken()"
>
{{ "rotateToken" | i18n }}
</button>
</div>
</form>
</div>
</div>

View File

@ -1,6 +1,7 @@
import { Component } from "@angular/core"; import { DIALOG_DATA } from "@angular/cdk/dialog";
import { Component, Inject } from "@angular/core";
import { FormControl, FormGroup, Validators } from "@angular/forms";
import { ModalConfig } from "@bitwarden/angular/services/modal.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { OrganizationApiKeyType } from "@bitwarden/common/admin-console/enums"; import { OrganizationApiKeyType } from "@bitwarden/common/admin-console/enums";
@ -8,8 +9,10 @@ import { OrganizationApiKeyRequest } from "@bitwarden/common/admin-console/model
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { ApiKeyResponse } from "@bitwarden/common/auth/models/response/api-key.response"; import { ApiKeyResponse } from "@bitwarden/common/auth/models/response/api-key.response";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Verification } from "@bitwarden/common/types/verification"; import { Verification } from "@bitwarden/common/types/verification";
import { DialogService } from "@bitwarden/components";
export interface BillingSyncApiModalData { export interface BillingSyncApiModalData {
organizationId: string; organizationId: string;
@ -20,59 +23,66 @@ export interface BillingSyncApiModalData {
templateUrl: "billing-sync-api-key.component.html", templateUrl: "billing-sync-api-key.component.html",
}) })
export class BillingSyncApiKeyComponent { export class BillingSyncApiKeyComponent {
organizationId: string; protected organizationId: string;
hasBillingToken: boolean; protected hasBillingToken: boolean;
protected formGroup = new FormGroup({
verification: new FormControl<Verification>(null, Validators.required),
});
showRotateScreen: boolean; showRotateScreen: boolean;
masterPassword: Verification;
formPromise: Promise<ApiKeyResponse>;
clientSecret?: string; clientSecret?: string;
keyRevisionDate?: Date; keyRevisionDate?: Date;
lastSyncDate?: Date = null; lastSyncDate?: Date;
constructor( constructor(
@Inject(DIALOG_DATA) protected data: BillingSyncApiModalData,
private userVerificationService: UserVerificationService, private userVerificationService: UserVerificationService,
private apiService: ApiService, private apiService: ApiService,
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService, private i18nService: I18nService,
private organizationApiService: OrganizationApiServiceAbstraction, private organizationApiService: OrganizationApiServiceAbstraction,
modalConfig: ModalConfig<BillingSyncApiModalData> private logService: LogService
) { ) {
this.organizationId = modalConfig.data.organizationId; this.organizationId = data.organizationId;
this.hasBillingToken = modalConfig.data.hasBillingToken; this.hasBillingToken = data.hasBillingToken;
} }
copy() { copy() {
this.platformUtilsService.copyToClipboard(this.clientSecret); this.platformUtilsService.copyToClipboard(this.clientSecret);
} }
async submit() { submit = async () => {
if (this.showRotateScreen) { try {
this.formPromise = this.userVerificationService const request = this.userVerificationService
.buildRequest(this.masterPassword, OrganizationApiKeyRequest) .buildRequest(this.formGroup.value.verification, OrganizationApiKeyRequest)
.then((request) => { .then((request) => {
request.type = OrganizationApiKeyType.BillingSync; request.type = OrganizationApiKeyType.BillingSync;
return request;
});
if (this.showRotateScreen) {
const response = await request.then((request) => {
return this.organizationApiService.rotateApiKey(this.organizationId, request); return this.organizationApiService.rotateApiKey(this.organizationId, request);
}); });
const response = await this.formPromise; await this.load(response);
await this.load(response); this.showRotateScreen = false;
this.showRotateScreen = false; this.platformUtilsService.showToast(
this.platformUtilsService.showToast( "success",
"success", null,
null, this.i18nService.t("billingSyncApiKeyRotated")
this.i18nService.t("billingSyncApiKeyRotated") );
); } else {
} else { const response = await request.then((request) => {
this.formPromise = this.userVerificationService
.buildRequest(this.masterPassword, OrganizationApiKeyRequest)
.then((request) => {
request.type = OrganizationApiKeyType.BillingSync;
return this.organizationApiService.getOrCreateApiKey(this.organizationId, request); return this.organizationApiService.getOrCreateApiKey(this.organizationId, request);
}); });
const response = await this.formPromise; await this.load(response);
await this.load(response); }
} catch (e) {
this.logService.error(e);
throw e;
} }
} };
async load(response: ApiKeyResponse) { async load(response: ApiKeyResponse) {
this.clientSecret = response.apiKey; this.clientSecret = response.apiKey;
@ -116,4 +126,8 @@ export class BillingSyncApiKeyComponent {
get daysBetween(): number { get daysBetween(): number {
return this.dayDiff(this.keyRevisionDate, new Date()); return this.dayDiff(this.keyRevisionDate, new Date());
} }
static open(dialogService: DialogService, data: BillingSyncApiModalData) {
return dialogService.open(BillingSyncApiKeyComponent, { data });
}
} }

View File

@ -1,9 +1,8 @@
import { DatePipe } from "@angular/common"; import { DatePipe } from "@angular/common";
import { Component, OnDestroy, OnInit } from "@angular/core"; import { Component, OnDestroy, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router"; import { ActivatedRoute } from "@angular/router";
import { concatMap, Subject, takeUntil } from "rxjs"; import { concatMap, firstValueFrom, Subject, takeUntil } from "rxjs";
import { ModalConfig, ModalService } from "@bitwarden/angular/services/modal.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
@ -18,10 +17,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { DialogService } from "@bitwarden/components"; import { DialogService } from "@bitwarden/components";
import { import { BillingSyncApiKeyComponent } from "./billing-sync-api-key.component";
BillingSyncApiKeyComponent,
BillingSyncApiModalData,
} from "./billing-sync-api-key.component";
import { SecretsManagerSubscriptionOptions } from "./sm-adjust-subscription.component"; import { SecretsManagerSubscriptionOptions } from "./sm-adjust-subscription.component";
@Component({ @Component({
@ -54,7 +50,6 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService, private i18nService: I18nService,
private logService: LogService, private logService: LogService,
private modalService: ModalService,
private organizationService: OrganizationService, private organizationService: OrganizationService,
private organizationApiService: OrganizationApiServiceAbstraction, private organizationApiService: OrganizationApiServiceAbstraction,
private route: ActivatedRoute, private route: ActivatedRoute,
@ -330,22 +325,13 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
} }
async manageBillingSync() { async manageBillingSync() {
const modalConfig: ModalConfig<BillingSyncApiModalData> = { const dialogRef = BillingSyncApiKeyComponent.open(this.dialogService, {
data: { organizationId: this.organizationId,
organizationId: this.organizationId, hasBillingToken: this.hasBillingSyncToken,
hasBillingToken: this.hasBillingSyncToken, });
},
};
const modalRef = this.modalService.open(BillingSyncApiKeyComponent, modalConfig);
modalRef.onClosed await firstValueFrom(dialogRef.closed);
.pipe( this.load();
concatMap(async () => {
this.load();
}),
takeUntil(this.destroy$)
)
.subscribe();
} }
closeDownloadLicense() { closeDownloadLicense() {