+
{{ additionalServiceAccounts.quantity }}
- {{ additionalServiceAccounts.translationKey | i18n }} x
- {{ additionalServiceAccounts.cost | currency: "USD" : "symbol" }}
- /
- {{ term }}
+ {{
+ translateWithParams(
+ additionalServiceAccounts.translationKey,
+ additionalServiceAccounts.translationParams
+ )
+ }}
+ @if (!additionalServiceAccounts.hideBreakdown) {
+ x
+ {{ additionalServiceAccounts.cost | currency: "USD" : "symbol" }}
+ /
+ {{ term }}
+ }
-
{{ discountLabel() }}
+
{{ discountLabel() }}
-{{ discountAmount() | currency: "USD" : "symbol" }}
}
+
+ @if (creditAmount() > 0) {
+
+
+ {{ translateWithParams(cart.credit!.translationKey, cart.credit!.translationParams) }}
+
+
+ -{{ creditAmount() | currency: "USD" : "symbol" }}
+
+
+ }
+
-
{{ "estimatedTax" | i18n }}
+
+ {{ "estimatedTax" | i18n }}
+
{{ estimatedTax() | currency: "USD" : "symbol" }}
@@ -149,7 +217,7 @@
-
{{ "total" | i18n }}
+
{{ "total" | i18n }}
{{ total() | currency: "USD" : "symbol" }} / {{ term | i18n }}
diff --git a/libs/pricing/src/components/cart-summary/cart-summary.component.mdx b/libs/pricing/src/components/cart-summary/cart-summary.component.mdx
index d327d5658fe..542181f8264 100644
--- a/libs/pricing/src/components/cart-summary/cart-summary.component.mdx
+++ b/libs/pricing/src/components/cart-summary/cart-summary.component.mdx
@@ -25,8 +25,10 @@ behavior across Bitwarden applications.
- [With Secrets Manager](#with-secrets-manager)
- [With Secrets Manager and Additional Service Accounts](#with-secrets-manager-and-additional-service-accounts)
- [All Products](#all-products)
+ - [With Account Credit](#with-account-credit)
- [With Percent Discount](#with-percent-discount)
- [With Amount Discount](#with-amount-discount)
+ - [With Discount and Credit](#with-discount-and-credit)
- [Custom Header Template](#custom-header-template)
- [Premium Plan](#premium-plan)
- [Families Plan](#families-plan)
@@ -85,9 +87,16 @@ export type Cart = {
};
cadence: "annually" | "monthly"; // Billing period for entire cart
discount?: Discount; // Optional cart-level discount
+ credit?: Credit; // Optional account credit
estimatedTax: number; // Tax amount
};
+export type Credit = {
+ translationKey: string; // Translation key for credit label
+ translationParams?: Array
; // Optional params for translation
+ value: number; // Credit amount to subtract from subtotal
+};
+
import { DiscountTypes, DiscountType } from "@bitwarden/pricing";
export type Discount = {
@@ -330,6 +339,33 @@ Show a cart with all available products:
```
+### With Account Credit
+
+Show cart with account credit applied:
+
+
+
+```html
+
+
+```
+
### With Percent Discount
Show cart with percentage-based discount:
@@ -396,6 +432,42 @@ Show cart with fixed amount discount:
```
+### With Discount and Credit
+
+Show cart with both discount and credit applied:
+
+
+
+```html
+
+
+```
+
### Custom Header Template
Show cart with custom header template:
@@ -466,10 +538,12 @@ Show cart with families plan:
- **Collapsible Interface**: Users can toggle between a summary view showing only the total and a
detailed view showing all line items
- **Line Item Grouping**: Organizes items by product category (Password Manager, Secrets Manager)
-- **Dynamic Calculations**: Automatically calculates subtotals, discounts, taxes, and totals using
- Angular signals and computed values
+- **Dynamic Calculations**: Automatically calculates subtotals, discounts, credits, taxes, and
+ totals using Angular signals and computed values
- **Discount Support**: Displays both percentage-based and fixed-amount discounts with green success
styling
+- **Credit Support**: Shows account credit deductions with clear labeling using i18n translation
+ keys
- **Custom Header Templates**: Optional header input allows for custom header designs while
maintaining cart functionality
- **Flexible Structure**: Accommodates different combinations of products, add-ons, and discounts
diff --git a/libs/pricing/src/components/cart-summary/cart-summary.component.spec.ts b/libs/pricing/src/components/cart-summary/cart-summary.component.spec.ts
index 10975585899..9fabd3ecc59 100644
--- a/libs/pricing/src/components/cart-summary/cart-summary.component.spec.ts
+++ b/libs/pricing/src/components/cart-summary/cart-summary.component.spec.ts
@@ -89,6 +89,8 @@ describe("CartSummaryComponent", () => {
return "Premium membership";
case "discount":
return "discount";
+ case "accountCredit":
+ return "accountCredit";
default:
return key;
}
@@ -253,6 +255,126 @@ describe("CartSummaryComponent", () => {
});
});
+ describe("hideBreakdown Property", () => {
+ it("should hide cost breakdown when hideBreakdown is true for password manager seats", () => {
+ // Arrange
+ const cartWithHiddenBreakdown: Cart = {
+ ...mockCart,
+ passwordManager: {
+ seats: {
+ quantity: 5,
+ translationKey: "members",
+ cost: 50,
+ hideBreakdown: true,
+ },
+ },
+ };
+ fixture.componentRef.setInput("cart", cartWithHiddenBreakdown);
+ fixture.detectChanges();
+
+ const pmLineItem = fixture.debugElement.query(
+ By.css('[id="password-manager-members"] .tw-flex-1 .tw-text-muted'),
+ );
+
+ // Act / Assert
+ expect(pmLineItem.nativeElement.textContent).toContain("5 Members");
+ });
+
+ it("should show cost breakdown when hideBreakdown is false for password manager seats", () => {
+ // Arrange / Act
+ const pmLineItem = fixture.debugElement.query(
+ By.css('[id="password-manager-members"] .tw-flex-1 .tw-text-muted'),
+ );
+
+ // Assert
+ expect(pmLineItem.nativeElement.textContent).toContain("5 Members x $50.00 / month");
+ });
+
+ it("should hide cost breakdown for additional storage when hideBreakdown is true", () => {
+ // Arrange
+ const cartWithHiddenBreakdown: Cart = {
+ ...mockCart,
+ passwordManager: {
+ ...mockCart.passwordManager,
+ additionalStorage: {
+ quantity: 2,
+ translationKey: "additionalStorageGB",
+ cost: 10,
+ hideBreakdown: true,
+ },
+ },
+ };
+ fixture.componentRef.setInput("cart", cartWithHiddenBreakdown);
+ fixture.detectChanges();
+
+ const storageItem = fixture.debugElement.query(By.css("[id='additional-storage']"));
+ const storageLineItem = storageItem.query(By.css(".tw-flex-1 .tw-text-muted"));
+ const storageTotal = storageItem.query(By.css("[data-testid='additional-storage-total']"));
+
+ // Act / Assert
+ expect(storageLineItem.nativeElement.textContent).toContain("2 Additional storage GB");
+ expect(storageTotal.nativeElement.textContent).toContain("$20.00");
+ });
+
+ it("should hide cost breakdown for secrets manager seats when hideBreakdown is true", () => {
+ // Arrange
+ const cartWithHiddenBreakdown: Cart = {
+ ...mockCart,
+ secretsManager: {
+ seats: {
+ quantity: 3,
+ translationKey: "secretsManagerSeats",
+ cost: 30,
+ hideBreakdown: true,
+ },
+ additionalServiceAccounts: mockCart.secretsManager!.additionalServiceAccounts,
+ },
+ };
+ fixture.componentRef.setInput("cart", cartWithHiddenBreakdown);
+ fixture.detectChanges();
+
+ const smLineItem = fixture.debugElement.query(
+ By.css('[id="secrets-manager-members"] .tw-text-muted'),
+ );
+ const smTotal = fixture.debugElement.query(
+ By.css('[data-testid="secrets-manager-seats-total"]'),
+ );
+
+ // Act / Assert
+ expect(smLineItem.nativeElement.textContent).toContain("3 Secrets Manager seats");
+ expect(smTotal.nativeElement.textContent).toContain("$90.00");
+ });
+
+ it("should hide cost breakdown for additional service accounts when hideBreakdown is true", () => {
+ // Arrange
+ const cartWithHiddenBreakdown: Cart = {
+ ...mockCart,
+ secretsManager: {
+ seats: mockCart.secretsManager!.seats,
+ additionalServiceAccounts: {
+ quantity: 2,
+ translationKey: "additionalServiceAccountsV2",
+ cost: 6,
+ hideBreakdown: true,
+ },
+ },
+ };
+ fixture.componentRef.setInput("cart", cartWithHiddenBreakdown);
+ fixture.detectChanges();
+
+ const saLineItem = fixture.debugElement.query(
+ By.css('[id="additional-service-accounts"] .tw-text-muted'),
+ );
+ const saTotal = fixture.debugElement.query(
+ By.css('[data-testid="additional-service-accounts-total"]'),
+ );
+
+ // Act / Assert
+ expect(saLineItem.nativeElement.textContent).toContain("2 Additional machine accounts");
+ expect(saTotal.nativeElement.textContent).toContain("$12.00");
+ });
+ });
+
describe("Discount Display", () => {
it("should not display discount section when no discount is present", () => {
// Arrange / Act
@@ -336,6 +458,94 @@ describe("CartSummaryComponent", () => {
expect(bottomTotal.nativeElement.textContent).toContain(expectedTotal);
});
});
+
+ describe("Credit Display", () => {
+ it("should not display credit section when no credit is present", () => {
+ // Arrange / Act
+ const creditSection = fixture.debugElement.query(By.css('[data-testid="credit-section"]'));
+
+ // Assert
+ expect(creditSection).toBeFalsy();
+ });
+
+ it("should display credit correctly", () => {
+ // Arrange
+ const cartWithCredit: Cart = {
+ ...mockCart,
+ credit: {
+ translationKey: "accountCredit",
+ value: 25.0,
+ },
+ };
+ fixture.componentRef.setInput("cart", cartWithCredit);
+ fixture.detectChanges();
+
+ const creditSection = fixture.debugElement.query(By.css('[data-testid="credit-section"]'));
+ const creditLabel = creditSection.query(By.css("h3"));
+ const creditAmount = creditSection.query(By.css('[data-testid="credit-amount"]'));
+
+ // Act / Assert
+ expect(creditSection).toBeTruthy();
+ expect(creditLabel.nativeElement.textContent.trim()).toBe("accountCredit");
+ expect(creditAmount.nativeElement.textContent).toContain("-$25.00");
+ });
+
+ it("should apply credit to total calculation", () => {
+ // Arrange
+ const cartWithCredit: Cart = {
+ ...mockCart,
+ credit: {
+ translationKey: "accountCredit",
+ value: 50.0,
+ },
+ };
+ fixture.componentRef.setInput("cart", cartWithCredit);
+ fixture.detectChanges();
+
+ // Subtotal = 372, credit = 50, tax = 9.6
+ // Total = 372 - 50 + 9.6 = 331.6
+ const expectedTotal = "$331.60";
+ const topTotal = fixture.debugElement.query(By.css("h2"));
+ const bottomTotal = fixture.debugElement.query(By.css("[data-testid='final-total']"));
+
+ // Act / Assert
+ expect(topTotal.nativeElement.textContent).toContain(expectedTotal);
+ expect(bottomTotal.nativeElement.textContent).toContain(expectedTotal);
+ });
+
+ it("should display and apply both discount and credit correctly", () => {
+ // Arrange
+ const cartWithBoth: Cart = {
+ ...mockCart,
+ discount: {
+ type: DiscountTypes.PercentOff,
+ value: 10,
+ },
+ credit: {
+ translationKey: "accountCredit",
+ value: 30.0,
+ },
+ };
+ fixture.componentRef.setInput("cart", cartWithBoth);
+ fixture.detectChanges();
+
+ // Subtotal = 372, discount = 37.2 (10%), credit = 30, tax = 9.6
+ // Total = 372 - 37.2 - 30 + 9.6 = 314.4
+ const expectedTotal = "$314.40";
+ const discountSection = fixture.debugElement.query(
+ By.css('[data-testid="discount-section"]'),
+ );
+ const creditSection = fixture.debugElement.query(By.css('[data-testid="credit-section"]'));
+ const topTotal = fixture.debugElement.query(By.css("h2"));
+ const bottomTotal = fixture.debugElement.query(By.css("[data-testid='final-total']"));
+
+ // Act / Assert
+ expect(discountSection).toBeTruthy();
+ expect(creditSection).toBeTruthy();
+ expect(topTotal.nativeElement.textContent).toContain(expectedTotal);
+ expect(bottomTotal.nativeElement.textContent).toContain(expectedTotal);
+ });
+ });
});
describe("CartSummaryComponent - Custom Header Template", () => {
@@ -424,6 +634,8 @@ describe("CartSummaryComponent - Custom Header Template", () => {
return "Collapse purchase details";
case "discount":
return "discount";
+ case "accountCredit":
+ return "accountCredit";
default:
return key;
}
diff --git a/libs/pricing/src/components/cart-summary/cart-summary.component.stories.ts b/libs/pricing/src/components/cart-summary/cart-summary.component.stories.ts
index 581e363ab24..3a2c2cbf5e0 100644
--- a/libs/pricing/src/components/cart-summary/cart-summary.component.stories.ts
+++ b/libs/pricing/src/components/cart-summary/cart-summary.component.stories.ts
@@ -57,6 +57,8 @@ export default {
return "Your next charge is for";
case "dueOn":
return "due on";
+ case "premiumSubscriptionCredit":
+ return "Premium subscription credit";
default:
return key;
}
@@ -341,3 +343,92 @@ export const WithAmountDiscount: Story = {
} satisfies Cart,
},
};
+
+export const WithHiddenBreakdown: Story = {
+ name: "Hidden Cost Breakdown",
+ args: {
+ cart: {
+ passwordManager: {
+ seats: {
+ quantity: 5,
+ translationKey: "members",
+ cost: 50.0,
+ hideBreakdown: true,
+ },
+ additionalStorage: {
+ quantity: 2,
+ translationKey: "additionalStorageGB",
+ cost: 10.0,
+ hideBreakdown: true,
+ },
+ },
+ secretsManager: {
+ seats: {
+ quantity: 3,
+ translationKey: "members",
+ cost: 30.0,
+ hideBreakdown: true,
+ },
+ additionalServiceAccounts: {
+ quantity: 2,
+ translationKey: "additionalServiceAccountsV2",
+ cost: 6.0,
+ hideBreakdown: true,
+ },
+ },
+ cadence: "monthly",
+ estimatedTax: 19.2,
+ } satisfies Cart,
+ },
+};
+
+export const WithCredit: Story = {
+ name: "With Account Credit",
+ args: {
+ cart: {
+ passwordManager: {
+ seats: {
+ quantity: 5,
+ translationKey: "members",
+ cost: 50.0,
+ },
+ },
+ cadence: "monthly",
+ credit: {
+ translationKey: "premiumSubscriptionCredit",
+ value: 25.0,
+ },
+ estimatedTax: 10.0,
+ } satisfies Cart,
+ },
+};
+
+export const WithDiscountAndCredit: Story = {
+ name: "With Both Discount and Credit",
+ args: {
+ cart: {
+ passwordManager: {
+ seats: {
+ quantity: 5,
+ translationKey: "members",
+ cost: 50.0,
+ },
+ additionalStorage: {
+ quantity: 2,
+ translationKey: "additionalStorageGB",
+ cost: 10.0,
+ },
+ },
+ cadence: "annually",
+ discount: {
+ type: DiscountTypes.PercentOff,
+ value: 15,
+ },
+ credit: {
+ translationKey: "premiumSubscriptionCredit",
+ value: 50.0,
+ },
+ estimatedTax: 15.0,
+ } satisfies Cart,
+ },
+};
diff --git a/libs/pricing/src/components/cart-summary/cart-summary.component.ts b/libs/pricing/src/components/cart-summary/cart-summary.component.ts
index ef35f0ded33..cdf66ab0b4d 100644
--- a/libs/pricing/src/components/cart-summary/cart-summary.component.ts
+++ b/libs/pricing/src/components/cart-summary/cart-summary.component.ts
@@ -142,11 +142,22 @@ export class CartSummaryComponent {
return getLabel(this.i18nService, discount);
});
+ /**
+ * Calculates the credit amount from the cart credit
+ */
+ readonly creditAmount = computed(() => {
+ const { credit } = this.cart();
+ if (!credit) {
+ return 0;
+ }
+ return credit.value;
+ });
+
/**
* Calculates the total of all line items including discount and tax
*/
readonly total = computed(
- () => this.subtotal() - this.discountAmount() + this.estimatedTax(),
+ () => this.subtotal() - this.discountAmount() - this.creditAmount() + this.estimatedTax(),
);
/**
@@ -154,6 +165,16 @@ export class CartSummaryComponent {
*/
readonly total$ = toObservable(this.total);
+ /**
+ * Translates a key with optional parameters
+ */
+ translateWithParams(key: string, params?: Array): string {
+ if (!params || params.length === 0) {
+ return this.i18nService.t(key);
+ }
+ return this.i18nService.t(key, ...params);
+ }
+
/**
* Toggles the expanded/collapsed state of the cart items
*/
diff --git a/libs/pricing/src/components/pricing-card/pricing-card.component.html b/libs/pricing/src/components/pricing-card/pricing-card.component.html
index 5c80ebd7e99..39e88252998 100644
--- a/libs/pricing/src/components/pricing-card/pricing-card.component.html
+++ b/libs/pricing/src/components/pricing-card/pricing-card.component.html
@@ -22,22 +22,24 @@
@if (price(); as priceValue) {
+
{{
- priceValue.amount | currency: "$"
+ priceValue.amount
+ | currency: "$" : true : (priceValue.amount % 1 === 0 ? "1.0-0" : "1.2-2")
}}
- / {{ priceValue.cadence }}
+ / {{ priceValue.cadence | i18n }}
@if (priceValue.showPerUser) {
- per user
+ {{ "perUser" | i18n }}
}
}
-
-
- @if (button(); as buttonConfig) {
+
+ @if (button(); as buttonConfig) {
+
- }
-
+
+ }
@@ -67,10 +69,12 @@
@for (feature of featureList; track feature) {
-
-
+ >
+
{{
feature
}}
diff --git a/libs/pricing/src/components/pricing-card/pricing-card.component.mdx b/libs/pricing/src/components/pricing-card/pricing-card.component.mdx
index 905b8e6981f..1cbac94d8ee 100644
--- a/libs/pricing/src/components/pricing-card/pricing-card.component.mdx
+++ b/libs/pricing/src/components/pricing-card/pricing-card.component.mdx
@@ -39,7 +39,7 @@ import { PricingCardComponent } from "@bitwarden/pricing";
| Input | Type | Description |
| ------------- | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tagline` | `string` | **Required.** Descriptive text below title (max 2 lines) |
-| `price` | `{ amount: number; cadence: "monthly" \| "annually"; showPerUser?: boolean }` | **Optional.** Price information. If omitted, no price is shown |
+| `price` | `{ amount: number; cadence: "month" \| "monthly" \| "year" \| "annually"; showPerUser?: boolean }` | **Optional.** Price information. If omitted, no price is shown |
| `button` | `{ type: ButtonType; text: string; disabled?: boolean; icon?: { type: string; position: "before" \| "after" } }` | **Optional.** Button configuration with optional icon. If omitted, no button is shown. Icon uses `bwi-*` classes, position defaults to "after" |
| `features` | `string[]` | **Optional.** List of features with checkmarks |
| `activeBadge` | `{ text: string; variant?: BadgeVariant }` | **Optional.** Active plan badge using proper Badge component, positioned on the same line as title, aligned to the right. If omitted, no badge is shown |
@@ -182,6 +182,58 @@ For coming soon or unavailable plans:
```
+### With Button Icons
+
+Add icons to buttons for enhanced visual communication:
+
+
+
+```html
+
+
+
+
+
+
+
+```
+
+### Active Plan Badge
+
+Show which plan is currently active:
+
+
+
+```html
+
+
+```
+
### Pricing Grid Layout
Multiple cards displayed together:
diff --git a/libs/pricing/src/components/pricing-card/pricing-card.component.spec.ts b/libs/pricing/src/components/pricing-card/pricing-card.component.spec.ts
index 669b54c5b57..fc8a9541952 100644
--- a/libs/pricing/src/components/pricing-card/pricing-card.component.spec.ts
+++ b/libs/pricing/src/components/pricing-card/pricing-card.component.spec.ts
@@ -2,6 +2,7 @@ import { CommonModule } from "@angular/common";
import { ChangeDetectionStrategy, Component } from "@angular/core";
import { ComponentFixture, TestBed } from "@angular/core/testing";
+import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { BadgeVariant, ButtonType, SvgModule, TypographyModule } from "@bitwarden/components";
import { PricingCardComponent } from "@bitwarden/pricing";
@@ -69,6 +70,29 @@ describe("PricingCardComponent", () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [PricingCardComponent, TestHostComponent, SvgModule, TypographyModule, CommonModule],
+ providers: [
+ {
+ provide: I18nService,
+ useValue: {
+ t: (key: string) => {
+ switch (key) {
+ case "month":
+ return "month";
+ case "monthly":
+ return "monthly";
+ case "year":
+ return "year";
+ case "annually":
+ return "annually";
+ case "perUser":
+ return "per user";
+ default:
+ return key;
+ }
+ },
+ },
+ },
+ ],
}).compileComponents();
// For signal inputs, we need to set required inputs through the host component
@@ -151,7 +175,7 @@ describe("PricingCardComponent", () => {
it("should display bwi-check icons for features", () => {
hostFixture.detectChanges();
const compiled = hostFixture.nativeElement;
- const icons = compiled.querySelectorAll("i.bwi-check");
+ const icons = compiled.querySelectorAll("bit-icon[name='bwi-check']");
expect(icons.length).toBe(3); // One for each feature
});
diff --git a/libs/pricing/src/components/pricing-card/pricing-card.component.stories.ts b/libs/pricing/src/components/pricing-card/pricing-card.component.stories.ts
index 832345de357..63946cbf19a 100644
--- a/libs/pricing/src/components/pricing-card/pricing-card.component.stories.ts
+++ b/libs/pricing/src/components/pricing-card/pricing-card.component.stories.ts
@@ -1,15 +1,42 @@
-import { Meta, StoryObj } from "@storybook/angular";
+import { Meta, moduleMetadata, StoryObj } from "@storybook/angular";
-import { TypographyModule } from "@bitwarden/components";
+import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
+import { SvgModule, TypographyModule } from "@bitwarden/components";
+import { I18nPipe } from "@bitwarden/ui-common";
import { PricingCardComponent } from "./pricing-card.component";
export default {
title: "Billing/Pricing Card",
component: PricingCardComponent,
- moduleMetadata: {
- imports: [TypographyModule],
- },
+ decorators: [
+ moduleMetadata({
+ imports: [PricingCardComponent, SvgModule, TypographyModule, I18nPipe],
+ providers: [
+ {
+ provide: I18nService,
+ useValue: {
+ t: (key: string) => {
+ switch (key) {
+ case "month":
+ return "month";
+ case "monthly":
+ return "monthly";
+ case "year":
+ return "year";
+ case "annually":
+ return "annually";
+ case "perUser":
+ return "per user";
+ default:
+ return key;
+ }
+ },
+ },
+ },
+ ],
+ }),
+ ],
args: {
tagline: "Everything you need for secure password management across all your devices",
},
@@ -83,7 +110,7 @@ export const WithoutFeatures: Story = {
}),
args: {
tagline: "Advanced security and management for your organization",
- price: { amount: 3, cadence: "monthly" },
+ price: { amount: 3, cadence: "month" },
button: { text: "Contact Sales", type: "primary" },
},
};
@@ -150,7 +177,7 @@ export const LongTagline: Story = {
args: {
tagline:
"Comprehensive password management solution for teams and organizations that need advanced security features, detailed reporting, and enterprise-grade administration tools that scale with your business",
- price: { amount: 5, cadence: "monthly", showPerUser: true },
+ price: { amount: 5, cadence: "month", showPerUser: true },
button: { text: "Start Business Trial", type: "primary" },
features: [
"Everything in Premium",
@@ -274,7 +301,7 @@ export const WithoutButton: Story = {
}),
args: {
tagline: "This plan will be available soon with exciting new features",
- price: { amount: 15, cadence: "monthly" },
+ price: { amount: 15, cadence: "month" },
features: ["Advanced security features", "Enhanced collaboration tools", "Premium support"],
},
};
diff --git a/libs/pricing/src/components/pricing-card/pricing-card.component.ts b/libs/pricing/src/components/pricing-card/pricing-card.component.ts
index 4b9241fc9dd..23eda0fa99b 100644
--- a/libs/pricing/src/components/pricing-card/pricing-card.component.ts
+++ b/libs/pricing/src/components/pricing-card/pricing-card.component.ts
@@ -4,12 +4,15 @@ import { ChangeDetectionStrategy, Component, input, output } from "@angular/core
import {
BadgeModule,
BadgeVariant,
+ BitwardenIcon,
ButtonModule,
ButtonType,
CardComponent,
+ IconModule,
SvgModule,
TypographyModule,
} from "@bitwarden/components";
+import { I18nPipe } from "@bitwarden/ui-common";
/**
* A reusable UI-only component that displays pricing information in a card format.
@@ -20,20 +23,29 @@ import {
selector: "billing-pricing-card",
templateUrl: "./pricing-card.component.html",
changeDetection: ChangeDetectionStrategy.OnPush,
- imports: [BadgeModule, ButtonModule, SvgModule, TypographyModule, CurrencyPipe, CardComponent],
+ imports: [
+ BadgeModule,
+ ButtonModule,
+ SvgModule,
+ IconModule,
+ TypographyModule,
+ CurrencyPipe,
+ CardComponent,
+ I18nPipe,
+ ],
})
export class PricingCardComponent {
readonly tagline = input.required();
readonly price = input<{
amount: number;
- cadence: "monthly" | "annually";
+ cadence: "month" | "monthly" | "year" | "annually";
showPerUser?: boolean;
}>();
readonly button = input<{
type: ButtonType;
text: string;
disabled?: boolean;
- icon?: { type: string; position: "before" | "after" };
+ icon?: { type: BitwardenIcon; position: "before" | "after" };
}>();
readonly features = input();
readonly activeBadge = input<{ text: string; variant?: BadgeVariant }>();
diff --git a/libs/pricing/src/types/cart.ts b/libs/pricing/src/types/cart.ts
index ed5108edee8..aeec6b269af 100644
--- a/libs/pricing/src/types/cart.ts
+++ b/libs/pricing/src/types/cart.ts
@@ -1,10 +1,14 @@
import { Discount } from "@bitwarden/pricing";
+import { Credit } from "./credit";
+
export type CartItem = {
translationKey: string;
+ translationParams?: Array;
quantity: number;
cost: number;
discount?: Discount;
+ hideBreakdown?: boolean;
};
export type Cart = {
@@ -18,5 +22,6 @@ export type Cart = {
};
cadence: "annually" | "monthly";
discount?: Discount;
+ credit?: Credit;
estimatedTax: number;
};
diff --git a/libs/pricing/src/types/credit.ts b/libs/pricing/src/types/credit.ts
new file mode 100644
index 00000000000..bb7e42bcb62
--- /dev/null
+++ b/libs/pricing/src/types/credit.ts
@@ -0,0 +1,5 @@
+export type Credit = {
+ translationKey: string;
+ translationParams?: Array;
+ value: number;
+};