1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-06 03:33:30 +00:00

implement the ticket requirement

This commit is contained in:
Cy Okeke
2025-12-29 14:09:13 +01:00
parent d4a276f1de
commit 6fc680fb50
9 changed files with 158 additions and 42 deletions

View File

@@ -2,6 +2,7 @@ import { NgModule } from "@angular/core";
import { BaseCardComponent } from "@bitwarden/components";
import { PricingCardComponent } from "@bitwarden/pricing";
import { AdditionalOptionsCardComponent, StorageCardComponent } from "@bitwarden/subscription";
import {
EnterBillingAddressComponent,
EnterPaymentMethodComponent,
@@ -24,6 +25,8 @@ import { UserSubscriptionComponent } from "./user-subscription.component";
EnterBillingAddressComponent,
PricingCardComponent,
BaseCardComponent,
StorageCardComponent,
AdditionalOptionsCardComponent,
],
declarations: [SubscriptionComponent, BillingHistoryViewComponent, UserSubscriptionComponent],
})

View File

@@ -138,47 +138,28 @@
</div>
</ng-container>
<div class="tw-max-w-[1340px]" *ngIf="!selfHosted">
<h3 bitTypography="h3" class="tw-mt-8">{{ "storage" | i18n }}</h3>
<p bitTypography="body1">
{{ "subscriptionStorage" | i18n: sub.maxStorageGb || 0 : sub.storageName || "0 MB" }}
</p>
<bit-progress [barWidth]="storagePercentage" bgColor="success" size="default"></bit-progress>
<ng-container *ngIf="subscription && !subscription.cancelled && !subscriptionMarkedForCancel">
<div class="tw-mt-3">
<div class="tw-flex tw-gap-4">
<button bitButton type="button" buttonType="secondary" (click)="adjustStorage(true)">
{{ "addStorage" | i18n }}
</button>
<button bitButton type="button" buttonType="secondary" (click)="adjustStorage(false)">
{{ "removeStorage" | i18n }}
</button>
</div>
</div>
</ng-container>
<h3 bitTypography="h3" class="tw-mt-16">{{ "additionalOptions" | i18n }}</h3>
<p bitTypography="body1" class="tw-mt-3">{{ "additionalOptionsDesc" | i18n }}</p>
<div class="tw-flex tw-gap-4 tw-mt-3">
<button
bitButton
type="button"
buttonType="secondary"
(click)="downloadLicense()"
*ngIf="!subscription || !subscription.cancelled"
>
{{ "downloadLicense" | i18n }}
</button>
<button
bitButton
#cancelBtn
type="button"
buttonType="danger"
(click)="cancelSubscription()"
[appApiAction]="cancelPromise"
[disabled]="$any(cancelBtn).loading()"
*ngIf="subscription && !subscription.cancelled && !subscriptionMarkedForCancel"
>
{{ "cancelSubscription" | i18n }}
</button>
<div class="tw-mt-10">
<app-storage-card
[maxStorageGb]="sub.maxStorageGb"
[storageName]="sub.storageName"
[storagePercentage]="storagePercentage"
[showActions]="subscription && !subscription.cancelled && !subscriptionMarkedForCancel"
[isSubscriptionCancelled]="subscription?.cancelled || subscriptionMarkedForCancel"
(addStorage)="adjustStorage(true)"
(removeStorage)="adjustStorage(false)"
></app-storage-card>
</div>
<div class="tw-mt-10">
<app-additional-options-card
[showDownloadLicense]="!subscription || !subscription.cancelled"
[showCancelSubscription]="
subscription && !subscription.cancelled && !subscriptionMarkedForCancel
"
[cancelPromise]="cancelPromise"
(downloadLicense)="downloadLicense()"
(cancelSubscription)="cancelSubscription()"
></app-additional-options-card>
</div>
</div>
</ng-container>

View File

@@ -1,3 +1,4 @@
import { StorageRequest } from "../../../models/request/storage.request";
import {
BillingInvoiceResponse,
BillingTransactionResponse,
@@ -9,4 +10,6 @@ export abstract class AccountBillingApiServiceAbstraction {
startAfter?: string,
): Promise<BillingInvoiceResponse[]>;
abstract getBillingTransactions(startAfter?: string): Promise<BillingTransactionResponse[]>;
abstract getUserLicense(): Promise<any>;
abstract putAccountStorage(request: StorageRequest): Promise<any>;
}

View File

