|
import React, { useState, useEffect } from 'react'; |
|
import Header from './components/Header'; |
|
import JobForm from './components/JobForm'; |
|
import JobStatusCard from './components/JobStatusCard'; |
|
import ResultsDisplay from './components/ResultsDisplay'; |
|
import LoadingSkeleton from './components/LoadingSkeleton'; |
|
import HistoryPanel from './components/HistoryPanel'; |
|
import { createJob, getJob } from './services/api'; |
|
import { XCircle } from 'lucide-react'; |
|
|
|
function App() { |
|
const [job, setJob] = useState(null); |
|
const [isLoading, setIsLoading] = useState(false); |
|
const [isPolling, setIsPolling] = useState(false); |
|
const [error, setError] = useState(null); |
|
|
|
const handleAnalysisRequest = async (ticker) => { |
|
setIsLoading(true); |
|
setIsPolling(true); |
|
setError(null); |
|
setJob(null); |
|
try { |
|
const response = await createJob(ticker); |
|
setJob(response.data); |
|
} catch (err) { |
|
setError('Failed to create job. Please check the API server and try again.'); |
|
setIsLoading(false); |
|
setIsPolling(false); |
|
} |
|
}; |
|
|
|
const handleSelectHistoryJob = (historyJob) => { |
|
setIsLoading(false); |
|
setIsPolling(false); |
|
setError(null); |
|
setJob(historyJob); |
|
} |
|
|
|
useEffect(() => { |
|
if (!job?.id || !isPolling) return; |
|
|
|
if (job.status !== 'PENDING') { |
|
setIsLoading(false); |
|
} |
|
|
|
const intervalId = setInterval(async () => { |
|
try { |
|
const response = await getJob(job.id); |
|
const updatedJob = response.data; |
|
setJob(updatedJob); |
|
|
|
if (updatedJob.status === 'SUCCESS' || updatedJob.status === 'FAILED') { |
|
clearInterval(intervalId); |
|
setIsPolling(false); |
|
} |
|
} catch (err) { |
|
setError('Failed to poll job status.'); |
|
clearInterval(intervalId); |
|
setIsPolling(false); |
|
} |
|
}, 3000); |
|
|
|
return () => clearInterval(intervalId); |
|
}, [job, isPolling]); |
|
|
|
return ( |
|
<div className="min-h-screen bg-gray-900 text-white font-sans"> |
|
<Header /> |
|
<HistoryPanel onSelectJob={handleSelectHistoryJob} /> |
|
|
|
<main className="container mx-auto p-4 md:p-8"> |
|
<div className="max-w-4xl mx-auto"> |
|
<p className="text-lg text-gray-400 mb-4 text-center"> |
|
Enter an Indian stock ticker to receive a comprehensive, AI-powered analysis. |
|
</p> |
|
|
|
{/* --- NEW, SIMPLE TEXT EXAMPLE LINE --- */} |
|
<p className="text-sm text-gray-500 text-center mb-8"> |
|
e.g., RELIANCE.NS, TCS.NS, INFY.NS, HDFCBANK.NS |
|
</p> |
|
{/* --- END NEW LINE --- */} |
|
|
|
<JobForm onAnalyze={handleAnalysisRequest} isLoading={isLoading || isPolling} /> |
|
|
|
{error && <div className="my-6 p-4 bg-red-900/50 rounded-lg text-red-300 text-center">{error}</div>} |
|
|
|
{isLoading && !job && <LoadingSkeleton />} |
|
|
|
{job && !isLoading && <JobStatusCard job={job} />} |
|
|
|
{job?.status === 'SUCCESS' && job.result && ( |
|
<ResultsDisplay result={job.result} /> |
|
)} |
|
|
|
{job?.status === 'FAILED' && job.result?.error && ( |
|
<div className="mt-8 p-6 bg-gray-800/30 border border-red-500/30 rounded-lg text-center animate-fade-in"> |
|
<XCircle className="w-16 h-16 text-red-400 mx-auto mb-4" /> |
|
<h2 className="text-2xl font-bold text-red-300 mb-2">Analysis Failed</h2> |
|
<p className="text-gray-400 max-w-lg mx-auto"> |
|
We couldn't complete the analysis for <strong className="text-white">{job.ticker}</strong>. |
|
This usually means the stock symbol is incorrect or not listed. |
|
</p> |
|
<p className="text-xs text-gray-500 mt-4">Please double-check the ticker and try again.</p> |
|
|
|
<details className="mt-6 text-left w-full max-w-lg mx-auto"> |
|
<summary className="cursor-pointer text-xs text-gray-500 hover:text-gray-400 focus:outline-none">Show technical details</summary> |
|
<pre className="mt-2 bg-gray-900 p-4 rounded-md text-gray-400 text-xs whitespace-pre-wrap font-mono"> |
|
{job.result.error} |
|
</pre> |
|
</details> |
|
</div> |
|
)} |
|
</div> |
|
</main> |
|
</div> |
|
); |
|
} |
|
|
|
export default App; |