Subscription Manager
Display and manage subscription status with cancel, pause, resume, and billing cycle controls. Includes confirmation dialogs for dangerous actions.
Preview
Status Variants
Features
- Status-aware card styling with color-coded backgrounds and borders
- Status icons: CheckCircle (active), PauseCircle (paused), XCircle (canceled), AlertCircle (past due), Clock (trial)
- Current plan display with price and renewal date
- Actions: Pause, Resume, Cancel, Change billing cycle, Upgrade
- Confirmation dialogs with action-specific icons for all action types
- Async action handlers with loading states
- Contextual alert messages with status-aware styling
Install Summary
This kit will add the following files and dependencies to your project. Download the bundle and extract it into your project root.
Component Files
| File | Path |
|---|---|
| subscription-manager.tsx | components/billing/subscription-manager/subscription-manager.tsx |
| subscription-card.tsx | components/billing/subscription-manager/subscription-card.tsx |
| subscription-actions.tsx | components/billing/subscription-manager/subscription-actions.tsx |
| confirm-dialog.tsx | components/billing/subscription-manager/confirm-dialog.tsx |
| types.ts | components/billing/subscription-manager/types.ts |
| index.ts | components/billing/subscription-manager/index.ts |
Utility Files
| File | Path |
|---|---|
| format.ts | lib/format.ts |
| design-tokens.ts | lib/design-tokens.ts |
Dependencies
Install these dependencies before using the component:
Terminal
bash
npx shadcn@latest add button dialog badgeTerminal
bash
npm install @phosphor-icons/reactInstallation
Download the complete bundle as a ZIP file, or copy the text bundle to your clipboard:
Component Files
The component consists of the following files:
1. subscription-manager.tsx
subscription-manager.tsx
tsx
"use client";
import { useState } from "react";
import { cn } from "@/lib/utils";
import { SubscriptionCard } from "./subscription-card";2. subscription-card.tsx
subscription-card.tsx
tsx
"use client";
import {
CalendarDots as CalendarDays,
CreditCard,3. subscription-actions.tsx
subscription-actions.tsx
tsx
"use client";
import { Pause, Play, XCircle, ArrowClockwise as RefreshCw, ArrowUpRight } from "@phosphor-icons/react";
import { Button } from "@/components/ui/button";
import type { Subscription, BillingInterval, ConfirmAction } from "./types";4. confirm-dialog.tsx
confirm-dialog.tsx
tsx
"use client";
import { WarningDiamond as AlertTriangle, PauseCircle, PlayCircle, ArrowClockwise as RefreshCw } from "@phosphor-icons/react";
import {
Dialog,5. types.ts
types.ts
tsx
export type SubscriptionStatus = "active" | "paused" | "canceled" | "past_due" | "trialing";
export type BillingInterval = "monthly" | "yearly";
export interface SubscriptionPlan {
id: string;6. index.ts
index.ts
tsx
export { SubscriptionManager } from "./subscription-manager";
export { SubscriptionCard } from "./subscription-card";
export { SubscriptionActions } from "./subscription-actions";
export { ConfirmDialog } from "./confirm-dialog";
export type {
SubscriptionStatus,
BillingInterval,
SubscriptionPlan,
Subscription,
SubscriptionManagerProps,
ConfirmAction,
} from "./types";
Shared Utilities
This component uses shared utility functions. These are included in the bundle above:
format.ts
format.ts
tsx
export function formatDate(date: Date): string {
return date.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
});
}
export function formatRelativeTime(date: Date, now: Date = new Date()): string {
const diff = now.getTime() - date.getTime();
const minutes = Math.floor(diff / 60000);
if (minutes < 1) return "Just now";
if (minutes < 60) return `${minutes}m ago`;
const hours = Math.floor(minutes / 60);
if (hours < 24) return `${hours}h ago`;
const days = Math.floor(hours / 24);
if (days < 7) return `${days}d ago`;
return formatDate(date);
}
export function formatPrice(amount: number, currency = "USD"): string {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency,
minimumFractionDigits: 0,
maximumFractionDigits: 2,
}).format(amount);
}
export function formatNumber(num: number): string {
if (num >= 1_000_000) return `${(num / 1_000_000).toFixed(1)}M`;
if (num >= 1_000) return `${(num / 1_000).toFixed(1)}K`;
return num.toString();
}
design-tokens.ts
design-tokens.ts
tsx
// Border radius
export const radius = {
card: "rounded-xl",
badge: "rounded-full",
button: "rounded-lg",Usage
app/billing/page.tsx
tsx
"use client";
import { useState } from "react";
import { SubscriptionManager, type Subscription } from "@/components/billing";
Props
| Prop | Type | Default |
|---|---|---|
| subscription | Subscription | Required |
| onCancel | () => void | Promise | - |
| onPause | () => void | Promise | - |
| onResume | () => void | Promise | - |
| onChangeBillingCycle | (interval) => void | Promise | - |
| onUpgrade | () => void | - |
| isLoading | boolean | false |
| className | string | - |