import { redirect } from "next/navigation" import { and, eq, sql } from "drizzle-orm" import { db } from "@/lib/db" import { profiles, properties, tenants } from "@/lib/db/schema" import { getSessionUser } from "@/lib/session" import { CheckoutButton } from "@/components/forms/checkout-button" import { PortalButton } from "@/components/forms/portal-button" import { PaypalCancelButton } from "@/components/forms/paypal-cancel-button" import { getPlanLabel, PLAN_LIMITS, annualEnabled } from "@/lib/stripe/plans" import { paypalConfigured } from "@/lib/paypal/client" import { Check } from "lucide-react" import type { Plan } from "@/types" export const metadata = { title: "Billing" } const PLANS = [ { key: "starter" as Plan, name: "Starter", price: "$0", interval: "forever", description: "For landlords just getting started", features: ["1 property", "3 tenants", "Maintenance tracking", "Rent tracker"], cta: "Current plan", highlight: false, }, { key: "pro" as Plan, name: "Pro", price: "$29", interval: "/month", description: "For active landlords growing their portfolio", features: ["10 properties", "Unlimited tenants", "50 AI calls/month", "5GB storage", "Email notifications", "Stripe rent collection"], cta: "Upgrade to Pro", highlight: true, }, { key: "landlord" as Plan, name: "Landlord", price: "$59", interval: "/month", description: "For property managers at scale", features: ["Unlimited properties", "Unlimited tenants", "200 AI calls/month", "25GB storage", "Team access", "White-label"], cta: "Upgrade to Landlord", highlight: false, }, { key: "lifetime" as Plan, name: "Lifetime", price: "$199", interval: "one-time", description: "Everything in Landlord, forever", features: ["Everything in Landlord", "Lifetime updates", "Priority support", "Flippa-ready asset"], cta: "Get Lifetime Deal", highlight: false, }, ] export default async function BillingPage({ searchParams, }: { searchParams: Promise<{ success?: string; canceled?: string; error?: string }> }) { const user = await getSessionUser() if (!user) redirect("/login") const profile = await db.query.profiles.findFirst({ where: eq(profiles.id, user.id), columns: { plan: true, subscription_status: true, plan_expires_at: true, stripe_customer_id: true, stripe_subscription_id: true, paypal_subscription_id: true, billing_provider: true, }, }) const params = await searchParams const currentPlan = (profile?.plan ?? "starter") as Plan const hasStripeAccount = !!profile?.stripe_customer_id const isPaypal = profile?.billing_provider === "paypal" || !!profile?.paypal_subscription_id const paypalEnabled = paypalConfigured() const limits = PLAN_LIMITS[currentPlan] const canBillAnnually = annualEnabled() const [[{ count: propertiesUsed }], [{ count: tenantsUsed }]] = await Promise.all([ db .select({ count: sql`count(*)::int` }) .from(properties) .where(eq(properties.user_id, user.id)), db .select({ count: sql`count(*)::int` }) .from(tenants) .where(and(eq(tenants.user_id, user.id), eq(tenants.status, "active"))), ]) return (

Billing

Manage your subscription and plan

{params.success && (
Payment successful! Your plan has been upgraded.
)} {params.canceled && (
Checkout canceled — no charge was made.
)} {params.error === "paypal" && (
We couldn't complete your PayPal payment. No charge was made — please try again.
)} {/* Current plan */}

Current Plan

{getPlanLabel(currentPlan)}

{profile?.subscription_status && (

Status: {profile.subscription_status}

)}
{currentPlan !== "starter" && currentPlan !== "lifetime" && (isPaypal ? ( ) : hasStripeAccount ? ( ) : null)}
{/* Plans grid */}
{PLANS.map((plan) => { const isCurrent = currentPlan === plan.key const isDowngrade = ( currentPlan === "landlord" && (plan.key === "pro" || plan.key === "starter") || currentPlan === "lifetime" ) return (
{plan.highlight && (
Most Popular
)}

{plan.name}

{plan.price} {plan.interval}

{plan.description}

    {plan.features.map((f) => (
  • {f}
  • ))}
{isCurrent ? (
Current Plan
) : plan.key === "starter" || isDowngrade ? (
{plan.key === "starter" ? "Free" : "Downgrade via portal"}
) : ( )}
) })}
{/* Usage overview */}

Usage

{[ { label: "Properties", used: propertiesUsed ?? 0, max: limits.maxProperties, }, { label: "Active Tenants", used: tenantsUsed ?? 0, max: limits.maxTenants, }, ].map(({ label, used, max }) => { const unlimited = max === Infinity const pct = unlimited ? 0 : Math.min(100, Math.round((used / max) * 100)) const nearLimit = !unlimited && pct >= 80 return (
{label} {used} / {unlimited ? "Unlimited" : max}
{!unlimited && (
= 100 ? "bg-red-500" : pct >= 80 ? "bg-amber-500" : "bg-indigo-500" }`} style={{ width: `${pct}%` }} />
)}
) })}

AI Calls / mo

{limits.maxAiCalls === 0 ? "Not included" : limits.maxAiCalls}

Storage

{limits.maxStorageMB >= 1024 ? `${limits.maxStorageMB / 1024}GB` : `${limits.maxStorageMB}MB`}

) }