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

FilePath
subscription-manager.tsxcomponents/billing/subscription-manager/subscription-manager.tsx
subscription-card.tsxcomponents/billing/subscription-manager/subscription-card.tsx
subscription-actions.tsxcomponents/billing/subscription-manager/subscription-actions.tsx
confirm-dialog.tsxcomponents/billing/subscription-manager/confirm-dialog.tsx
types.tscomponents/billing/subscription-manager/types.ts
index.tscomponents/billing/subscription-manager/index.ts

Utility Files

FilePath
format.tslib/format.ts
design-tokens.tslib/design-tokens.ts

Dependencies

Install these dependencies before using the component:

Terminal
bash
npx shadcn@latest add button dialog badge
Terminal
bash
npm install @phosphor-icons/react

Installation

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

PropTypeDefault
subscriptionSubscriptionRequired
onCancel() => void | Promise-
onPause() => void | Promise-
onResume() => void | Promise-
onChangeBillingCycle(interval) => void | Promise-
onUpgrade() => void-
isLoadingbooleanfalse
classNamestring-