[AC-2568] Split billing history calls to separately call for invoices and transactions. Added paging buttons (#10697)
* Split billing history calls to separately call for invoices and transactions. Added paging button * Added missing button types
This commit is contained in:
parent
9e45e32c7d
commit
60e9969017
|
@ -2,17 +2,6 @@
|
||||||
<h2 bitTypography="h2">
|
<h2 bitTypography="h2">
|
||||||
{{ "billingHistory" | i18n }}
|
{{ "billingHistory" | i18n }}
|
||||||
</h2>
|
</h2>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
bitButton
|
|
||||||
buttonType="secondary"
|
|
||||||
(click)="load()"
|
|
||||||
*ngIf="firstLoaded"
|
|
||||||
[disabled]="loading"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-refresh bwi-fw" [ngClass]="{ 'bwi-spin': loading }" aria-hidden="true"></i>
|
|
||||||
{{ "refresh" | i18n }}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<ng-container *ngIf="!firstLoaded && loading">
|
<ng-container *ngIf="!firstLoaded && loading">
|
||||||
<i
|
<i
|
||||||
|
@ -22,6 +11,15 @@
|
||||||
></i>
|
></i>
|
||||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="billing">
|
<ng-container *ngIf="invoices || transactions">
|
||||||
<app-billing-history [billing]="billing"></app-billing-history>
|
<app-billing-history [invoices]="invoices" [transactions]="transactions"></app-billing-history>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
bitButton
|
||||||
|
buttonType="secondary"
|
||||||
|
(click)="load()"
|
||||||
|
*ngIf="hasAdditionalHistory"
|
||||||
|
>
|
||||||
|
{{ "loadMore" | i18n }}
|
||||||
|
</button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
import { Router } from "@angular/router";
|
import { Router } from "@angular/router";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { AccountBillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/account/account-billing-api.service.abstraction";
|
||||||
import { BillingHistoryResponse } from "@bitwarden/common/billing/models/response/billing-history.response";
|
import {
|
||||||
|
BillingInvoiceResponse,
|
||||||
|
BillingTransactionResponse,
|
||||||
|
} from "@bitwarden/common/billing/models/response/billing.response";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -11,12 +14,14 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
||||||
export class BillingHistoryViewComponent implements OnInit {
|
export class BillingHistoryViewComponent implements OnInit {
|
||||||
loading = false;
|
loading = false;
|
||||||
firstLoaded = false;
|
firstLoaded = false;
|
||||||
billing: BillingHistoryResponse;
|
invoices: BillingInvoiceResponse[] = [];
|
||||||
|
transactions: BillingTransactionResponse[] = [];
|
||||||
|
hasAdditionalHistory: boolean = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private apiService: ApiService,
|
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
|
private accountBillingApiService: AccountBillingApiServiceAbstraction,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
@ -35,7 +40,27 @@ export class BillingHistoryViewComponent implements OnInit {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.billing = await this.apiService.getUserBillingHistory();
|
|
||||||
|
const invoicesPromise = this.accountBillingApiService.getBillingInvoices(
|
||||||
|
this.invoices.length > 0 ? this.invoices[this.invoices.length - 1].id : null,
|
||||||
|
);
|
||||||
|
|
||||||
|
const transactionsPromise = this.accountBillingApiService.getBillingTransactions(
|
||||||
|
this.transactions.length > 0
|
||||||
|
? this.transactions[this.transactions.length - 1].createdDate
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
const accountInvoices = await invoicesPromise;
|
||||||
|
const accountTransactions = await transactionsPromise;
|
||||||
|
const pageSize = 5;
|
||||||
|
|
||||||
|
this.invoices = [...this.invoices, ...accountInvoices];
|
||||||
|
this.transactions = [...this.transactions, ...accountTransactions];
|
||||||
|
this.hasAdditionalHistory = !(
|
||||||
|
accountInvoices.length < pageSize && accountTransactions.length < pageSize
|
||||||
|
);
|
||||||
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,4 @@
|
||||||
<app-header>
|
<app-header> </app-header>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
bitButton
|
|
||||||
buttonType="secondary"
|
|
||||||
(click)="load()"
|
|
||||||
class="tw-ml-auto"
|
|
||||||
*ngIf="firstLoaded"
|
|
||||||
[disabled]="loading"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-refresh bwi-fw" [ngClass]="{ 'bwi-spin': loading }" aria-hidden="true"></i>
|
|
||||||
{{ "refresh" | i18n }}
|
|
||||||
</button>
|
|
||||||
</app-header>
|
|
||||||
|
|
||||||
<bit-container>
|
<bit-container>
|
||||||
<ng-container *ngIf="!firstLoaded && loading">
|
<ng-container *ngIf="!firstLoaded && loading">
|
||||||
|
@ -22,7 +9,16 @@
|
||||||
></i>
|
></i>
|
||||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="billing">
|
<ng-container *ngIf="invoices || transactions">
|
||||||
<app-billing-history [billing]="billing"></app-billing-history>
|
<app-billing-history [invoices]="invoices" [transactions]="transactions"></app-billing-history>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
bitButton
|
||||||
|
buttonType="secondary"
|
||||||
|
(click)="load()"
|
||||||
|
*ngIf="hasAdditionalHistory"
|
||||||
|
>
|
||||||
|
{{ "loadMore" | i18n }}
|
||||||
|
</button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</bit-container>
|
</bit-container>
|
||||||
|
|
|
@ -2,8 +2,11 @@ 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, Subject, takeUntil } from "rxjs";
|
||||||
|
|
||||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
import { OrganizationBillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/organizations/organization-billing-api.service.abstraction";
|
||||||
import { BillingHistoryResponse } from "@bitwarden/common/billing/models/response/billing-history.response";
|
import {
|
||||||
|
BillingInvoiceResponse,
|
||||||
|
BillingTransactionResponse,
|
||||||
|
} from "@bitwarden/common/billing/models/response/billing.response";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: "organization-billing-history-view.component.html",
|
templateUrl: "organization-billing-history-view.component.html",
|
||||||
|
@ -11,13 +14,15 @@ import { BillingHistoryResponse } from "@bitwarden/common/billing/models/respons
|
||||||
export class OrgBillingHistoryViewComponent implements OnInit, OnDestroy {
|
export class OrgBillingHistoryViewComponent implements OnInit, OnDestroy {
|
||||||
loading = false;
|
loading = false;
|
||||||
firstLoaded = false;
|
firstLoaded = false;
|
||||||
billing: BillingHistoryResponse;
|
invoices: BillingInvoiceResponse[] = [];
|
||||||
|
transactions: BillingTransactionResponse[] = [];
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
|
hasAdditionalHistory: boolean = false;
|
||||||
|
|
||||||
private destroy$ = new Subject<void>();
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
private organizationBillingApiService: OrganizationBillingApiServiceAbstraction,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
@ -43,8 +48,28 @@ export class OrgBillingHistoryViewComponent implements OnInit, OnDestroy {
|
||||||
if (this.loading) {
|
if (this.loading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.billing = await this.organizationApiService.getBillingHistory(this.organizationId);
|
|
||||||
|
const invoicesPromise = this.organizationBillingApiService.getBillingInvoices(
|
||||||
|
this.organizationId,
|
||||||
|
this.invoices.length > 0 ? this.invoices[this.invoices.length - 1].id : null,
|
||||||
|
);
|
||||||
|
|
||||||
|
const transactionsPromise = this.organizationBillingApiService.getBillingTransactions(
|
||||||
|
this.organizationId,
|
||||||
|
this.transactions.length > 0
|
||||||
|
? this.transactions[this.transactions.length - 1].createdDate
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
const invoices = await invoicesPromise;
|
||||||
|
const transactions = await transactionsPromise;
|
||||||
|
const pageSize = 5;
|
||||||
|
|
||||||
|
this.invoices = [...this.invoices, ...invoices];
|
||||||
|
this.transactions = [...this.transactions, ...transactions];
|
||||||
|
this.hasAdditionalHistory = !(invoices.length < pageSize && transactions.length < pageSize);
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
import { Component, Input } from "@angular/core";
|
import { Component, Input } from "@angular/core";
|
||||||
|
|
||||||
import { PaymentMethodType, TransactionType } from "@bitwarden/common/billing/enums";
|
import { PaymentMethodType, TransactionType } from "@bitwarden/common/billing/enums";
|
||||||
import { BillingHistoryResponse } from "@bitwarden/common/billing/models/response/billing-history.response";
|
import {
|
||||||
|
BillingInvoiceResponse,
|
||||||
|
BillingTransactionResponse,
|
||||||
|
} from "@bitwarden/common/billing/models/response/billing.response";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-billing-history",
|
selector: "app-billing-history",
|
||||||
|
@ -9,19 +12,14 @@ import { BillingHistoryResponse } from "@bitwarden/common/billing/models/respons
|
||||||
})
|
})
|
||||||
export class BillingHistoryComponent {
|
export class BillingHistoryComponent {
|
||||||
@Input()
|
@Input()
|
||||||
billing: BillingHistoryResponse;
|
invoices: BillingInvoiceResponse[];
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
transactions: BillingTransactionResponse[];
|
||||||
|
|
||||||
paymentMethodType = PaymentMethodType;
|
paymentMethodType = PaymentMethodType;
|
||||||
transactionType = TransactionType;
|
transactionType = TransactionType;
|
||||||
|
|
||||||
get invoices() {
|
|
||||||
return this.billing != null ? this.billing.invoices : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
get transactions() {
|
|
||||||
return this.billing != null ? this.billing.transactions : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
paymentMethodClasses(type: PaymentMethodType) {
|
paymentMethodClasses(type: PaymentMethodType) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case PaymentMethodType.Card:
|
case PaymentMethodType.Card:
|
||||||
|
|
|
@ -125,9 +125,13 @@ import {
|
||||||
BillingApiServiceAbstraction,
|
BillingApiServiceAbstraction,
|
||||||
OrganizationBillingServiceAbstraction,
|
OrganizationBillingServiceAbstraction,
|
||||||
} from "@bitwarden/common/billing/abstractions";
|
} from "@bitwarden/common/billing/abstractions";
|
||||||
|
import { AccountBillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/account/account-billing-api.service.abstraction";
|
||||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||||
|
import { OrganizationBillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/organizations/organization-billing-api.service.abstraction";
|
||||||
|
import { AccountBillingApiService } from "@bitwarden/common/billing/services/account/account-billing-api.service";
|
||||||
import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service";
|
import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service";
|
||||||
import { BillingApiService } from "@bitwarden/common/billing/services/billing-api.service";
|
import { BillingApiService } from "@bitwarden/common/billing/services/billing-api.service";
|
||||||
|
import { OrganizationBillingApiService } from "@bitwarden/common/billing/services/organization/organization-billing-api.service";
|
||||||
import { OrganizationBillingService } from "@bitwarden/common/billing/services/organization-billing.service";
|
import { OrganizationBillingService } from "@bitwarden/common/billing/services/organization-billing.service";
|
||||||
import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service";
|
import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||||
|
@ -985,6 +989,16 @@ const safeProviders: SafeProvider[] = [
|
||||||
// subscribes to sync notifications and will update itself based on that.
|
// subscribes to sync notifications and will update itself based on that.
|
||||||
deps: [ApiServiceAbstraction, SyncService],
|
deps: [ApiServiceAbstraction, SyncService],
|
||||||
}),
|
}),
|
||||||
|
safeProvider({
|
||||||
|
provide: OrganizationBillingApiServiceAbstraction,
|
||||||
|
useClass: OrganizationBillingApiService,
|
||||||
|
deps: [ApiServiceAbstraction],
|
||||||
|
}),
|
||||||
|
safeProvider({
|
||||||
|
provide: AccountBillingApiServiceAbstraction,
|
||||||
|
useClass: AccountBillingApiService,
|
||||||
|
deps: [ApiServiceAbstraction],
|
||||||
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: DefaultConfigService,
|
provide: DefaultConfigService,
|
||||||
useClass: DefaultConfigService,
|
useClass: DefaultConfigService,
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import {
|
||||||
|
BillingInvoiceResponse,
|
||||||
|
BillingTransactionResponse,
|
||||||
|
} from "@bitwarden/common/billing/models/response/billing.response";
|
||||||
|
|
||||||
|
export class AccountBillingApiServiceAbstraction {
|
||||||
|
getBillingInvoices: (id: string, startAfter?: string) => Promise<BillingInvoiceResponse[]>;
|
||||||
|
getBillingTransactions: (
|
||||||
|
id: string,
|
||||||
|
startAfter?: string,
|
||||||
|
) => Promise<BillingTransactionResponse[]>;
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import {
|
||||||
|
BillingInvoiceResponse,
|
||||||
|
BillingTransactionResponse,
|
||||||
|
} from "@bitwarden/common/billing/models/response/billing.response";
|
||||||
|
|
||||||
|
export class OrganizationBillingApiServiceAbstraction {
|
||||||
|
getBillingInvoices: (id: string, startAfter?: string) => Promise<BillingInvoiceResponse[]>;
|
||||||
|
getBillingTransactions: (
|
||||||
|
id: string,
|
||||||
|
startAfter?: string,
|
||||||
|
) => Promise<BillingTransactionResponse[]>;
|
||||||
|
}
|
|
@ -29,6 +29,7 @@ export class BillingSourceResponse extends BaseResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BillingInvoiceResponse extends BaseResponse {
|
export class BillingInvoiceResponse extends BaseResponse {
|
||||||
|
id: string;
|
||||||
url: string;
|
url: string;
|
||||||
pdfUrl: string;
|
pdfUrl: string;
|
||||||
number: string;
|
number: string;
|
||||||
|
@ -38,6 +39,7 @@ export class BillingInvoiceResponse extends BaseResponse {
|
||||||
|
|
||||||
constructor(response: any) {
|
constructor(response: any) {
|
||||||
super(response);
|
super(response);
|
||||||
|
this.id = this.getResponseProperty("Id");
|
||||||
this.url = this.getResponseProperty("Url");
|
this.url = this.getResponseProperty("Url");
|
||||||
this.pdfUrl = this.getResponseProperty("PdfUrl");
|
this.pdfUrl = this.getResponseProperty("PdfUrl");
|
||||||
this.number = this.getResponseProperty("Number");
|
this.number = this.getResponseProperty("Number");
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { ApiService } from "../../../abstractions/api.service";
|
||||||
|
import { AccountBillingApiServiceAbstraction } from "../../abstractions/account/account-billing-api.service.abstraction";
|
||||||
|
import {
|
||||||
|
BillingInvoiceResponse,
|
||||||
|
BillingTransactionResponse,
|
||||||
|
} from "../../models/response/billing.response";
|
||||||
|
|
||||||
|
export class AccountBillingApiService implements AccountBillingApiServiceAbstraction {
|
||||||
|
constructor(private apiService: ApiService) {}
|
||||||
|
|
||||||
|
async getBillingInvoices(startAfter?: string): Promise<BillingInvoiceResponse[]> {
|
||||||
|
const queryParams = startAfter ? `?startAfter=${startAfter}` : "";
|
||||||
|
const r = await this.apiService.send(
|
||||||
|
"GET",
|
||||||
|
`/accounts/billing/invoices${queryParams}`,
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
return r?.map((i: any) => new BillingInvoiceResponse(i)) || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBillingTransactions(startAfter?: string): Promise<BillingTransactionResponse[]> {
|
||||||
|
const queryParams = startAfter ? `?startAfter=${startAfter}` : "";
|
||||||
|
const r = await this.apiService.send(
|
||||||
|
"GET",
|
||||||
|
`/accounts/billing/transactions${queryParams}`,
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
return r?.map((i: any) => new BillingTransactionResponse(i)) || [];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { ApiService } from "../../../abstractions/api.service";
|
||||||
|
import { OrganizationBillingApiServiceAbstraction } from "../../abstractions/organizations/organization-billing-api.service.abstraction";
|
||||||
|
import {
|
||||||
|
BillingInvoiceResponse,
|
||||||
|
BillingTransactionResponse,
|
||||||
|
} from "../../models/response/billing.response";
|
||||||
|
|
||||||
|
export class OrganizationBillingApiService implements OrganizationBillingApiServiceAbstraction {
|
||||||
|
constructor(private apiService: ApiService) {}
|
||||||
|
|
||||||
|
async getBillingInvoices(id: string, startAfter?: string): Promise<BillingInvoiceResponse[]> {
|
||||||
|
const queryParams = startAfter ? `?startAfter=${startAfter}` : "";
|
||||||
|
const r = await this.apiService.send(
|
||||||
|
"GET",
|
||||||
|
`/organizations/${id}/billing/invoices${queryParams}`,
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
return r?.map((i: any) => new BillingInvoiceResponse(i)) || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBillingTransactions(
|
||||||
|
id: string,
|
||||||
|
startAfter?: string,
|
||||||
|
): Promise<BillingTransactionResponse[]> {
|
||||||
|
const queryParams = startAfter ? `?startAfter=${startAfter}` : "";
|
||||||
|
const r = await this.apiService.send(
|
||||||
|
"GET",
|
||||||
|
`/organizations/${id}/billing/transactions${queryParams}`,
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
return r?.map((i: any) => new BillingTransactionResponse(i)) || [];
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue