← back to Goodquestion
src/app/page.tsx
491 lines
'use client'
import { useState } from 'react'
import { Search } from 'lucide-react'
import { ScoreCard } from '@/components/swot/ScoreCard'
import { SWOTChart } from '@/components/swot/SWOTChart'
import { HistoricalContext } from '@/components/historical/HistoricalContext'
import { CompetitorMap } from '@/components/market/CompetitorMap'
import { MarketSizeChart } from '@/components/market/MarketSizeChart'
import { IncomeProjections } from '@/components/market/IncomeProjections'
import { MindMap } from '@/components/mindmap/MindMap'
import { ActionPlan } from '@/components/action-plan/ActionPlan'
import { SearchForm, SearchFormData } from '@/components/search/SearchForm'
import { StartupCapitalBreakdown } from '@/components/financial/StartupCapitalBreakdown'
import { LoadingTicker } from '@/components/LoadingTicker'
// Mock data for demonstration - replace with actual API calls
import { mockSWOTData, mockSaaSSWOTData } from '@/lib/mock-data'
export default function Home() {
const [selectedExample, setSelectedExample] = useState<'coffee' | 'saas'>('coffee')
const [analysisData, setAnalysisData] = useState(mockSWOTData)
const [isAnalyzing, setIsAnalyzing] = useState(false)
const [isComplete, setIsComplete] = useState(false)
const [hasSearched, setHasSearched] = useState(false)
const [opportunities, setOpportunities] = useState<any[]>([])
const [showOpportunities, setShowOpportunities] = useState(false)
// Section loading states
const [loadingSections, setLoadingSections] = useState<Record<string, boolean>>({})
const [loadedSections, setLoadedSections] = useState<Record<string, boolean>>({})
const [currentSearchData, setCurrentSearchData] = useState<SearchFormData | null>(null)
const handleExampleSwitch = (example: 'coffee' | 'saas') => {
setSelectedExample(example)
setAnalysisData(example === 'coffee' ? mockSWOTData : mockSaaSSWOTData)
setHasSearched(true)
setLoadedSections({})
}
const loadSection = async (section: 'historical' | 'startup' | 'market' | 'action') => {
if (!currentSearchData) return
setLoadingSections(prev => ({ ...prev, [section]: true }))
try {
const response = await fetch('/api/sections', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
section,
businessType: currentSearchData.businessType,
location: currentSearchData.location,
radiusMiles: currentSearchData.radiusMiles,
}),
})
if (!response.ok) {
throw new Error(`Failed to load ${section} section`)
}
const result = await response.json()
if (result.success && result.data) {
setAnalysisData(prev => ({
...prev,
[section === 'historical' ? 'historicalContext' :
section === 'startup' ? 'startupCosts' :
section === 'market' ? 'marketAnalysis' : 'actionPlan']: result.data
}))
setLoadedSections(prev => ({ ...prev, [section]: true }))
}
} catch (error) {
console.error(`Error loading ${section}:`, error)
alert(`Failed to load ${section} section`)
} finally {
setLoadingSections(prev => ({ ...prev, [section]: false }))
}
}
const handleSearch = async (searchData: SearchFormData) => {
setIsAnalyzing(true)
setIsComplete(false)
setShowOpportunities(false)
setCurrentSearchData(searchData)
setLoadedSections({}) // Reset loaded sections
try {
// If searchMode is opportunities, call opportunities API
if (searchData.searchMode === 'opportunities') {
const response = await fetch('/api/opportunities', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
location: searchData.location,
capitalOnHand: searchData.capitalOnHand,
}),
})
if (!response.ok) {
throw new Error(`API error: ${response.statusText}`)
}
const result = await response.json()
if (result.success && result.data) {
setOpportunities(result.data.opportunities || [])
setShowOpportunities(true)
setIsComplete(true)
setHasSearched(false) // Don't show SWOT analysis
setTimeout(() => {
setIsAnalyzing(false)
setIsComplete(false)
}, 1500)
} else {
throw new Error(result.error || 'Failed to generate opportunities')
}
return
}
// Call the API to generate analysis
const response = await fetch('/api/analyze', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
businessName: `${searchData.businessType} in ${searchData.location}`,
businessType: searchData.businessType,
location: searchData.location,
radiusMiles: searchData.radiusMiles,
}),
})
if (!response.ok) {
throw new Error(`API error: ${response.statusText}`)
}
const result = await response.json()
if (result.success && result.data) {
// Use real API data for all sections
setAnalysisData({
swotAnalysis: result.data.swotAnalysis || mockSWOTData.swotAnalysis,
historicalContext: result.data.historicalContext || mockSWOTData.historicalContext,
startupCosts: result.data.startupCosts || mockSWOTData.startupCosts,
marketAnalysis: result.data.marketAnalysis || mockSWOTData.marketAnalysis,
actionPlan: result.data.actionPlan || mockSWOTData.actionPlan,
})
} else {
throw new Error(result.error || 'Analysis failed')
}
// Show completion state with green checkmark
setIsComplete(true)
setHasSearched(true)
setShowOpportunities(false)
// Hide loader after showing success for 1.5 seconds
setTimeout(() => {
setIsAnalyzing(false)
setIsComplete(false)
}, 1500)
} catch (error) {
console.error('Analysis error:', error)
alert(`Failed to generate analysis: ${error instanceof Error ? error.message : 'Unknown error'}`)
setIsAnalyzing(false)
setIsComplete(false)
}
}
const { swotAnalysis, historicalContext, marketAnalysis, startupCosts, actionPlan } = analysisData
return (
<div>
{/* Loading Ticker Overlay */}
{isAnalyzing && <LoadingTicker isComplete={isComplete} />}
<main className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100">
{/* Header */}
<header className="bg-white shadow-sm border-b">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
<h1 className="text-4xl font-bold text-gray-900">
SWOT Analysis Platform
</h1>
<p className="mt-2 text-lg text-gray-600">
AI-Powered Local Business Viability Analysis
</p>
</div>
</header>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Example Selector */}
<section className="mb-8">
<div className="bg-white rounded-xl shadow-md p-6 border-2 border-gray-200">
<h3 className="text-lg font-semibold text-gray-900 mb-3">View Example SWOT Analysis:</h3>
<div className="flex gap-4">
<button
onClick={() => handleExampleSwitch('coffee')}
className={`flex-1 py-3 px-6 rounded-lg font-semibold transition-all duration-200 ${
selectedExample === 'coffee'
? 'bg-gradient-to-r from-amber-600 to-orange-600 text-white shadow-lg'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
Coffee Shop Example
</button>
<button
onClick={() => handleExampleSwitch('saas')}
className={`flex-1 py-3 px-6 rounded-lg font-semibold transition-all duration-200 ${
selectedExample === 'saas'
? 'bg-gradient-to-r from-blue-600 to-indigo-600 text-white shadow-lg'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
SaaS Application Example
</button>
</div>
</div>
</section>
{/* Search Form Section - Always visible */}
<section className="mb-12">
<SearchForm onSearch={handleSearch} isLoading={isAnalyzing} />
</section>
{/* Business Opportunities List */}
{showOpportunities && opportunities.length > 0 && (
<section className="mb-12">
<div className="bg-gradient-to-r from-green-600 to-emerald-600 text-white rounded-xl p-8 mb-6">
<h2 className="text-3xl font-bold mb-2">Top Business Opportunities</h2>
<p className="text-green-100">Realistic opportunities based on local market data and actual startup costs</p>
</div>
<div className="grid gap-6">
{opportunities.map((opp, index) => (
<div
key={opp.id}
className="bg-white rounded-xl shadow-lg p-6 border-2 border-gray-200 hover:border-green-500 transition-all duration-200"
>
<div className="flex items-start gap-4">
<div className="flex-shrink-0">
<div className="w-16 h-16 bg-gradient-to-br from-green-500 to-emerald-500 rounded-full flex items-center justify-center text-white font-bold text-2xl">
#{index + 1}
</div>
</div>
<div className="flex-1">
<div className="flex items-start justify-between mb-3">
<div>
<h3 className="text-2xl font-bold text-gray-900 mb-1">{opp.businessType}</h3>
<p className="text-gray-600">{opp.description}</p>
</div>
<div className="ml-4 text-right">
<div className="inline-block bg-green-100 text-green-800 px-4 py-2 rounded-lg font-bold text-lg">
{opp.viabilityScore}%
</div>
<p className="text-xs text-gray-500 mt-1">Viability</p>
</div>
</div>
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-4">
<p className="text-sm text-blue-900"><strong>Why it works:</strong> {opp.reasoning}</p>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-4">
<div className="bg-gray-50 rounded-lg p-3">
<p className="text-xs text-gray-600 mb-1">Startup Cost</p>
<p className="font-bold text-gray-900">${(opp.estimatedStartupCost || 0).toLocaleString()}</p>
</div>
<div className="bg-gray-50 rounded-lg p-3">
<p className="text-xs text-gray-600 mb-1">Monthly Revenue</p>
<p className="font-bold text-green-700">${(opp.estimatedMonthlyRevenue || 0).toLocaleString()}/mo</p>
</div>
<div className="bg-gray-50 rounded-lg p-3">
<p className="text-xs text-gray-600 mb-1">Competition</p>
<p className={`font-bold ${opp.competitionLevel === 'low' ? 'text-green-600' : opp.competitionLevel === 'medium' ? 'text-yellow-600' : 'text-red-600'}`}>
{opp.competitionLevel || 'Medium'}
</p>
</div>
<div className="bg-gray-50 rounded-lg p-3">
<p className="text-xs text-gray-600 mb-1">Time to Profit</p>
<p className="font-bold text-gray-900">{opp.timeToProfit || 'N/A'}</p>
</div>
</div>
<div className="flex flex-wrap gap-2">
{(opp.keyStrengths || []).map((strength: string, idx: number) => (
<span key={idx} className="inline-block bg-green-100 text-green-800 px-3 py-1 rounded-full text-sm font-medium">
✓ {strength}
</span>
))}
</div>
<div className="mt-4 pt-4 border-t border-gray-200">
<button
onClick={() => {
handleSearch({
location: opportunities[0]?.location || '',
businessType: opp.businessType,
radiusMiles: 10,
searchMode: 'single',
})
}}
className="w-full bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 text-white font-semibold py-3 px-6 rounded-lg transition-all duration-200 flex items-center justify-center gap-2"
>
<Search className="h-5 w-5" />
Analyze This Opportunity
</button>
</div>
</div>
</div>
</div>
))}
</div>
</section>
)}
{/* Analysis Results */}
{hasSearched && (
<>
{/* The Good Question Section */}
<section className="mb-12">
<ScoreCard analysis={swotAnalysis} />
</section>
{/* Full SWOT Analysis */}
<section className="mb-12">
<h2 className="text-3xl font-bold mb-6">Detailed SWOT Analysis</h2>
<SWOTChart
strengths={swotAnalysis.strengths}
weaknesses={swotAnalysis.weaknesses}
opportunities={swotAnalysis.opportunities}
threats={swotAnalysis.threats}
/>
</section>
{/* Historical Context */}
<section className="mb-12">
<div className="flex items-center justify-between mb-6">
<h2 className="text-3xl font-bold">Historical & Trajectory Context</h2>
{!loadedSections.historical && !loadingSections.historical && (
<button
onClick={() => loadSection('historical')}
className="bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-6 rounded-lg transition-all duration-200"
>
Load Details
</button>
)}
</div>
{loadingSections.historical && (
<div className="bg-white rounded-xl shadow-lg p-8 text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-600">Loading historical context...</p>
</div>
)}
{loadedSections.historical && historicalContext && (
<HistoricalContext context={historicalContext} />
)}
{!loadedSections.historical && !loadingSections.historical && (
<div className="bg-white rounded-xl shadow-lg p-8 border-2 border-dashed border-gray-300">
<p className="text-gray-600 text-center">Click "Load Details" to view historical context and market trajectory</p>
</div>
)}
</section>
{/* Startup Capital Breakdown */}
<section className="mb-12">
<div className="flex items-center justify-between mb-6">
<h2 className="text-3xl font-bold">Startup Capital Required</h2>
{!loadedSections.startup && !loadingSections.startup && (
<button
onClick={() => loadSection('startup')}
className="bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-6 rounded-lg transition-all duration-200"
>
Load Details
</button>
)}
</div>
{loadingSections.startup && (
<div className="bg-white rounded-xl shadow-lg p-8 text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-600">Loading startup costs...</p>
</div>
)}
{loadedSections.startup && startupCosts && (
<StartupCapitalBreakdown startupCosts={startupCosts} />
)}
{!loadedSections.startup && !loadingSections.startup && (
<div className="bg-white rounded-xl shadow-lg p-8 border-2 border-dashed border-gray-300">
<p className="text-gray-600 text-center">Click "Load Details" to view detailed startup cost breakdown</p>
</div>
)}
</section>
{/* Market Analysis */}
<section className="mb-12">
<div className="flex items-center justify-between mb-6">
<h2 className="text-3xl font-bold">Market Deep Dive</h2>
{!loadedSections.market && !loadingSections.market && (
<button
onClick={() => loadSection('market')}
className="bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-6 rounded-lg transition-all duration-200"
>
Load Details
</button>
)}
</div>
{loadingSections.market && (
<div className="bg-white rounded-xl shadow-lg p-8 text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-600">Loading market analysis...</p>
</div>
)}
{loadedSections.market && marketAnalysis && (
<div className="space-y-6">
<CompetitorMap
competitors={marketAnalysis.competitors}
businessType={swotAnalysis.businessType}
/>
<MarketSizeChart
marketSize={marketAnalysis.marketSize}
demographics={marketAnalysis.demographics}
/>
<IncomeProjections projections={marketAnalysis.incomeProjections} />
</div>
)}
{!loadedSections.market && !loadingSections.market && (
<div className="bg-white rounded-xl shadow-lg p-8 border-2 border-dashed border-gray-300">
<p className="text-gray-600 text-center">Click "Load Details" to view competitor analysis, market size, and income projections</p>
</div>
)}
</section>
{/* Mind Map */}
<section className="mb-12">
<h2 className="text-3xl font-bold mb-6">System Architecture</h2>
<MindMap />
</section>
{/* Action Plan */}
<section className="mb-12">
<div className="flex items-center justify-between mb-6">
<h2 className="text-3xl font-bold">Implementation Roadmap</h2>
{!loadedSections.action && !loadingSections.action && (
<button
onClick={() => loadSection('action')}
className="bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-6 rounded-lg transition-all duration-200"
>
Load Details
</button>
)}
</div>
{loadingSections.action && (
<div className="bg-white rounded-xl shadow-lg p-8 text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-600">Loading action plan...</p>
</div>
)}
{loadedSections.action && actionPlan && (
<ActionPlan actionItems={actionPlan} />
)}
{!loadedSections.action && !loadingSections.action && (
<div className="bg-white rounded-xl shadow-lg p-8 border-2 border-dashed border-gray-300">
<p className="text-gray-600 text-center">Click "Load Details" to view detailed implementation roadmap</p>
</div>
)}
</section>
</>
)}
</div>
{/* Footer */}
<footer className="bg-white border-t mt-16">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="text-center text-gray-600">
<p className="text-sm">
Powered by Claude AI and Model Context Protocol (MCP)
</p>
{hasSearched && (
<p className="text-xs mt-2" suppressHydrationWarning>
Analysis generated on {new Date(swotAnalysis.dateGenerated).toLocaleDateString()}
</p>
)}
</div>
</div>
</footer>
</main>
</div>
)
}