'use client'
import React, { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import {
ChevronLeftIcon,
ChevronRightIcon,
XMarkIcon,
BookOpenIcon,
ChatBubbleBottomCenterTextIcon,
FolderIcon,
PencilIcon,
SparklesIcon,
RocketLaunchIcon,
CheckIcon,
PlayIcon
} from '@heroicons/react/24/outline'
import { PRICING_TIERS, formatPrice, getStripe, type BillingInterval, getPriceForInterval } from '@/lib/stripe'
import { supabase } from '@/lib/supabase'
interface WelcomeTourProps {
isOpen: boolean
onClose: () => void
onComplete: () => void
}
interface TourStep {
id: string
title: string
description: string
content: React.ReactNode
}
export default function WelcomeTour({ isOpen, onClose, onComplete }: WelcomeTourProps) {
const [currentStep, setCurrentStep] = useState(0)
const [selectedPlan, setSelectedPlan] = useState<string>('')
const [billingInterval, setBillingInterval] = useState<BillingInterval>('month')
const [isProcessingPayment, setIsProcessingPayment] = useState(false)
const [viewedSteps, setViewedSteps] = useState<Set<number>>(new Set([0]))
const [tourStartTime, setTourStartTime] = useState<number | null>(null)
const [tourVersion, setTourVersion] = useState<'v1' | 'v2'>('v1')
const [isVideoLoaded, setIsVideoLoaded] = useState(false)
const [videoLoadError, setVideoLoadError] = useState(false)
const [videoKey, setVideoKey] = useState(0)
// Initialize tour start time and A/B test version when tour opens
React.useEffect(() => {
if (isOpen && !tourStartTime) {
setTourStartTime(Date.now())
// A/B testing: 50/50 chance for v1 (storyteller) vs v2 (explorer)
const random = Math.random()
const version = random < 0.5 ? 'v1' : 'v2'
const defaultPlan = version === 'v1' ? 'storyteller' : 'explorer'
setTourVersion(version)
setSelectedPlan(defaultPlan)
// Reset video loading state when tour opens
setIsVideoLoaded(false)
setVideoLoadError(false)
setVideoKey(prev => prev + 1)
}
}, [isOpen, tourStartTime])
// Reset video state when navigating to step 1
React.useEffect(() => {
if (currentStep === 0) {
setIsVideoLoaded(false)
setVideoLoadError(false)
setVideoKey(prev => prev + 1)
}
}, [currentStep])
// Handle video loading timeout
React.useEffect(() => {
if (isOpen && currentStep === 0 && !isVideoLoaded && !videoLoadError) {
const videoTimeout = setTimeout(() => {
setVideoLoadError(true)
}, 10000)
return () => clearTimeout(videoTimeout)
}
}, [isOpen, currentStep, isVideoLoaded, videoLoadError])
// Track plan selection changes
React.useEffect(() => {
if (isOpen && currentStep === 2) {
trackPlanSelection(selectedPlan, billingInterval)
}
}, [selectedPlan, billingInterval, isOpen, currentStep])
// Lock body scroll when tour is open
React.useEffect(() => {
if (isOpen) {
// Save current scroll position
const scrollY = window.scrollY
document.body.style.position = 'fixed'
document.body.style.top = `-${scrollY}px`
document.body.style.width = '100%'
} else {
// Restore scroll position
const scrollY = document.body.style.top
document.body.style.position = ''
document.body.style.top = ''
document.body.style.width = ''
if (scrollY) {
window.scrollTo(0, parseInt(scrollY || '0') * -1)
}
}
// Cleanup on unmount
return () => {
document.body.style.position = ''
document.body.style.top = ''
document.body.style.width = ''
}
}, [isOpen])
// Get pricing tiers in ascending order by price (Free first)
const pricingTiersOrdered = [
PRICING_TIERS.FREE,
PRICING_TIERS.EXPLORER,
PRICING_TIERS.STORYTELLER,
PRICING_TIERS.PROFESSIONAL
]
const tourSteps: TourStep[] = [
{
id: 'welcome',
title: 'Welcome to Bookwiz',
description: 'Watch how it works',
content: (
<div className="space-y-4">
{/* Video Embed - More Prominent */}
<div className="relative w-full aspect-video rounded-lg overflow-hidden bg-slate-800 border border-slate-700 shadow-lg">
{/* Loading State */}
<AnimatePresence>
{!isVideoLoaded && !videoLoadError && (
<motion.div
className="absolute inset-0 flex items-center justify-center bg-slate-800 z-10"
initial={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.3 }}
>
<div className="text-center space-y-4">
<div className="relative">
<div className="w-16 h-16 border-4 border-slate-600 border-t-blue-500 rounded-full animate-spin mx-auto"></div>
<div className="absolute inset-0 flex items-center justify-center">
<PlayIcon className="w-6 h-6 text-blue-400" />
</div>
</div>
<div className="space-y-2">
<p className="text-slate-300 text-sm font-medium">Loading demo video...</p>
<p className="text-slate-500 text-xs">This will just take a moment</p>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
{/* Error State */}
<AnimatePresence>
{videoLoadError && (
<motion.div
className="absolute inset-0 flex items-center justify-center bg-slate-800 z-10"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
>
<div className="text-center space-y-4">
<div className="w-16 h-16 bg-red-500/20 rounded-full flex items-center justify-center mx-auto">
<PlayIcon className="w-6 h-6 text-red-400" />
</div>
<div className="space-y-2">
<p className="text-slate-300 text-sm font-medium">Video unavailable</p>
<p className="text-slate-500 text-xs">You can continue with the tour</p>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
<motion.iframe
key={`video-${videoKey}`}
src="https://www.veed.io/embed/d002828c-4b1e-4552-8d87-d6ce48b05d4a?watermark=0&color=&sharing=0&title=0&autoplay=0&volume=100&unmuted=1"
className="w-full h-full"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
title="Bookwiz Demo"
initial={{ opacity: 0 }}
animate={{ opacity: isVideoLoaded ? 1 : 0 }}
transition={{ duration: 0.3, delay: 0.1 }}
onLoad={() => {
setIsVideoLoaded(true)
trackVideoWatched()
}}
onError={() => {
setVideoLoadError(true)
}}
/>
</div>
<div className="text-center">
<p className="text-slate-400 text-xs">
💡 You can skip and continue if you prefer
</p>
</div>
</div>
)
},
{
id: 'how-it-works',
title: 'How Bookwiz Works',
description: 'Your 3-column writing workspace explained',
content: (
<div className="space-y-6">
{/* Interactive 3-Column Layout */}
<div className="relative overflow-hidden">
{/* Background Layout Visualization */}
<div className="grid grid-cols-3 gap-2 h-48 mb-4">
{/* File Explorer Column */}
<motion.div
className="relative group cursor-pointer"
whileHover={{ scale: 1.02 }}
transition={{ duration: 0.2 }}
>
<div className="absolute inset-0 bg-gradient-to-b from-blue-500/10 to-blue-600/5 rounded-xl border border-blue-500/20 group-hover:border-blue-400/40 group-hover:bg-blue-500/15 transition-all duration-300" />
<div className="absolute inset-0 bg-gradient-to-br from-blue-500/5 via-transparent to-transparent rounded-xl" />
{/* Column Content */}
<div className="relative h-full p-3 flex flex-col items-center justify-center space-y-2">
<div className="w-12 h-12 bg-gradient-to-br from-blue-500/20 to-blue-600/20 rounded-xl flex items-center justify-center group-hover:scale-110 transition-transform duration-300">
<FolderIcon className="h-6 w-6 text-blue-400 group-hover:text-blue-300 transition-colors" />
</div>
<div className="text-center">
<h4 className="font-bold text-white text-sm group-hover:text-blue-300 transition-colors">File Explorer</h4>
<p className="text-slate-400 text-xs mt-1 group-hover:text-slate-300 transition-colors">Organize your content</p>
</div>
</div>
{/* Hover Info Panel */}
<motion.div
className="absolute -top-2 left-1/2 transform -translate-x-1/2 z-20 opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none"
initial={{ y: 10, opacity: 0 }}
whileHover={{ y: 0, opacity: 1 }}
>
<div className="bg-slate-900/95 backdrop-blur-sm border border-blue-500/30 rounded-xl p-3 shadow-2xl w-56">
<div className="flex items-center space-x-2 mb-2">
<div className="w-6 h-6 bg-blue-500/20 rounded-lg flex items-center justify-center">
<FolderIcon className="h-3 w-3 text-blue-400" />
</div>
<h5 className="font-semibold text-white text-sm">File Explorer</h5>
</div>
<div className="space-y-1 text-xs">
<p className="text-slate-300">• Organize chapters, scenes, and characters</p>
<p className="text-slate-300">• Create folders and subfolders</p>
<p className="text-slate-300">• Quick search and navigation</p>
<p className="text-slate-300">• Drag & drop file management</p>
</div>
</div>
</motion.div>
</motion.div>
{/* Writing Editor Column */}
<motion.div
className="relative group cursor-pointer"
whileHover={{ scale: 1.02 }}
transition={{ duration: 0.2 }}
>
<div className="absolute inset-0 bg-gradient-to-b from-green-500/10 to-green-600/5 rounded-xl border border-green-500/20 group-hover:border-green-400/40 group-hover:bg-green-500/15 transition-all duration-300" />
<div className="absolute inset-0 bg-gradient-to-br from-green-500/5 via-transparent to-transparent rounded-xl" />
{/* Column Content */}
<div className="relative h-full p-3 flex flex-col items-center justify-center space-y-2">
<div className="w-12 h-12 bg-gradient-to-br from-green-500/20 to-green-600/20 rounded-xl flex items-center justify-center group-hover:scale-110 transition-transform duration-300">
<PencilIcon className="h-6 w-6 text-green-400 group-hover:text-green-300 transition-colors" />
</div>
<div className="text-center">
<h4 className="font-bold text-white text-sm group-hover:text-green-300 transition-colors">Writing Editor</h4>
<p className="text-slate-400 text-xs mt-1 group-hover:text-slate-300 transition-colors">Your creative space</p>
</div>
</div>
{/* Hover Info Panel */}
<motion.div
className="absolute -top-2 left-1/2 transform -translate-x-1/2 z-20 opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none"
initial={{ y: 10, opacity: 0 }}
whileHover={{ y: 0, opacity: 1 }}
>
<div className="bg-slate-900/95 backdrop-blur-sm border border-green-500/30 rounded-xl p-3 shadow-2xl w-56">
<div className="flex items-center space-x-2 mb-2">
<div className="w-6 h-6 bg-green-500/20 rounded-lg flex items-center justify-center">
<PencilIcon className="h-3 w-3 text-green-400" />
</div>
<h5 className="font-semibold text-white text-sm">Writing Editor</h5>
</div>
<div className="space-y-1 text-xs">
<p className="text-slate-300">• Distraction-free writing environment</p>
<p className="text-slate-300">• Rich text formatting & styling</p>
<p className="text-slate-300">• Auto-save & version history</p>
<p className="text-slate-300">• Word count & progress tracking</p>
</div>
</div>
</motion.div>
</motion.div>
{/* AI Assistant Column */}
<motion.div
className="relative group cursor-pointer"
whileHover={{ scale: 1.02 }}
transition={{ duration: 0.2 }}
>
<div className="absolute inset-0 bg-gradient-to-b from-purple-500/10 to-purple-600/5 rounded-xl border border-purple-500/20 group-hover:border-purple-400/40 group-hover:bg-purple-500/15 transition-all duration-300" />
<div className="absolute inset-0 bg-gradient-to-br from-purple-500/5 via-transparent to-transparent rounded-xl" />
{/* Column Content */}
<div className="relative h-full p-3 flex flex-col items-center justify-center space-y-2">
<div className="w-12 h-12 bg-gradient-to-br from-purple-500/20 to-purple-600/20 rounded-xl flex items-center justify-center group-hover:scale-110 transition-transform duration-300">
<ChatBubbleBottomCenterTextIcon className="h-6 w-6 text-purple-400 group-hover:text-purple-300 transition-colors" />
</div>
<div className="text-center">
<h4 className="font-bold text-white text-sm group-hover:text-purple-300 transition-colors">AI Assistant</h4>
<p className="text-slate-400 text-xs mt-1 group-hover:text-slate-300 transition-colors">Your writing coach</p>
</div>
</div>
{/* Hover Info Panel */}
<motion.div
className="absolute -top-2 left-1/2 transform -translate-x-1/2 z-20 opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none"
initial={{ y: 10, opacity: 0 }}
whileHover={{ y: 0, opacity: 1 }}
>
<div className="bg-slate-900/95 backdrop-blur-sm border border-purple-500/30 rounded-xl p-3 shadow-2xl w-56">
<div className="flex items-center space-x-2 mb-2">
<div className="w-6 h-6 bg-purple-500/20 rounded-lg flex items-center justify-center">
<ChatBubbleBottomCenterTextIcon className="h-3 w-3 text-purple-400" />
</div>
<h5 className="font-semibold text-white text-sm">AI Assistant</h5>
</div>
<div className="space-y-1 text-xs">
<p className="text-slate-300">• Get writing suggestions & feedback</p>
<p className="text-slate-300">• Ask questions about your story</p>
<p className="text-slate-300">• Generate ideas & overcome blocks</p>
<p className="text-slate-300">• Context-aware assistance</p>
</div>
</div>
</motion.div>
</motion.div>
</div>
{/* Connection Lines */}
<div className="absolute inset-0 pointer-events-none overflow-hidden">
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none" style={{ maxWidth: '100%' }}>
<defs>
<linearGradient id="connectionGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stopColor="rgba(59, 130, 246, 0.3)" />
<stop offset="50%" stopColor="rgba(34, 197, 94, 0.3)" />
<stop offset="100%" stopColor="rgba(147, 51, 234, 0.3)" />
</linearGradient>
</defs>
<path
d="M33 50 Q50 45 67 50"
stroke="url(#connectionGradient)"
strokeWidth="2"
fill="none"
className="animate-pulse"
/>
</svg>
</div>
</div>
{/* Bottom Info */}
<div className="bg-gradient-to-r from-slate-800/50 to-slate-700/50 rounded-xl p-4 border border-slate-600/30">
<div className="text-center space-y-2">
<div className="flex items-center justify-center space-x-2">
<SparklesIcon className="h-4 w-4 text-blue-400" />
<h3 className="text-base font-semibold text-white">Everything Works Together</h3>
<SparklesIcon className="h-4 w-4 text-purple-400" />
</div>
<p className="text-slate-300 text-xs leading-relaxed max-w-2xl mx-auto">
<span className="text-blue-400 font-medium">File Explorer</span> keeps you organized,
<span className="text-green-400 font-medium"> Writing Editor</span> lets you focus on creativity,
and <span className="text-purple-400 font-medium">AI Assistant</span> provides intelligent support.
All three work seamlessly together to make your writing journey magical.
</p>
</div>
</div>
</div>
)
},
{
id: 'plan-selection',
title: 'Choose Your Plan',
description: 'Select the plan that fits your writing journey',
content: (
<div className="space-y-6">
<div className="space-y-3">
{pricingTiersOrdered.map((tier) => {
const pricing = getPriceForInterval(tier, billingInterval)
return (
<div
key={tier.id}
className={`p-4 rounded-lg border cursor-pointer transition-all ${
selectedPlan === tier.id
? 'border-blue-500 bg-blue-500/10'
: 'border-slate-600 hover:border-slate-500'
}`}
onClick={() => setSelectedPlan(tier.id)}
>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<div className={`w-8 h-8 rounded-lg bg-gradient-to-br ${tier.color} flex items-center justify-center`}>
<tier.icon className="h-4 w-4 text-white" />
</div>
<div>
<div className="flex items-center space-x-2">
<h4 className="font-semibold text-white">{tier.name}</h4>
{selectedPlan === tier.id && (
<span className="text-xs bg-blue-500 text-white px-2 py-0.5 rounded">
Popular
</span>
)}
</div>
<p className="text-sm text-slate-400">
{tier.id === 'free' ? 'Perfect for trying out Bookwiz' : tier.description}
</p>
</div>
</div>
<div className="text-right">
<span className="text-xl font-bold text-white">
{tier.id === 'free' ? 'Free' : formatPrice(pricing.price)}
</span>
{tier.id !== 'free' && (
<p className="text-xs text-slate-400">
/{billingInterval === 'month' ? 'month' : 'year'}
</p>
)}
</div>
</div>
</div>
)
})}
</div>
<div className="text-center space-y-3">
<p className="text-xs text-slate-500">
Cancel anytime • No long-term commitments
</p>
{/* Minimal Billing Toggle */}
<div className="flex justify-center">
<div className="bg-slate-800/30 rounded-md p-0.5 border border-slate-600/50">
<div className="flex">
<button
onClick={() => setBillingInterval('month')}
className={`px-3 py-1.5 rounded text-xs font-medium transition-all ${
billingInterval === 'month'
? 'bg-blue-600 text-white'
: 'text-slate-400 hover:text-white'
}`}
>
Monthly
</button>
<button
onClick={() => setBillingInterval('year')}
className={`px-3 py-1.5 rounded text-xs font-medium transition-all relative ${
billingInterval === 'year'
? 'bg-blue-600 text-white'
: 'text-slate-400 hover:text-white'
}`}
>
Annual
<span className="absolute -top-3 -right-1 bg-green-500 text-white text-xs px-1 py-0.5 rounded-full">
17%
</span>
</button>
</div>
</div>
</div>
</div>
</div>
)
}
]
const handleNext = async () => {
// If we're on the plan selection step (final step)
if (currentStep === 2) {
if (selectedPlan === 'free') {
await handleComplete()
} else {
await handlePlanPurchase()
}
return
}
// Otherwise, just move to next step
if (currentStep < tourSteps.length - 1) {
const nextStep = currentStep + 1
setCurrentStep(nextStep)
// Track step view
const newViewedSteps = new Set(viewedSteps)
newViewedSteps.add(nextStep)
setViewedSteps(newViewedSteps)
await trackStepProgress(nextStep, Array.from(newViewedSteps))
}
}
const handlePlanPurchase = async () => {
const selectedTier = pricingTiersOrdered.find(tier => tier.id === selectedPlan)
if (!selectedTier) return
setIsProcessingPayment(true)
try {
// Track tour completion BEFORE redirecting to Stripe
const totalTime = tourStartTime ? Math.round((Date.now() - tourStartTime) / 1000) : 0
// Get the current session for authentication
const { data: { session } } = await supabase.auth.getSession()
if (!session) {
throw new Error('No active session')
}
// Track tour completion first
const trackingResponse = await fetch('/api/profile/tour-tracking', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${session.access_token}`
},
body: JSON.stringify({
action: 'complete',
plan: selectedPlan,
billing: billingInterval,
totalTimeSeconds: totalTime,
tourVersion
})
})
if (!trackingResponse.ok) {
console.error('Failed to track tour completion before payment')
}
const pricing = getPriceForInterval(selectedTier, billingInterval)
const response = await fetch('/api/create-checkout-session', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${session.access_token}`,
},
body: JSON.stringify({
priceId: pricing.priceId,
}),
})
if (!response.ok) {
const errorData = await response.json()
throw new Error(errorData.error || 'Failed to create checkout session')
}
const { sessionId } = await response.json()
// Redirect to Stripe Checkout using the session ID
const stripe = await getStripe()
if (stripe) {
const { error } = await stripe.redirectToCheckout({ sessionId })
if (error) {
throw error
}
}
} catch (error) {
console.error('Error creating checkout session:', error)
setIsProcessingPayment(false)
}
}
const handlePrevious = () => {
if (currentStep > 0) {
setCurrentStep(currentStep - 1)
}
}
const trackStepProgress = async (stepIndex: number, stepsViewed: number[]) => {
try {
const { data: { session } } = await supabase.auth.getSession()
if (session) {
await fetch('/api/profile/tour-tracking', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${session.access_token}`
},
body: JSON.stringify({
action: 'step',
stepIndex,
stepsViewed,
tourVersion
})
})
}
} catch (error) {
console.error('Error tracking step progress:', error)
}
}
const trackVideoWatched = async () => {
try {
const { data: { session } } = await supabase.auth.getSession()
if (session) {
await fetch('/api/profile/tour-tracking', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${session.access_token}`
},
body: JSON.stringify({
action: 'video_watched',
tourVersion
})
})
}
} catch (error) {
console.error('Error tracking video watched:', error)
}
}
const trackPlanSelection = async (plan: string, billing: BillingInterval) => {
try {
const { data: { session } } = await supabase.auth.getSession()
if (session) {
await fetch('/api/profile/tour-tracking', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${session.access_token}`
},
body: JSON.stringify({
action: 'plan_selected',
plan,
billing,
tourVersion
})
})
}
} catch (error) {
console.error('Error tracking plan selection:', error)
}
}
const handleComplete = async () => {
try {
// Track completion with enhanced data via tour-tracking API
const totalTime = tourStartTime ? Math.round((Date.now() - tourStartTime) / 1000) : 0
const { data: { session } } = await supabase.auth.getSession()
if (session) {
// This API call sets welcome_tour_completed: true and all analytics data
const response = await fetch('/api/profile/tour-tracking', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${session.access_token}`
},
body: JSON.stringify({
action: 'complete',
plan: selectedPlan,
billing: billingInterval,
totalTimeSeconds: totalTime,
tourVersion
})
})
if (!response.ok) {
throw new Error('Failed to track tour completion')
}
}
// Call the hook's completion handler (which will refetch the profile)
await onComplete()
} catch (error) {
console.error('Error completing tour:', error)
}
}
const currentStepData = tourSteps[currentStep]
if (!isOpen) return null
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<div className="fixed inset-0 bg-black/85 backdrop-blur-sm" onClick={onClose} />
<motion.div
className="relative bg-slate-900 rounded-xl border border-slate-700 max-w-2xl w-full h-[600px] flex flex-col shadow-2xl z-10"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.2 }}
>
{/* Header */}
<div className="flex flex-col p-6 border-b border-slate-700 flex-shrink-0">
{/* Title, Progress, and Close */}
<div className="flex items-center justify-between mb-2">
<div className="flex items-center space-x-3">
<div className="flex space-x-1">
{tourSteps.map((_, index) => (
<div
key={index}
className={`w-2 h-2 rounded-full transition-colors ${
index <= currentStep ? 'bg-blue-500' : 'bg-slate-600'
}`}
/>
))}
</div>
<span className="text-xs text-slate-500">
{currentStep + 1}/{tourSteps.length}
</span>
<div className="flex flex-col">
<h2 className="text-xl font-bold text-white">
{currentStepData.title}
</h2>
<p className="text-slate-400 text-sm">
{currentStepData.description}
</p>
</div>
</div>
<button
onClick={onClose}
className="p-1 text-slate-500 hover:text-white transition-colors"
>
<XMarkIcon className="h-4 w-4" />
</button>
</div>
</div>
{/* Content - Scrollable */}
<div className="flex-1 overflow-y-auto overflow-x-hidden">
<div className="p-6">
<AnimatePresence mode="wait">
<motion.div
key={currentStep}
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.2 }}
>
{currentStepData.content}
</motion.div>
</AnimatePresence>
</div>
</div>
{/* Footer - Fixed at bottom */}
<div className="flex items-center justify-between p-6 border-t border-slate-700 flex-shrink-0">
<button
onClick={handlePrevious}
disabled={currentStep === 0}
className="flex items-center space-x-1 px-3 py-2 text-slate-400 hover:text-white transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
<ChevronLeftIcon className="h-4 w-4" />
<span className="text-sm">Back</span>
</button>
<div className="flex items-center space-x-3">
<button
onClick={onClose}
className="px-3 py-2 text-slate-500 hover:text-white transition-colors text-sm"
>
Skip
</button>
{/* Final step (plan selection) has special button logic */}
{currentStep === 2 ? (
<button
onClick={handleNext}
disabled={isProcessingPayment}
className={`flex items-center space-x-1 px-4 py-2 rounded-lg transition-colors text-sm disabled:opacity-50 disabled:cursor-not-allowed ${
selectedPlan === 'free'
? 'bg-green-600 hover:bg-green-700 text-white'
: 'bg-blue-600 hover:bg-blue-700 text-white'
}`}
>
{isProcessingPayment ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-2 border-white border-t-transparent" />
<span>Processing...</span>
</>
) : (
<>
<span>{selectedPlan === 'free' ? 'Get Started Free' : 'Continue to Payment'}</span>
{selectedPlan === 'free' ? (
<RocketLaunchIcon className="h-4 w-4" />
) : (
<ChevronRightIcon className="h-4 w-4" />
)}
</>
)}
</button>
) : (
<button
onClick={handleNext}
className="flex items-center space-x-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors text-sm"
>
<span>Next</span>
<ChevronRightIcon className="h-4 w-4" />
</button>
)}
</div>
</div>
</motion.div>
</div>
)
}