'use client' import { Suspense, useEffect, useState } from 'react' import { useRouter, useSearchParams } from 'next/navigation' import Image from 'next/image' import Link from 'next/link' import { motion } from 'framer-motion' import { useAuth } from '@/lib/auth/context' import { useSubscription } from '@/lib/swr/dashboard' import { getSubscription } from '@/lib/api/billing' import { PLAN_PRICES, TRAFFIC_TIERS } from '@/lib/plans' import PlanSummary from '@/components/checkout/PlanSummary' import PaymentForm from '@/components/checkout/PaymentForm' import FeatureSlideshow from '@/components/checkout/FeatureSlideshow' import pulseIcon from '@/public/pulse_icon_no_margins.png' // --------------------------------------------------------------------------- // Validation helpers // --------------------------------------------------------------------------- const VALID_PLANS = new Set(Object.keys(PLAN_PRICES)) const VALID_INTERVALS = new Set(['month', 'year']) const VALID_LIMITS = new Set(TRAFFIC_TIERS.map((t) => t.value)) function isValidCheckoutParams(plan: string | null, interval: string | null, limit: string | null) { if (!plan || !interval || !limit) return false const limitNum = Number(limit) if (!VALID_PLANS.has(plan)) return false if (!VALID_INTERVALS.has(interval)) return false if (!VALID_LIMITS.has(limitNum)) return false if (!PLAN_PRICES[plan]?.[limitNum]) return false return true } // --------------------------------------------------------------------------- // Success polling component (post-3DS return) // --------------------------------------------------------------------------- function CheckoutSuccess() { const router = useRouter() const [ready, setReady] = useState(false) const [timedOut, setTimedOut] = useState(false) useEffect(() => { let cancelled = false const timeout = setTimeout(() => setTimedOut(true), 30000) const poll = async () => { for (let i = 0; i < 15; i++) { if (cancelled) return try { const data = await getSubscription() if (data.subscription_status === 'active' || data.subscription_status === 'trialing') { setReady(true) clearTimeout(timeout) setTimeout(() => router.push('/'), 2000) return } } catch { // ignore — keep polling } await new Promise((r) => setTimeout(r, 2000)) } setTimedOut(true) } poll() return () => { cancelled = true clearTimeout(timeout) } }, [router]) return (
{ready ? ( <>

You're all set!

Redirecting to dashboard...

) : timedOut ? ( <>

Taking longer than expected

Your payment was received. It may take a moment to activate.

Go to dashboard ) : ( <>

Setting up your subscription...

This usually takes a few seconds.

)}
) } // --------------------------------------------------------------------------- // Main checkout content (reads searchParams) // --------------------------------------------------------------------------- function CheckoutContent() { const router = useRouter() const searchParams = useSearchParams() const { user, loading: authLoading } = useAuth() const { data: subscription } = useSubscription() const status = searchParams.get('status') const plan = searchParams.get('plan') const interval = searchParams.get('interval') const limit = searchParams.get('limit') // -- Auth guard -- useEffect(() => { if (!authLoading && !user) { const returnUrl = encodeURIComponent(window.location.pathname + window.location.search) router.replace(`/login?redirect=${returnUrl}`) } }, [authLoading, user, router]) // -- Subscription guard (skip on success page — it handles its own redirect) -- useEffect(() => { if (status === 'success') return if (subscription && (subscription.subscription_status === 'active' || subscription.subscription_status === 'trialing')) { router.replace('/') } }, [subscription, status, router]) // -- Param validation -- useEffect(() => { if (status === 'success') return // success state doesn't need plan params if (!authLoading && user && !isValidCheckoutParams(plan, interval, limit)) { router.replace('/pricing') } }, [authLoading, user, plan, interval, limit, status, router]) // -- Post-3DS success -- if (status === 'success') { return } // -- Loading state -- if (authLoading || !user || !isValidCheckoutParams(plan, interval, limit)) { return (
) } const planId = plan! const billingInterval = interval as 'month' | 'year' const pageviewLimit = Number(limit) return (
{/* Left — Feature slideshow (hidden on mobile) */}
{/* Right — Payment (scrollable) */}
{/* Header */}
Pulse Pulse
{/* Main content */}
{/* Plan summary (compact) */} {/* Payment form */}
) } // --------------------------------------------------------------------------- // Page wrapper with Suspense (required for useSearchParams in App Router) // --------------------------------------------------------------------------- export default function CheckoutPage() { return (
} >
) }