'use client' import { useState } from 'react' import { useRouter } from 'next/navigation' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Textarea } from '@/components/ui/textarea' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Label } from '@/components/ui/label' import { Skeleton } from '@/components/ui/skeleton' import { Alert, AlertDescription } from '@/components/ui/alert' import { ArrowRight, Globe, Loader2, Sparkles, AlertCircle, ArrowLeft } from 'lucide-react' import type { EnhancedProductAnalysis, Keyword } from '@/lib/types' import { useMutation, useQuery } from 'convex/react' import { api } from '@/convex/_generated/api' import { AnalysisTimeline } from '@/components/analysis-timeline' const examples = [ { name: 'Notion', url: 'https://notion.so' }, { name: 'Stripe', url: 'https://stripe.com' }, { name: 'Figma', url: 'https://figma.com' }, { name: 'Linear', url: 'https://linear.app' }, ] export default function OnboardingPage() { const router = useRouter() const addDataSource = useMutation(api.dataSources.addDataSource) const updateDataSourceStatus = useMutation(api.dataSources.updateDataSourceStatus) const createAnalysis = useMutation(api.analyses.createAnalysis) const createAnalysisJob = useMutation(api.analysisJobs.create) const [url, setUrl] = useState('') const [loading, setLoading] = useState(false) const [progress, setProgress] = useState('') const [error, setError] = useState('') const [showManualInput, setShowManualInput] = useState(false) // Manual input fields const [manualProductName, setManualProductName] = useState('') const [manualDescription, setManualDescription] = useState('') const [manualFeatures, setManualFeatures] = useState('') const [pendingSourceId, setPendingSourceId] = useState(null) const [pendingProjectId, setPendingProjectId] = useState(null) const [pendingJobId, setPendingJobId] = useState(null) const analysisJob = useQuery( api.analysisJobs.getById, pendingJobId ? { jobId: pendingJobId as any } : "skip" ) const persistAnalysis = async ({ analysis, sourceUrl, sourceName, projectId, dataSourceId, }: { analysis: EnhancedProductAnalysis sourceUrl: string sourceName: string projectId?: string dataSourceId?: string }) => { const resolved = projectId && dataSourceId ? { projectId, sourceId: dataSourceId } : await addDataSource({ url: sourceUrl, name: sourceName, type: 'website', }) try { await createAnalysis({ projectId: resolved.projectId, dataSourceId: resolved.sourceId, analysis, }) await updateDataSourceStatus({ dataSourceId: resolved.sourceId, analysisStatus: 'completed', lastAnalyzedAt: Date.now(), }) } catch (err: any) { await updateDataSourceStatus({ dataSourceId: resolved.sourceId, analysisStatus: 'failed', lastError: err?.message || 'Failed to save analysis', lastAnalyzedAt: Date.now(), }) throw err } } async function analyzeWebsite() { if (!url) return setLoading(true) setError('') setProgress('Scraping website...') let manualFallback = false try { const { sourceId, projectId } = await addDataSource({ url, name: url.replace(/^https?:\/\//, '').replace(/\/$/, ''), type: 'website', }) await updateDataSourceStatus({ dataSourceId: sourceId, analysisStatus: 'pending', lastError: undefined, lastAnalyzedAt: undefined, }) setPendingSourceId(sourceId) setPendingProjectId(projectId) const jobId = await createAnalysisJob({ projectId, dataSourceId: sourceId, }) setPendingJobId(jobId) const response = await fetch('/api/analyze', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url, jobId }), }) if (response.redirected) { router.push('/auth?next=/onboarding') return } const data = await response.json() if (!response.ok) { if (data.needsManualInput) { setShowManualInput(true) setManualProductName(url.replace(/^https?:\/\//, '').replace(/\/$/, '')) manualFallback = true throw new Error(data.error) } throw new Error(data.error || 'Failed to analyze') } setProgress('Analyzing with AI...') // Store in localStorage for dashboard localStorage.setItem('productAnalysis', JSON.stringify(data.data)) localStorage.setItem('analysisStats', JSON.stringify(data.stats)) setProgress('Saving analysis...') await persistAnalysis({ analysis: data.data, sourceUrl: url, sourceName: data.data.productName, projectId, dataSourceId: sourceId, }) setPendingSourceId(null) setPendingProjectId(null) setPendingJobId(null) setProgress('Redirecting to dashboard...') // Redirect to dashboard with product name in query const params = new URLSearchParams({ product: data.data.productName }) router.push(`/dashboard?${params.toString()}`) } catch (err: any) { console.error('Analysis error:', err) setError(err.message || 'Failed to analyze website') if (pendingSourceId && !manualFallback) { await updateDataSourceStatus({ dataSourceId: pendingSourceId, analysisStatus: 'failed', lastError: err?.message || 'Failed to analyze', lastAnalyzedAt: Date.now(), }) } } finally { setLoading(false) } } async function analyzeManually() { if (!manualProductName || !manualDescription) return setLoading(true) setError('') setProgress('Analyzing with AI...') try { // Create a mock analysis from manual input const manualFeaturesList = manualFeatures .split('\n') .map((feature) => feature.trim()) .filter(Boolean) const keywordSeed = manualProductName .toLowerCase() .split(' ') .filter(Boolean) const manualKeywords: Keyword[] = keywordSeed.map((term) => ({ term, type: 'product', searchVolume: 'low', intent: 'informational', funnel: 'awareness', emotionalIntensity: 'curious', })) const manualAnalysis: EnhancedProductAnalysis = { productName: manualProductName, tagline: manualDescription.split('.')[0], description: manualDescription, category: '', positioning: '', features: manualFeaturesList.map((name) => ({ name, description: '', benefits: [], useCases: [], })), problemsSolved: [], personas: [], keywords: manualKeywords, useCases: [], competitors: [], dorkQueries: [], scrapedAt: new Date().toISOString(), analysisVersion: 'manual', } // Send to API to enhance with AI let resolvedProjectId = pendingProjectId let resolvedSourceId = pendingSourceId let resolvedJobId = pendingJobId if (!resolvedProjectId || !resolvedSourceId) { const created = await addDataSource({ url: `manual:${manualProductName}`, name: manualProductName, type: 'website', }) resolvedProjectId = created.projectId resolvedSourceId = created.sourceId setPendingProjectId(created.projectId) setPendingSourceId(created.sourceId) } if (!resolvedJobId && resolvedProjectId && resolvedSourceId) { resolvedJobId = await createAnalysisJob({ projectId: resolvedProjectId, dataSourceId: resolvedSourceId, }) setPendingJobId(resolvedJobId) } const response = await fetch('/api/analyze-manual', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ productName: manualProductName, description: manualDescription, features: manualFeatures, jobId: resolvedJobId || undefined, }), }) if (response.redirected) { router.push('/auth?next=/onboarding') return } let finalAnalysis = manualAnalysis if (response.ok) { const data = await response.json() finalAnalysis = data.data } // Store in localStorage for dashboard localStorage.setItem('productAnalysis', JSON.stringify(finalAnalysis)) localStorage.setItem('analysisStats', JSON.stringify({ features: finalAnalysis.features.length, keywords: finalAnalysis.keywords.length, personas: finalAnalysis.personas.length, useCases: finalAnalysis.useCases.length, competitors: finalAnalysis.competitors.length, dorkQueries: finalAnalysis.dorkQueries.length })) setProgress('Saving analysis...') const manualSourceUrl = pendingSourceId ? 'manual-input' : `manual:${finalAnalysis.productName}` await persistAnalysis({ analysis: finalAnalysis, sourceUrl: manualSourceUrl, sourceName: finalAnalysis.productName, projectId: resolvedProjectId || undefined, dataSourceId: resolvedSourceId || undefined, }) setPendingSourceId(null) setPendingProjectId(null) setPendingJobId(null) // Redirect to dashboard const params = new URLSearchParams({ product: finalAnalysis.productName }) router.push(`/dashboard?${params.toString()}`) } catch (err: any) { console.error('Manual analysis error:', err) setError(err.message || 'Failed to analyze') if (pendingSourceId) { await updateDataSourceStatus({ dataSourceId: pendingSourceId, analysisStatus: 'failed', lastError: err?.message || 'Failed to analyze', lastAnalyzedAt: Date.now(), }) } } finally { setLoading(false) } } if (showManualInput) { return (
{/* Left Side - Content */}

Couldn't Reach Website

No problem! Tell us about your product and we'll analyze it manually.

{error && ( {error} )} Describe Your Product Enter your product details and we'll extract the key information.
setManualProductName(e.target.value)} />