import { Link, createFileRoute, useNavigate } from "@tanstack/react-router"; import { useCustomer } from "autumn-js/react"; import { useEffect, useState } from "react"; import { ArrowLeft, ChevronDown, Globe, Settings, User } from "lucide-react"; import { ThemePreferenceMenuItems } from "@/client/components/ThemePreferenceMenuItems"; import { captureClientEvent } from "@/client/lib/posthog"; import { signOutAndRedirect, useSession } from "@/lib/auth-client"; import { isHostedClientAuthMode } from "@/lib/auth-mode"; import { getStandardErrorMessage } from "@/client/lib/error-messages"; import { getSubscribeRouteState } from "@/client/features/billing/route-state"; import { getCustomerPlanStatus } from "@/client/features/billing/plan-detection"; import { normalizeAuthRedirect } from "@/lib/auth-redirect"; import { useCanManageBilling } from "@/client/features/team/organizationQueries"; import { AUTUMN_MANAGED_ACCESS_FEATURE_ID } from "@/shared/billing"; import { PricingTable } from "@/client/features/billing/PricingTable"; import type { PricingPlan, BillingInterval, Currency, } from "@/client/features/billing/PricingData"; import { createStripeCheckoutSession, getOrganizationBilling, verifyStripeCheckoutSession, } from "@/serverFunctions/stripe"; import { CornerWaveDecoration, CrawlerXLogo, } from "@/client/features/billing/PricingGraphics"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { getProjects } from "@/serverFunctions/projects"; import { WorkspaceUsageBanner } from "@/client/features/billing/WorkspaceUsageBanner"; import { toast } from "sonner"; const SUPPORT_EMAIL = "support@metatroncube.com"; const FINALIZING_TIMEOUT_MS = 30_000; export const Route = createFileRoute("/_authenticated/subscribe")({ validateSearch: ( search: Record, ): { upgrade?: true; redirect?: string; checkout?: "success"; session_id?: string; } => ({ upgrade: search.upgrade === true || search.upgrade === "true" ? true : undefined, redirect: typeof search.redirect === "string" ? normalizeAuthRedirect(search.redirect) : undefined, checkout: search.checkout === "success" ? "success" : undefined, session_id: typeof search.session_id === "string" ? search.session_id : undefined, }), component: SubscribePage, }); function SubscribePage() { const navigate = useNavigate(); const queryClient = useQueryClient(); const { upgrade: isUpgradeFlow, redirect, checkout, session_id: sessionId, } = Route.useSearch(); const { data: session } = useSession(); const [loadingPlanId, setLoadingPlanId] = useState(null); const [error, setError] = useState(null); const [finalizingTimedOut, setFinalizingTimedOut] = useState(false); const [hasVerifiedSession, setHasVerifiedSession] = useState(false); const checkoutCompleted = checkout === "success"; const hasSession = Boolean(session?.user?.id); const customerQuery = useCustomer({ queryOptions: { enabled: hasSession }, }); const billingQuery = useQuery({ queryKey: ["organization-billing", session?.user?.id], queryFn: () => getOrganizationBilling(), enabled: hasSession, }); useEffect(() => { if (checkoutCompleted && sessionId && !hasVerifiedSession) { setHasVerifiedSession(true); void (async () => { try { const res = await verifyStripeCheckoutSession({ data: { sessionId }, }); if (res?.success) { await queryClient.invalidateQueries({ queryKey: ["organization-billing"], }); toast.success( `Payment successful! Your ${res.planId.toUpperCase()} plan is now active.`, ); } } catch (err) { console.error("Failed to verify Stripe checkout session:", err); } })(); } }, [checkoutCompleted, sessionId, hasVerifiedSession, queryClient]); const canManageBilling = useCanManageBilling(); const hasManagedAccess = isHostedClientAuthMode() ? customerQuery.check({ featureId: AUTUMN_MANAGED_ACCESS_FEATURE_ID }) .allowed : true; const planStatus = getCustomerPlanStatus(customerQuery.data); const projectsQuery = useQuery({ queryKey: ["projects"], queryFn: () => getProjects(), enabled: hasSession, }); const siteCount = Math.max(1, projectsQuery.data?.length ?? 1); const activePlanId: "free" | "starter" | "pro" | "agency" = billingQuery.data?.planId ?? (planStatus === "paid" ? "pro" : "free"); const subscribeRouteState = getSubscribeRouteState({ hasSession, isCustomerLoading: customerQuery.isLoading, isCustomerError: customerQuery.isError, hasManagedAccess, planStatus, isUpgradeFlow: isUpgradeFlow === true, checkoutCompleted: checkoutCompleted && !sessionId, finalizingTimedOut, }); const isFinalizing = !sessionId && subscribeRouteState === "finalizing"; const { refetch: refetchCustomer } = customerQuery; useEffect(() => { if (!isFinalizing) return; const interval = setInterval(() => { void refetchCustomer(); }, 2000); return () => clearInterval(interval); }, [refetchCustomer, isFinalizing]); useEffect(() => { if (!checkoutCompleted || finalizingTimedOut) return; const timeout = setTimeout( () => setFinalizingTimedOut(true), FINALIZING_TIMEOUT_MS, ); return () => clearTimeout(timeout); }, [checkoutCompleted, finalizingTimedOut]); useEffect(() => { if (subscribeRouteState === "redirectToApp") { if (checkoutCompleted) { captureClientEvent("billing:checkout_success"); } void navigate({ href: redirect ?? "/", replace: true }); } }, [checkoutCompleted, navigate, redirect, subscribeRouteState]); useEffect(() => { if (subscribeRouteState === "showPaywall" && !isUpgradeFlow) { captureClientEvent("billing:paywall_viewed"); } }, [isUpgradeFlow, subscribeRouteState]); if ( subscribeRouteState === "loading" || subscribeRouteState === "redirectToApp" ) { return null; } if (subscribeRouteState === "finalizing") { return (
CrawlerX

Finalizing your subscription…

Setting up your workspace limits and credits.

); } async function handleSelectPlan( plan: PricingPlan, interval: BillingInterval = "monthly", currency: Currency = "INR", ) { if (plan.id === "free") { void navigate({ to: redirect ?? "/", replace: true }); return; } if (!canManageBilling) { toast.error("Only the organization owner can change subscription plans."); return; } setError(null); setLoadingPlanId(plan.id); try { captureClientEvent("billing:checkout_start", { planId: plan.id, interval, currency, }); const response = await createStripeCheckoutSession({ data: { planId: plan.id, interval, currency, }, }); if (response?.checkoutUrl) { window.location.assign(response.checkoutUrl); return; } throw new Error("Stripe checkout session did not return a valid URL."); } catch (err: unknown) { const errObj = err as { message?: string; data?: { message?: string } } | undefined; const rawMsg = errObj?.data?.message || errObj?.message; const msg = rawMsg ? getStandardErrorMessage(new Error(rawMsg), rawMsg) : "Stripe payment is not configured yet. Please set your STRIPE_SECRET_KEY in your .env file."; setError(msg); toast.error(msg); } finally { setLoadingPlanId(null); } } return (
{/* 3D Organic Fluid Corner Ribbons & Aura in Theme Blues */} {/* Main Content Container */}
{/* Top Navbar */}
{/* Exact CrawlerX Cube Logo & Subtitle */} {/* Center Navigation Links */} {/* Right Actions */}
INR (₹)
Dashboard
{/* Workspace Usage Notice */} { const el = document.getElementById("pricing-plans"); el?.scrollIntoView({ behavior: "smooth" }); }} /> {error ? (
{error}
) : null} {/* Pricing Component */}
{/* Footer */}
CrawlerX by Metatroncube Solutions • Enterprise & Custom Agency Plans: {SUPPORT_EMAIL}
); } function SubscribePageAccountMenu({ email }: { email: string | undefined }) { if (!email) return null; return (
  • {email}
  • Settings
); }