@@ -1,4 +1,5 @@
import { ApiService } from "../../../abstractions/api.service";
import { StorageRequest } from "../../../models/request/storage.request";
import { AccountBillingApiServiceAbstraction } from "../../abstractions/account/account-billing-api.service.abstraction";
import {
BillingInvoiceResponse,
@@ -45,4 +46,12 @@ export class AccountBillingApiService implements AccountBillingApiServiceAbstrac
);
return r?.map((i: any) => new BillingTransactionResponse(i)) || [];
}
async getUserLicense(): Promise<any> {
return await this.apiService.send("GET", "/account/billing/vnext/license", null, true, true);
}
async putAccountStorage(request: StorageRequest): Promise<any> {
return await this.apiService.send("PUT", "/account/billing/vnext/storage", request, true, true);
}
}

View File

@@ -0,0 +1,27 @@
<bit-card>
<h3 bitTypography="h3" class="tw-mt-0">{{ "additionalOptions" | i18n }}</h3>
<p bitTypography="body1">{{ "additionalOptionsDesc" | i18n }}</p>
<div class="tw-flex tw-gap-4 tw-mt-3">
<button
bitButton
type="button"
buttonType="secondary"
(click)="onDownloadLicense()"
*ngIf="showDownloadLicense()"
>
{{ "downloadLicense" | i18n }}
</button>
<button
bitButton
#cancelBtn
type="button"
buttonType="danger"
(click)="onCancelSubscription()"
[appApiAction]="cancelPromise()"
[disabled]="$any(cancelBtn).loading()"
*ngIf="showCancelSubscription()"
>
{{ "cancelSubscription" | i18n }}
</button>
</div>
</bit-card>

View File

@@ -0,0 +1,29 @@
import { CommonModule } from "@angular/common";
import { Component, ChangeDetectionStrategy, input, output } from "@angular/core";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { CardComponent, TypographyModule, ButtonModule } from "@bitwarden/components";
@Component({
selector: "app-additional-options-card",
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [CommonModule, CardComponent, TypographyModule, ButtonModule, JslibModule],
templateUrl: "./additional-options-card.component.html",
})
export class AdditionalOptionsCardComponent {
readonly showDownloadLicense = input<boolean>(true);
readonly showCancelSubscription = input<boolean>(true);
readonly cancelPromise = input<Promise<any> | null>(null);
readonly downloadLicense = output<void>();
readonly cancelSubscription = output<void>();
onDownloadLicense() {
this.downloadLicense.emit();
}
onCancelSubscription() {
this.cancelSubscription.emit();
}
}

View File

@@ -0,0 +1,20 @@
<bit-card>
<h3 bitTypography="h3" class="tw-mt-0">{{ "storage" | i18n }}</h3>
<p bitTypography="body1">
{{ "subscriptionStorage" | i18n: maxStorageGb() || 0 : storageName() || "0 MB" }}
</p>
<bit-progress [barWidth]="storagePercentage()" bgColor="success" size="default"></bit-progress>
<ng-container *ngIf="showActions() && !isSubscriptionCancelled()">
<div class="tw-mt-3">
<div class="tw-flex tw-gap-4">
<button bitButton type="button" buttonType="secondary" (click)="onAddStorage()">
{{ "addStorage" | i18n }}
</button>
<button bitButton type="button" buttonType="secondary" (click)="onRemoveStorage()">
{{ "removeStorage" | i18n }}
</button>
</div>
</div>
</ng-container>
</bit-card>

View File

@@ -0,0 +1,43 @@
import { CommonModule } from "@angular/common";
import { Component, ChangeDetectionStrategy, input, output } from "@angular/core";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import {
ProgressModule,
CardComponent,
TypographyModule,
ButtonModule,
} from "@bitwarden/components";
@Component({
selector: "app-storage-card",
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
CommonModule,
CardComponent,
TypographyModule,
ButtonModule,
ProgressModule,
JslibModule,
],
templateUrl: "./storage-card.component.html",
})
export class StorageCardComponent {
readonly maxStorageGb = input<number>();
readonly storageName = input<string>();
readonly storagePercentage = input<number>();
readonly showActions = input<boolean>(true);
readonly isSubscriptionCancelled = input<boolean>(false);
readonly addStorage = output<void>();
readonly removeStorage = output<void>();
onAddStorage() {
this.addStorage.emit();
}
onRemoveStorage() {
this.removeStorage.emit();
}
}

View File

@@ -1 +1,2 @@
export type Placeholder = unknown;
export { StorageCardComponent } from "./components/storage-card/storage-card.component";
export { AdditionalOptionsCardComponent } from "./components/additional-options-card/additional-options-card.component";