import type { Metadata } from "next"; import Link from "next/link"; import { ExternalLink } from "lucide-react"; import { db, users, auditLogs, count, eq, sql, like, desc } from "@epicure/db"; import { getAllSiteSettings, getSiteSetting } from "@/lib/site-settings"; import { isStripeTestMode } from "@/lib/stripe"; import { AdminSettingsForm } from "@/components/admin/admin-settings-form"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { requireFullAdminPage } from "@/lib/require-admin-page"; export const metadata: Metadata = {}; const STRIPE_GROUP = { title: "Stripe Connection", description: "Get keys from your Stripe Dashboard (Developers -> API keys). The webhook secret comes from " + "the endpoint you configure there pointed at /api/webhooks/stripe (events: checkout.session.completed, " + "customer.subscription.updated, customer.subscription.deleted, invoice.payment_failed, invoice.paid). " + "Price/Product IDs per tier are set on the Tier Limits page, not here.", keys: ["STRIPE_SECRET_KEY", "STRIPE_PUBLISHABLE_KEY", "STRIPE_WEBHOOK_SECRET"] as const, }; export default async function AdminBillingPage() { await requireFullAdminPage(); const [settings, secretKey] = await Promise.all([ getAllSiteSettings(), getSiteSetting("STRIPE_SECRET_KEY"), ]); const [subscriberCounts, pastDueUsers, recentBillingEvents] = await Promise.all([ db.select({ tier: users.tier, count: count() }).from(users).where(sql`${users.tier} != 'free'`).groupBy(users.tier), db .select({ id: users.id, name: users.name, email: users.email }) .from(users) .where(eq(users.subscriptionStatus, "past_due")) .limit(20), db .select({ id: auditLogs.id, action: auditLogs.action, targetId: auditLogs.targetId, metadata: auditLogs.metadata, createdAt: auditLogs.createdAt }) .from(auditLogs) .where(like(auditLogs.action, "billing.%")) .orderBy(desc(auditLogs.createdAt)) .limit(20), ]); const proCount = subscriberCounts.find((r) => r.tier === "pro")?.count ?? 0; const familyCount = subscriberCounts.find((r) => r.tier === "family")?.count ?? 0; return (

Billing

Stripe connection status, subscriber insights, and recent billing events. Price mapping per tier lives on the Tier Limits page.

{secretKey ? ( {isStripeTestMode(secretKey) ? "Test mode" : "Live mode"} ) : ( Not configured )} Stripe Dashboard
Pro subscribers

{proCount}

Family subscribers

{familyCount}

Past due

{pastDueUsers.length}

{pastDueUsers.length > 0 && (

Payments needing attention

    {pastDueUsers.map((u) => (
  • {u.name} ({u.email}) View
  • ))}
)}

Recent billing events

{recentBillingEvents.length === 0 ? (

Nothing yet.

) : (
    {recentBillingEvents.map((e) => (
  • {e.action} {e.targetId && ( user )} {e.createdAt.toLocaleString()}
  • ))}
)}
); }