1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-19 02:44:01 +00:00
Files
browser/libs/subscription/src/components/subscription-card/subscription-card.component.mdx
cyprain-okeke f46511b3e8 [PM-30908]Correct Premium subscription status handling (#18475)
* Implement the required changes

* Fix the family plan creation for expired sub

* Resolve the pr comments

* resolve the resubscribe issue

* Removed redirectOnCompletion: true from the resubscribe

* Display the Change payment method dialog on the subscription page

* adjust the page reload time

* revert payment method open in subscription page

* Enable cancel premium see the subscription page

* Revert the removal of hasPremiumPersonally

* remove extra space

* Add can view subscription

* Use the canViewSubscription

* Resolve the tab default to premium

* use the subscription Instead of hasPremium

* Revert the changes on user-subscription

* Use the flag to redirect to subscription page

* revert the canViewSubscription change

* resolve the route issue with premium

* Change the path to

* Revert the previous iteration changes

* Fix the build error
2026-02-13 18:56:35 +01:00

461 lines
13 KiB
Plaintext

import { Meta, Story, Canvas } from "@storybook/addon-docs/blocks";
import * as SubscriptionCardStories from "./subscription-card.component.stories";
<Meta of={SubscriptionCardStories} />
# Subscription Card
A comprehensive UI component for displaying subscription status, payment details, and contextual
action prompts based on subscription state. Dynamically adapts its presentation based on the
subscription status (active, trialing, incomplete, past due, canceled, unpaid, etc.).
<Canvas of={SubscriptionCardStories.Active} />
## Table of Contents
- [Usage](#usage)
- [API](#api)
- [Inputs](#inputs)
- [Outputs](#outputs)
- [Data Structure](#data-structure)
- [Subscription States](#subscription-states)
- [Design](#design)
- [Examples](#examples)
- [Active](#active)
- [Active With Upgrade](#active-with-upgrade)
- [Trial](#trial)
- [Trial With Upgrade](#trial-with-upgrade)
- [Incomplete Payment](#incomplete-payment)
- [Incomplete Expired](#incomplete-expired)
- [Past Due](#past-due)
- [Pending Cancellation](#pending-cancellation)
- [Unpaid](#unpaid)
- [Canceled](#canceled)
- [Enterprise](#enterprise)
- [Features](#features)
- [Do's and Don'ts](#dos-and-donts)
- [Accessibility](#accessibility)
## Usage
The subscription card component is designed to display comprehensive subscription information on
billing pages, account management interfaces, and subscription dashboards.
```ts
import { SubscriptionCardComponent, BitwardenSubscription } from "@bitwarden/subscription";
```
```html
<billing-subscription-card
[title]="'Premium Subscription'"
[subscription]="subscription"
[showUpgradeButton]="false"
(callToActionClicked)="handleAction($event)"
>
</billing-subscription-card>
```
## API
### Inputs
| Input | Type | Description |
| ------------------- | ----------------------- | ----------------------------------------------------------------------- |
| `title` | `string` | **Required.** The title displayed at the top of the card |
| `subscription` | `BitwardenSubscription` | **Required.** The subscription data including status, cart, and storage |
| `showUpgradeButton` | `boolean` | **Optional.** Whether to show the upgrade callout (default: `false`) |
### Outputs
| Output | Type | Description |
| --------------------- | ------------------------ | ---------------------------------------------------------- |
| `callToActionClicked` | `SubscriptionCardAction` | Emitted when a user clicks an action button in the callout |
**SubscriptionCardAction Type:**
```typescript
type SubscriptionCardAction =
| "contact-support"
| "manage-invoices"
| "reinstate-subscription"
| "resubscribe"
| "update-payment"
| "upgrade-plan";
```
## Data Structure
The component uses the `BitwardenSubscription` type, which is a discriminated union based on status:
```typescript
type BitwardenSubscription = HasCart & HasStorage & (Suspension | Billable | Canceled);
type HasCart = {
cart: Cart; // From @bitwarden/pricing
};
type HasStorage = {
storage: {
available: number;
readableUsed: string;
used: number;
};
};
type Suspension = {
status: "incomplete" | "incomplete_expired" | "past_due" | "unpaid";
suspension: Date;
gracePeriod: number;
};
type Billable = {
status: "trialing" | "active";
nextCharge: Date;
cancelAt?: Date;
};
type Canceled = {
status: "canceled";
canceled: Date;
};
```
## Subscription States
The component dynamically adapts its appearance and calls-to-action based on the subscription
status:
- **active**: Subscription is active and paid up
- **trialing**: Subscription is in trial period
- **incomplete**: Payment failed, requires action
- **incomplete_expired**: Payment issue expired, subscription suspended
- **past_due**: Payment overdue but within grace period
- **unpaid**: Subscription suspended due to non-payment
- **canceled**: Subscription was canceled
Each state displays an appropriate badge, callout message, and relevant action buttons.
## Design
The component follows the Bitwarden design system with:
- **Status Badge**: Color-coded badges (success, warning, danger) indicating subscription state
- **Cart Summary**: Integrated cart summary showing pricing details
- **Contextual Callouts**: Warning/info/danger callouts with appropriate actions
- **Modern Angular**: Uses signal inputs (`input.required`, `input`) and `computed` signals
- **OnPush Change Detection**: Optimized performance with change detection strategy
- **Typography**: Consistent text styling using the typography module
- **Tailwind CSS**: Uses `tw-` prefixed utility classes for styling
- **Responsive Layout**: Flexbox-based layout that adapts to container size
## Examples
### Active
Standard active subscription with regular billing:
<Canvas of={SubscriptionCardStories.Active} />
```html
<billing-subscription-card
[title]="'Premium Subscription'"
[subscription]="{
status: 'active',
nextCharge: new Date('2025-02-15'),
cart: {
passwordManager: {
seats: {
quantity: 1,
name: 'members',
cost: 10.00
}
},
cadence: 'annually',
estimatedTax: 2.71
},
storage: {
available: 1000,
used: 234,
readableUsed: '234 MB'
}
}"
(callToActionClicked)="handleAction($event)"
>
</billing-subscription-card>
```
### Active With Upgrade
Active subscription with upgrade promotion callout:
<Canvas of={SubscriptionCardStories.ActiveWithUpgrade} />
```html
<billing-subscription-card
[title]="'Premium Subscription'"
[subscription]="activeSubscription"
[showUpgradeButton]="true"
(callToActionClicked)="handleAction($event)"
>
</billing-subscription-card>
```
### Trial
Subscription in trial period showing next charge date:
<Canvas of={SubscriptionCardStories.Trial} />
```html
<billing-subscription-card
[title]="'Premium Subscription'"
[subscription]="{
status: 'trialing',
nextCharge: new Date('2025-02-01'),
cart: {...},
storage: {...}
}"
(callToActionClicked)="handleAction($event)"
>
</billing-subscription-card>
```
### Trial With Upgrade
Trial subscription with upgrade option displayed:
<Canvas of={SubscriptionCardStories.TrialWithUpgrade} />
```html
<billing-subscription-card
[title]="'Premium Subscription'"
[subscription]="trialSubscription"
[showUpgradeButton]="true"
(callToActionClicked)="handleAction($event)"
>
</billing-subscription-card>
```
### Incomplete Payment
Payment failed, showing warning with update payment action:
<Canvas of={SubscriptionCardStories.Incomplete} />
```html
<billing-subscription-card
[title]="'Premium Subscription'"
[subscription]="{
status: 'incomplete',
suspension: new Date('2025-02-15'),
gracePeriod: 7,
cart: {...},
storage: {...}
}"
(callToActionClicked)="handleAction($event)"
>
</billing-subscription-card>
```
**Actions available:** Update Payment, Contact Support
### Incomplete Expired
Payment issue expired, subscription has been suspended:
<Canvas of={SubscriptionCardStories.IncompleteExpired} />
```html
<billing-subscription-card
[title]="'Premium Subscription'"
[subscription]="{
status: 'incomplete_expired',
suspension: new Date('2025-01-01'),
gracePeriod: 0,
cart: {...},
storage: {...}
}"
(callToActionClicked)="handleAction($event)"
>
</billing-subscription-card>
```
**Actions available:** Resubscribe
### Past Due
Payment past due with active grace period:
<Canvas of={SubscriptionCardStories.PastDue} />
```html
<billing-subscription-card
[title]="'Premium Subscription'"
[subscription]="{
status: 'past_due',
suspension: new Date('2025-02-05'),
gracePeriod: 14,
cart: {...},
storage: {...}
}"
(callToActionClicked)="handleAction($event)"
>
</billing-subscription-card>
```
**Actions available:** Manage Invoices
### Pending Cancellation
Active subscription scheduled to be canceled:
<Canvas of={SubscriptionCardStories.PendingCancellation} />
```html
<billing-subscription-card
[title]="'Premium Subscription'"
[subscription]="{
status: 'active',
nextCharge: new Date('2025-02-15'),
cancelAt: new Date('2025-03-01'),
cart: {...},
storage: {...}
}"
(callToActionClicked)="handleAction($event)"
>
</billing-subscription-card>
```
**Actions available:** Reinstate Subscription
### Unpaid
Subscription suspended due to unpaid invoices:
<Canvas of={SubscriptionCardStories.Unpaid} />
```html
<billing-subscription-card
[title]="'Premium Subscription'"
[subscription]="{
status: 'unpaid',
suspension: new Date('2025-01-20'),
gracePeriod: 0,
cart: {...},
storage: {...}
}"
(callToActionClicked)="handleAction($event)"
>
</billing-subscription-card>
```
**Actions available:** Manage Invoices
### Canceled
Subscription that has been canceled:
<Canvas of={SubscriptionCardStories.Canceled} />
```html
<billing-subscription-card
[title]="'Premium Subscription'"
[subscription]="{
status: 'canceled',
canceled: new Date('2025-01-15'),
cart: {...},
storage: {...}
}"
(callToActionClicked)="handleAction($event)"
>
</billing-subscription-card>
```
**Actions available:** Resubscribe
### Enterprise
Enterprise subscription with multiple products and discount:
<Canvas of={SubscriptionCardStories.Enterprise} />
```html
<billing-subscription-card
[title]="'Enterprise Subscription'"
[subscription]="{
status: 'active',
nextCharge: new Date('2025-03-01'),
cart: {
passwordManager: {
seats: { quantity: 5, name: 'members', cost: 7 },
additionalStorage: { quantity: 2, name: 'additionalStorageGB', cost: 0.5 }
},
secretsManager: {
seats: { quantity: 3, name: 'members', cost: 13 },
additionalServiceAccounts: { quantity: 5, name: 'additionalServiceAccountsV2', cost: 1 }
},
discount: { type: 'percent-off', active: true, value: 0.25 },
cadence: 'monthly',
estimatedTax: 6.4
},
storage: {
available: 7,
readableUsed: '7 GB',
used: 0
}
}"
(callToActionClicked)="handleAction($event)"
>
</billing-subscription-card>
```
## Features
- **Dynamic Badge**: Status badge changes color and text based on subscription state
- **Contextual Callouts**: Warning, info, or danger callouts with relevant messages
- **Action Buttons**: Context-specific call-to-action buttons (update payment, contact support,
etc.)
- **Cart Summary Integration**: Embedded cart summary with pricing breakdown
- **Custom Header Support**: Cart summary can display custom headers based on subscription status
- **Date Formatting**: Consistent date formatting throughout (MMM. d, y format)
- **Computed Signals**: Efficient reactive computations using Angular signals
- **Type Safety**: Discriminated union types ensure type-safe subscription data
- **Internationalization**: All text uses i18n service for translation support
- **Event Emission**: Emits typed events for handling user actions
## Do's and Don'ts
### ✅ Do
- Handle all `callToActionClicked` events appropriately in parent components
- Provide complete `BitwardenSubscription` objects with all required fields
- Use the correct subscription status from the defined status types
- Include accurate date information for nextCharge, suspension, and cancelAt fields
- Set `showUpgradeButton` to `true` only when upgrade paths are available
- Use real translation keys that exist in the i18n messages file
- Provide accurate storage information with readable format strings
### ❌ Don't
- Omit required fields from the BitwardenSubscription type
- Use custom status strings not defined in the type
- Display upgrade buttons for users who cannot upgrade
- Ignore the `callToActionClicked` events - they require handling
- Mix subscription states (e.g., having both `canceled` date and `nextCharge`)
- Provide incorrect dates that don't match the subscription status
- Override component styles without ensuring accessibility
- Use placeholder or mock data in production environments
## Accessibility
The component includes:
- **Semantic HTML**: Proper heading hierarchy with `<h2>`, `<h3>`, `<h4>` tags
- **ARIA Labels**: Badge variants use appropriate semantic colors
- **Keyboard Navigation**: All action buttons are keyboard accessible
- **Focus Management**: Clear focus indicators on interactive elements
- **Color Contrast**: Sufficient contrast ratios for all text and badge variants
- **Screen Reader Support**: Descriptive text for all interactive elements
- **Button Types**: Proper `type="button"` attributes on all buttons
- **Date Formatting**: Human-readable date formats for assistive technologies