import { createFileRoute, notFound } from "@tanstack/react-router"; import { AutumnProvider, useCustomer } from "autumn-js/react"; import { useEffect, useState } from "react"; import { useSession } from "@/lib/auth-client"; import { isHostedClientAuthMode } from "@/lib/auth-mode"; import { getStandardErrorMessage } from "@/client/lib/error-messages"; import { getStoredRedditAttribution } from "@/client/lib/reddit-attribution"; import { BillingUsageChart } from "@/client/features/billing/BillingUsageChart"; import { BillingFeatureBreakdown } from "@/client/features/billing/BillingFeatureBreakdown"; import { parseTopUpAmount } from "@/client/features/billing/HostedBillingContentUtils"; import { getBillingRouteState } from "@/client/features/billing/route-state"; import { getCustomerPlanStatus } from "@/client/features/billing/plan-detection"; import { AUTUMN_PAID_PLAN_ID, AUTUMN_SEO_DATA_BALANCE_FEATURE_ID, LOW_CREDITS_THRESHOLD_USD, AUTUMN_SEO_DATA_CREDITS_PER_USD, AUTUMN_SEO_DATA_TOP_UP_PLAN_ID, AUTUMN_SEO_DATA_TOPUP_BALANCE_FEATURE_ID, autumnSeoDataCreditsToUsd, } from "@/shared/billing"; import { captureRedditConversionEvent } from "@/serverFunctions/redditConversions"; export const Route = createFileRoute("/_app/billing")({ beforeLoad: () => { if (!isHostedClientAuthMode()) { throw notFound(); } }, component: BillingPage, }); function BillingPage() { return ( ); } function BillingPageContent() { const { data: session, isPending: isSessionPending } = useSession(); const [topUpAmount, setTopUpAmount] = useState("20"); const [isPending, setIsPending] = useState(false); const [error, setError] = useState(null); const customerQuery = useCustomer({ queryOptions: { enabled: Boolean(session?.user?.id), }, }); const planStatus = getCustomerPlanStatus(customerQuery.data); const isFreePlan = planStatus === "free"; const billingRouteState = getBillingRouteState({ hasSession: Boolean(session?.user?.id), isSessionPending, isCustomerLoading: customerQuery.isLoading, isCustomerError: customerQuery.isError, }); const monthlyRemaining = autumnSeoDataCreditsToUsd( customerQuery.data?.balances?.[AUTUMN_SEO_DATA_BALANCE_FEATURE_ID] ?.remaining ?? 0, ); const topUpRemaining = autumnSeoDataCreditsToUsd( customerQuery.data?.balances?.[AUTUMN_SEO_DATA_TOPUP_BALANCE_FEATURE_ID] ?.remaining ?? 0, ); const totalRemaining = monthlyRemaining + topUpRemaining; const { isValid: isValidTopUp, parsed: parsedTopUpAmount } = parseTopUpAmount(topUpAmount); const checkoutCompleted = typeof window !== "undefined" && new URLSearchParams(window.location.search).get("checkout") === "success"; useEffect(() => { if (!checkoutCompleted || billingRouteState !== "ready") return; const attribution = getStoredRedditAttribution(); if (!attribution) return; void captureRedditConversionEvent({ data: { attribution, eventType: "PURCHASE" }, }); }, [billingRouteState, checkoutCompleted]); if (billingRouteState === "loading") { return null; } if (billingRouteState === "error") { return (

Billing unavailable

{getStandardErrorMessage( customerQuery.error, "We couldn't load your billing details right now. Please try again.", )}

); } async function runAction( callback: () => Promise, fallbackMessage: string, ) { setError(null); setIsPending(true); try { await callback(); await customerQuery.refetch(); } catch (err) { setError(getStandardErrorMessage(err, fallbackMessage)); } finally { setIsPending(false); } } if (isPending) { return (

Redirecting to Stripe...

); } return (

Billing

{/* Subscription card */}
${totalRemaining.toFixed(2)}{" "} remaining
{!isFreePlan ? (
Monthly ${monthlyRemaining.toFixed(2)} · Top-ups ${topUpRemaining.toFixed(2)}
) : null} {totalRemaining <= 0 ? (

You’ve used all your credits.{" "} {isFreePlan ? "Upgrade your plan to continue." : "Buy more credits below to continue."}

) : totalRemaining < LOW_CREDITS_THRESHOLD_USD ? (

You’re running low on credits.{" "} {isFreePlan ? "Upgrade to get $10/month." : "Buy more credits below."}

) : null}
Plan{" "} {isFreePlan ? "Free Plan" : "Base Plan"}
{isFreePlan ? (
Base Plan $10/month
    {[ "Access to all OpenSEO features", "Includes $10.00 of Usage Credits each month", ].map((item) => (
  • {item}
  • ))}
) : ( )}
{/* Buy credits card — paid plan only */} {!isFreePlan ? (
Buy credits

Top-up credits never expire and are used after your monthly credits.

$ setTopUpAmount(e.target.value)} />
{topUpAmount.trim() !== "" && !isValidTopUp ? (

Enter between $10–$99.

) : null}
) : null}
{/* Usage chart */} {/* Per-feature usage breakdown */} {error ?

{error}

: null}

Billing is powered by Stripe.

); }