From c1316d8b38031ad733a8754417c5965e4e983a59 Mon Sep 17 00:00:00 2001 From: "yves.gugger" Date: Wed, 10 Dec 2025 16:17:29 +0100 Subject: [PATCH] feat: Perfect onboarding journey after Stripe payment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NEW WELCOME PAGE (/command/welcome): - Celebratory confetti animation on arrival - Plan-specific welcome message (Trader/Tycoon) - Features unlocked section with icons - Next steps with quick links to key features - Link to documentation and support UPDATED USER JOURNEY: 1. Pricing Page (/pricing) ↓ Click plan button 2. (If not logged in) → Register → Back to Pricing ↓ Click plan button 3. Stripe Checkout (external) ↓ Payment successful 4. Welcome Page (/command/welcome?plan=trader) - Shows unlocked features - Guided next steps ↓ 'Go to Dashboard' 5. Dashboard (/command/dashboard) CANCEL FLOW: - Stripe Cancel → /pricing?cancelled=true - Shows friendly banner: 'No worries! Card not charged.' - Dismissible with X button - URL cleaned up automatically BACKEND UPDATES: - Default success URL: /command/welcome?plan={plan} - Default cancel URL: /pricing?cancelled=true - Portal return URL: /command/settings (not /dashboard) This creates a complete, professional onboarding experience that celebrates the upgrade and guides users to get started. --- backend/app/api/subscription.py | 4 +- frontend/src/app/command/welcome/page.tsx | 221 ++++++++++++++++++++++ frontend/src/app/pricing/page.tsx | 37 +++- 3 files changed, 257 insertions(+), 5 deletions(-) create mode 100644 frontend/src/app/command/welcome/page.tsx diff --git a/backend/app/api/subscription.py b/backend/app/api/subscription.py index a3adf02..18cc7ec 100644 --- a/backend/app/api/subscription.py +++ b/backend/app/api/subscription.py @@ -225,7 +225,7 @@ async def create_checkout_session( # Get site URL from environment site_url = os.getenv("SITE_URL", "http://localhost:3000") - success_url = request.success_url or f"{site_url}/dashboard?upgraded=true" + success_url = request.success_url or f"{site_url}/command/welcome?plan={request.plan}" cancel_url = request.cancel_url or f"{site_url}/pricing?cancelled=true" try: @@ -285,7 +285,7 @@ async def create_portal_session( ) site_url = os.getenv("SITE_URL", "http://localhost:3000") - return_url = f"{site_url}/dashboard" + return_url = f"{site_url}/command/settings" try: portal_url = await StripeService.create_portal_session( diff --git a/frontend/src/app/command/welcome/page.tsx b/frontend/src/app/command/welcome/page.tsx new file mode 100644 index 0000000..3b46849 --- /dev/null +++ b/frontend/src/app/command/welcome/page.tsx @@ -0,0 +1,221 @@ +'use client' + +import { useEffect, useState } from 'react' +import { useRouter, useSearchParams } from 'next/navigation' +import { CommandCenterLayout } from '@/components/CommandCenterLayout' +import { PageContainer } from '@/components/PremiumTable' +import { useStore } from '@/lib/store' +import { + CheckCircle, + Zap, + Crown, + ArrowRight, + Eye, + Store, + Bell, + BarChart3, + Sparkles, + TrendingUp, +} from 'lucide-react' +import Link from 'next/link' +import clsx from 'clsx' + +const planDetails = { + trader: { + name: 'Trader', + icon: TrendingUp, + color: 'text-accent', + bgColor: 'bg-accent/10', + features: [ + { icon: Eye, text: '50 domains in watchlist', description: 'Track up to 50 domains at once' }, + { icon: Zap, text: 'Hourly availability checks', description: '24x faster than Scout' }, + { icon: Store, text: '10 For Sale listings', description: 'List your domains on the marketplace' }, + { icon: Bell, text: '5 Sniper Alerts', description: 'Get notified when specific domains drop' }, + { icon: BarChart3, text: 'Deal scores & valuations', description: 'Know what domains are worth' }, + ], + nextSteps: [ + { href: '/command/watchlist', label: 'Add domains to watchlist', icon: Eye }, + { href: '/command/alerts', label: 'Set up Sniper Alerts', icon: Bell }, + { href: '/command/portfolio', label: 'Track your portfolio', icon: BarChart3 }, + ], + }, + tycoon: { + name: 'Tycoon', + icon: Crown, + color: 'text-amber-400', + bgColor: 'bg-amber-400/10', + features: [ + { icon: Eye, text: '500 domains in watchlist', description: 'Massive tracking capacity' }, + { icon: Zap, text: 'Real-time checks (10 min)', description: 'Never miss a drop' }, + { icon: Store, text: '50 For Sale listings', description: 'Full marketplace access' }, + { icon: Bell, text: 'Unlimited Sniper Alerts', description: 'Set as many as you need' }, + { icon: Sparkles, text: 'SEO Juice Detector', description: 'Find domains with backlinks' }, + ], + nextSteps: [ + { href: '/command/watchlist', label: 'Add domains to watchlist', icon: Eye }, + { href: '/command/seo', label: 'Analyze SEO metrics', icon: Sparkles }, + { href: '/command/alerts', label: 'Create Sniper Alerts', icon: Bell }, + ], + }, +} + +export default function WelcomePage() { + const router = useRouter() + const searchParams = useSearchParams() + const { fetchSubscription, checkAuth } = useStore() + const [loading, setLoading] = useState(true) + const [showConfetti, setShowConfetti] = useState(true) + + const planId = searchParams.get('plan') as 'trader' | 'tycoon' | null + const plan = planId && planDetails[planId] ? planDetails[planId] : planDetails.trader + + useEffect(() => { + const init = async () => { + await checkAuth() + await fetchSubscription() + setLoading(false) + } + init() + + // Hide confetti after animation + const timer = setTimeout(() => setShowConfetti(false), 3000) + return () => clearTimeout(timer) + }, [checkAuth, fetchSubscription]) + + if (loading) { + return ( + + +
+
+
+ + + ) + } + + return ( + + + {/* Confetti Effect */} + {showConfetti && ( +
+ {Array.from({ length: 50 }).map((_, i) => ( +
+ ))} +
+ )} + + {/* Success Header */} +
+
+ +
+ +

+ Welcome to {plan.name}! +

+

+ Your payment was successful. You now have access to all {plan.name} features. +

+
+ + {/* Features Unlocked */} +
+

+ Features Unlocked +

+
+ {plan.features.map((feature, i) => ( +
+
+
+ +
+
+

{feature.text}

+

{feature.description}

+
+
+
+ ))} +
+
+ + {/* Next Steps */} +
+

+ Get Started +

+
+ {plan.nextSteps.map((step, i) => ( + +
+
+ +
+ {step.label} +
+ + + ))} +
+
+ + {/* Go to Dashboard */} +
+ + Go to Dashboard + + +

+ Need help? Check out our documentation or{' '} + contact support. +

+
+ + + {/* Custom CSS for confetti animation */} + + + ) +} + diff --git a/frontend/src/app/pricing/page.tsx b/frontend/src/app/pricing/page.tsx index eb413cd..b8efd47 100644 --- a/frontend/src/app/pricing/page.tsx +++ b/frontend/src/app/pricing/page.tsx @@ -6,7 +6,7 @@ import { Header } from '@/components/Header' import { Footer } from '@/components/Footer' import { useStore } from '@/lib/store' import { api } from '@/lib/api' -import { Check, ArrowRight, Zap, TrendingUp, Crown, Loader2, Clock, X } from 'lucide-react' +import { Check, ArrowRight, Zap, TrendingUp, Crown, Loader2, Clock, X, AlertCircle } from 'lucide-react' import Link from 'next/link' import clsx from 'clsx' @@ -119,9 +119,20 @@ export default function PricingPage() { const { checkAuth, isLoading, isAuthenticated } = useStore() const [loadingPlan, setLoadingPlan] = useState(null) const [expandedFaq, setExpandedFaq] = useState(null) + const [showCancelledBanner, setShowCancelledBanner] = useState(false) useEffect(() => { checkAuth() + + // Check if user cancelled checkout + if (typeof window !== 'undefined') { + const params = new URLSearchParams(window.location.search) + if (params.get('cancelled') === 'true') { + setShowCancelledBanner(true) + // Clean up URL + window.history.replaceState({}, '', '/pricing') + } + } }, [checkAuth]) const handleSelectPlan = async (planId: string, isPaid: boolean) => { @@ -139,8 +150,8 @@ export default function PricingPage() { try { const response = await api.createCheckoutSession( planId, - `${window.location.origin}/dashboard?upgraded=true`, - `${window.location.origin}/pricing` + `${window.location.origin}/command/welcome?plan=${planId}`, + `${window.location.origin}/pricing?cancelled=true` ) window.location.href = response.checkout_url } catch (error) { @@ -168,6 +179,26 @@ export default function PricingPage() {
+ {/* Cancelled Banner */} + {showCancelledBanner && ( +
+ +
+

Checkout cancelled

+

+ No worries! Your card was not charged. You can try again whenever you're ready, + or continue with the free Scout plan. +

+
+ +
+ )} + {/* Hero */}
Pricing