Finance
Subscriptions Card - Recurring Billing
Monitor subscription management and recurring billing. Track subscription revenue and customer plans.
Finance Subscriptions Card
The Finance Subscriptions Card tracks recurring subscription expenses, helping users manage and optimize their subscription spending.
Preview
Installation
npx shadcn@latest add https://vectormotion.vercel.app/registry/finance-subscriptions-card.jsonFinance Subscriptions Card
'use client'import React from 'react';import { CalendarClock, AlertCircle, CheckCircle2 } from 'lucide-react';import { motion } from 'motion/react';import { clsx, type ClassValue } from "clsx"import { twMerge } from "tailwind-merge"import { ResponsiveContainer, PieChart, Pie, Cell, Tooltip } from 'recharts';function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs))}interface Subscription { id: number; name: string; cost: string; due: string; status: string;}interface SubscriptionChartData { name: string; value: number; color: string; [key: string]: any;}interface SubscriptionsCardProps { isInteractive?: boolean; className?: string; title?: string; totalCost?: string; periodLabel?: string; subscriptions?: Subscription[]; chartData?: SubscriptionChartData[]; totalFixedLabel?: string; totalFixedValue?: string;}const DEFAULT_TITLE = "Recurring";const DEFAULT_TOTAL_COST = "$128";const DEFAULT_PERIOD_LABEL = "/mo";const DEFAULT_SUBSCRIPTIONS: Subscription[] = [ { id: 1, name: 'Netflix', cost: '$16', due: 'Tomorrow', status: 'warning' }, { id: 2, name: 'Adobe', cost: '$55', due: '3 days', status: 'normal' }, { id: 3, name: 'Spotify', cost: '$12', due: 'Paid', status: 'paid' },];const DEFAULT_CHART_DATA: SubscriptionChartData[] = [ { name: 'Soft.', value: 55, color: '#3b82f6' }, // Blue { name: 'Ent.', value: 28, color: '#10b981' }, // Emerald { name: 'Fit.', value: 45, color: '#f59e0b' }, // Amber];const DEFAULT_TOTAL_FIXED_LABEL = "Total Fixed";const DEFAULT_TOTAL_FIXED_VALUE = "$127.97";export const SubscriptionsCard: React.FC<SubscriptionsCardProps> = ({ isInteractive = true, className = "", title = DEFAULT_TITLE, totalCost = DEFAULT_TOTAL_COST, periodLabel = DEFAULT_PERIOD_LABEL, subscriptions = DEFAULT_SUBSCRIPTIONS, chartData = DEFAULT_CHART_DATA, totalFixedLabel = DEFAULT_TOTAL_FIXED_LABEL, totalFixedValue = DEFAULT_TOTAL_FIXED_VALUE,}) => { const index = 47; return ( <motion.div layoutId={isInteractive ? `card-${index}-${title}` : undefined} transition={{ duration: 0.4, ease: "easeOut" }} className={cn( "relative overflow-hidden rounded-xl border border-border bg-card text-card-foreground p-5 shadow-sm transition-all flex flex-col h-full group", isInteractive ? "cursor-pointer hover:border-primary/50 hover:shadow-md" : "", className )} > <div className="mb-2 flex items-start justify-between relative z-10"> <div> <h3 className="font-semibold text-lg text-foreground"> {title} </h3> <div className="flex items-center gap-2 mt-1"> <span className="text-2xl font-bold text-foreground">{totalCost}</span> <span className="text-xs text-muted-foreground">{periodLabel}</span> </div> </div> <div className="h-12 w-12"> <ResponsiveContainer width="100%" height="100%"> <PieChart> <Pie data={chartData} dataKey="value" innerRadius={14} outerRadius={22} stroke="none"> {chartData.map((entry, index) => ( <Cell key={`cell-${index}`} fill={entry.color} /> ))} </Pie> </PieChart> </ResponsiveContainer> </div> </div> <div className="relative z-10 flex-1"> <div className="space-y-2 mt-1"> {subscriptions.map((sub) => ( <div key={sub.id} className="flex items-center justify-between p-1.5 rounded-lg hover:bg-zinc-50 dark:hover:bg-zinc-800/50 transition-colors"> <div className="flex items-center gap-2"> <div className={`w-1.5 h-1.5 rounded-full ${sub.status === 'warning' ? 'bg-amber-500' : 'bg-zinc-300 dark:bg-zinc-600'}`} /> <div> <p className="text-sm font-medium text-foreground">{sub.name}</p> <p className="text-[10px] text-muted-foreground">{sub.due}</p> </div> </div> <span className="text-sm font-semibold">{sub.cost}</span> </div> ))} </div> </div> <div className="z-10 mt-2 flex items-center justify-between text-xs pt-2 border-t border-border"> <span className="text-muted-foreground">{totalFixedLabel}</span> <span className="font-bold text-foreground">{totalFixedValue}</span> </div> </motion.div> );};Props
Prop
Type
Usage
This component is a demo card displaying subscription data with animated visualizations and dark mode support.