import { useEffect, useState } from "react"; import { toast } from "sonner"; import { ThemeToggle } from "../components/ThemeToggle"; import { Button } from "../components/ui/button"; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "../components/ui/card"; import { authClient } from "../lib/auth-client"; import { type PlanConfig, type PlanId, getPlanName, isPaidPlan, plans } from "../lib/plans"; interface PricingPageProps { navigate: ( route: | "/" | "/login" | "/signup" | "/pricing" | "/dashboard" | "/forgot-password" | "/reset-password", ) => void; } export default function PricingPage({ navigate }: PricingPageProps) { const [isLoggedIn, setIsLoggedIn] = useState(false); const [currentPlan, setCurrentPlan] = useState(null); const [isCancelling, setIsCancelling] = useState(false); const [periodEnd, setPeriodEnd] = useState(null); const [isLoading, setIsLoading] = useState(true); const [upgradeLoading, setUpgradeLoading] = useState(null); const [error, setError] = useState(null); const [stripeTestMode, setStripeTestMode] = useState(true); useEffect(() => { // Fetch config to check if Stripe is in test mode fetch("/api/config") .then((res) => res.json()) .then((data) => setStripeTestMode(data.stripeTestMode ?? true)) .catch(() => setStripeTestMode(true)); // Default to showing test banner on error }, []); useEffect(() => { const checkSession = async () => { try { const result = await authClient.getSession(); if (result.data?.user) { setIsLoggedIn(true); // Check subscription status from Better Auth try { const subscription = await authClient.subscription.list(); const activeSub = subscription?.data?.find( (s: { status: string }) => s.status === "active" || s.status === "trialing", ); if (activeSub?.plan) { setCurrentPlan(activeSub.plan as PlanId); // Better Auth uses cancelAt (date) to indicate pending cancellation if (activeSub.cancelAt) { setIsCancelling(true); setPeriodEnd(String(activeSub.periodEnd || activeSub.cancelAt)); } } else { setCurrentPlan("free"); } // Also check Stripe for real-time status const stripeRes = await fetch("/api/subscription-status"); if (stripeRes.ok) { const data = await stripeRes.json(); if (data.subscription?.cancelAtPeriodEnd) { setIsCancelling(true); setPeriodEnd(data.subscription.periodEnd ?? null); } } } catch (subErr) { console.error("Failed to load subscription:", subErr); setCurrentPlan("free"); } } } catch (err) { // Not logged in } setIsLoading(false); }; checkSession(); }, []); const handleUpgrade = async (plan: PlanId) => { setError(null); if (!isLoggedIn) { navigate("/signup"); return; } if (plan === "free") { // Downgrade = cancel subscription setUpgradeLoading("free"); try { const result = await authClient.subscription.cancel({ returnUrl: `${window.location.origin}/#/pricing?downgraded=true`, }); if (result?.error) { const errorMsg = result.error.message || ""; // If already cancelling, treat as success if (errorMsg.includes("already set to be canceled")) { setIsCancelling(true); toast.success("Subscription is set to cancel", { description: "You'll have access until the end of your billing period.", }); } else { setError(errorMsg || "Failed to cancel subscription."); } } else if (result?.data?.url) { window.location.href = result.data.url; } else { // Cancelled immediately setIsCancelling(true); setError(null); toast.success("Subscription cancelled", { description: "You'll have access until the end of your billing period.", }); } } catch (err) { console.error("Cancel error:", err); setError("Failed to cancel subscription. Please try again or contact support."); } setUpgradeLoading(null); return; } // If cancelling and clicking current plan = resubscribe (undo cancellation) if (isCancelling && plan === currentPlan) { setUpgradeLoading(plan); try { const res = await fetch("/api/resubscribe", { method: "POST" }); const data = await res.json(); if (res.ok && data.success) { setIsCancelling(false); toast.success("Resubscribed successfully", { description: "Your subscription will continue as normal.", }); } else { setError(data.error || "Failed to resubscribe."); } } catch (err) { console.error("Resubscribe error:", err); setError("Failed to resubscribe. Please try again."); } setUpgradeLoading(null); return; } setUpgradeLoading(plan); try { const result = await authClient.subscription.upgrade({ plan, successUrl: `${window.location.origin}/#/dashboard?upgraded=true`, cancelUrl: `${window.location.origin}/#/pricing`, }); if (result?.error) { setError(result.error.message || "Failed to upgrade. Please try again."); } else if (result?.data?.url) { // Redirect to Stripe checkout window.location.href = result.data.url; } } catch (err) { console.error("Upgrade error:", err); setError("An unexpected error occurred. Please try again."); } setUpgradeLoading(null); }; const getButtonText = (plan: PlanConfig) => { if (!isLoggedIn) { return plan.id === "free" ? "Get started" : "Start free trial"; } // When user is cancelling their current paid plan if (isCancelling && isPaidPlan(currentPlan || "free")) { if (plan.id === currentPlan) { // Their current plan - offer to undo cancellation return "Resubscribe"; } if (plan.id === "free") { // Free plan - this is where they're heading after cancellation return "After period ends"; } // Other paid plans - can still switch return "Switch plan"; } // Normal flow (not cancelling) if (currentPlan === plan.id) { return "Current plan"; } if (plan.id === "free") { return "Downgrade"; } return "Upgrade"; }; const isButtonDisabled = (plan: PlanConfig) => { if (isLoading || upgradeLoading !== null) return true; // When cancelling: only disable Free (they're already heading there) if (isCancelling && isPaidPlan(currentPlan || "free")) { if (plan.id === "free") return true; return false; // Allow resubscribe or switch } // Normal flow: disable current plan if (currentPlan === plan.id) return true; return false; }; return (
{/* Navigation */} {/* Pricing Header */}

Simple, transparent pricing

Choose the plan that's right for you. All plans include a 14-day free trial.

{isLoggedIn && currentPlan !== "free" && ( )}
{/* Test Mode Banner - only shown when Stripe is in test mode */} {stripeTestMode && (

Test Mode: Use card{" "} 4242 4242 4242 4242 {" "} with any future expiry and CVC.

)} {/* Cancellation Pending Notice */} {isCancelling && (

Your subscription is set to cancel. You'll have access to{" "} {getPlanName(currentPlan || "free")} features until{" "} {periodEnd ? new Date(periodEnd).toLocaleDateString() : "the end of your billing period"} .

)} {/* Error Message */} {error && (

{error}

)} {/* Pricing Cards */}
{plans.map((plan) => ( {plan.highlighted && (
Most popular
)} {plan.name}
{plan.price} {plan.id !== "free" && /month}
{plan.description}
    {plan.features.map((feature) => (
  • {feature}
  • ))}
))}
{/* FAQ Section */}

Frequently asked questions

Can I change plans later?

Yes, you can upgrade or downgrade your plan at any time. Changes take effect immediately.

What payment methods do you accept?

We accept all major credit cards through our secure Stripe integration.

Is there a free trial?

Yes, all paid plans come with a 14-day free trial. No credit card required to start.

Can I cancel anytime?

Absolutely. You can cancel your subscription at any time with no questions asked.

{/* Footer */}
); }