'use client';
import { useEffect, useState } from 'react';
import AdminLayout from '../../components/AdminLayout';
import { getSubscriptions } from '../../lib/api';
export default function SubscriptionsPage() {
const [subscriptions, setSubscriptions] = useState([]);
const [filter, setFilter] = useState('all');
const [loading, setLoading] = useState(true);
useEffect(() => {
loadSubscriptions();
}, []);
const loadSubscriptions = async () => {
try {
const data = await getSubscriptions();
setSubscriptions(data.subscriptions || []);
} catch (err) {
console.error('Failed to load subscriptions:', err);
} finally {
setLoading(false);
}
};
const filteredSubscriptions = subscriptions.filter((sub) => {
if (filter === 'all') return true;
if (filter === 'active') return sub.status === 'active';
if (filter === 'premium') return sub.plan === 'premium' && sub.status === 'active';
if (filter === 'canceled') return sub.status === 'canceled';
return true;
});
const stats = {
total: subscriptions.length,
active: subscriptions.filter(s => s.status === 'active').length,
premium: subscriptions.filter(s => s.plan === 'premium' && s.status === 'active').length,
free: subscriptions.filter(s => s.plan === 'free').length,
canceled: subscriptions.filter(s => s.status === 'canceled').length,
};
const mrr = stats.premium * 9.99;
return (
Subscriptions
{['all', 'active', 'premium', 'canceled'].map((f) => (
))}
{loading ? (
Loading...
) : (
User
Plan
Status
Created
Expires
Stripe ID
{filteredSubscriptions.length === 0 ? (
No subscriptions found
) : (
filteredSubscriptions.map((sub) => (
{sub.user?.email || '—'}
{sub.created_at ? new Date(sub.created_at).toLocaleDateString() : '—'}
{sub.expires_at ? new Date(sub.expires_at).toLocaleDateString() : '—'}
{sub.stripe_subscription_id || '—'}
))
)}
)}
);
}
function StatCard({ label, value, color }) {
return (
);
}
function PlanBadge({ plan }) {
const isPremium = plan === 'premium';
return (
{plan || 'free'}
);
}
function StatusBadge({ status }) {
const colors = {
active: { bg: '#D1FAE5', color: '#065F46' },
canceled: { bg: '#FEE2E2', color: '#991B1B' },
expired: { bg: '#FEE2E2', color: '#991B1B' },
trialing: { bg: '#DBEAFE', color: '#1E40AF' },
};
const style = colors[status] || colors.active;
return (
{status || 'unknown'}
);
}
const styles = {
header: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '24px',
},
title: {
fontSize: '24px',
fontWeight: '600',
},
filters: {
display: 'flex',
gap: '8px',
},
filterBtn: {
padding: '8px 16px',
fontSize: '13px',
fontWeight: '500',
border: '1px solid var(--border)',
borderRadius: '6px',
background: 'white',
color: 'var(--text-secondary)',
cursor: 'pointer',
},
filterBtnActive: {
background: 'var(--primary)',
color: 'white',
borderColor: 'var(--primary)',
},
statsGrid: {
display: 'grid',
gridTemplateColumns: 'repeat(4, 1fr)',
gap: '16px',
marginBottom: '24px',
},
statCard: {
background: 'white',
borderRadius: '12px',
padding: '20px',
textAlign: 'center',
},
statLabel: {
fontSize: '13px',
color: 'var(--text-muted)',
marginBottom: '4px',
},
statValue: {
fontSize: '28px',
fontWeight: '700',
},
loading: {
color: 'var(--text-muted)',
},
table: {
background: 'white',
borderRadius: '12px',
overflow: 'hidden',
},
tableHeader: {
display: 'grid',
gridTemplateColumns: '1.5fr 0.8fr 0.8fr 1fr 1fr 1.5fr',
gap: '16px',
padding: '16px 20px',
background: 'var(--surface)',
fontSize: '12px',
fontWeight: '600',
color: 'var(--text-muted)',
textTransform: 'uppercase',
},
tableRow: {
display: 'grid',
gridTemplateColumns: '1.5fr 0.8fr 0.8fr 1fr 1fr 1.5fr',
gap: '16px',
padding: '16px 20px',
borderBottom: '1px solid var(--border)',
alignItems: 'center',
fontSize: '14px',
},
email: {
color: 'var(--text-secondary)',
},
date: {
color: 'var(--text-muted)',
fontSize: '13px',
},
stripeId: {
fontFamily: 'monospace',
fontSize: '11px',
color: 'var(--text-muted)',
},
empty: {
padding: '48px',
textAlign: 'center',
color: 'var(--text-muted)',
},
};