Initial project structure: MarketScanner - Fear-to-Fortune Trading Intelligence
Features: - FastAPI backend with stocks, news, signals, watchlist, analytics endpoints - React frontend with TailwindCSS dark mode trading dashboard - Celery workers for news fetching, sentiment analysis, pattern detection - TimescaleDB schema for time-series stock data - Docker Compose setup for all services - OpenAI integration for sentiment analysis
This commit is contained in:
172
frontend/src/components/Layout.tsx
Normal file
172
frontend/src/components/Layout.tsx
Normal file
@@ -0,0 +1,172 @@
|
||||
import { ReactNode, useState } from 'react'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
import { motion } from 'framer-motion'
|
||||
import {
|
||||
HomeIcon,
|
||||
BellAlertIcon,
|
||||
ChartBarIcon,
|
||||
NewspaperIcon,
|
||||
StarIcon,
|
||||
ChartPieIcon,
|
||||
Bars3Icon,
|
||||
XMarkIcon,
|
||||
} from '@heroicons/react/24/outline'
|
||||
import clsx from 'clsx'
|
||||
|
||||
interface LayoutProps {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
const navigation = [
|
||||
{ name: 'Dashboard', href: '/', icon: HomeIcon },
|
||||
{ name: 'Buy Signals', href: '/signals', icon: BellAlertIcon },
|
||||
{ name: 'Stocks', href: '/stocks', icon: ChartBarIcon },
|
||||
{ name: 'News', href: '/news', icon: NewspaperIcon },
|
||||
{ name: 'Watchlist', href: '/watchlist', icon: StarIcon },
|
||||
{ name: 'Analytics', href: '/analytics', icon: ChartPieIcon },
|
||||
]
|
||||
|
||||
export default function Layout({ children }: LayoutProps) {
|
||||
const location = useLocation()
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-gray-950 via-gray-900 to-gray-950">
|
||||
{/* Mobile sidebar backdrop */}
|
||||
{sidebarOpen && (
|
||||
<div
|
||||
className="fixed inset-0 z-40 bg-black/50 backdrop-blur-sm lg:hidden"
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Sidebar */}
|
||||
<aside
|
||||
className={clsx(
|
||||
'fixed inset-y-0 left-0 z-50 w-64 bg-gray-900/95 backdrop-blur-xl border-r border-gray-800 transform transition-transform duration-300 lg:translate-x-0',
|
||||
sidebarOpen ? 'translate-x-0' : '-translate-x-full'
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col h-full">
|
||||
{/* Logo */}
|
||||
<div className="flex items-center justify-between h-16 px-4 border-b border-gray-800">
|
||||
<Link to="/" className="flex items-center space-x-3">
|
||||
<div className="w-10 h-10 bg-gradient-to-br from-green-500 to-emerald-600 rounded-xl flex items-center justify-center">
|
||||
<ChartBarIcon className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-bold text-lg gradient-text">MarketScanner</span>
|
||||
<p className="text-xs text-gray-500">Buy the Fear</p>
|
||||
</div>
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
className="lg:hidden p-1 rounded-lg hover:bg-gray-800"
|
||||
>
|
||||
<XMarkIcon className="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Navigation */}
|
||||
<nav className="flex-1 px-3 py-4 space-y-1 overflow-y-auto">
|
||||
{navigation.map((item) => {
|
||||
const isActive = location.pathname === item.href
|
||||
return (
|
||||
<Link
|
||||
key={item.name}
|
||||
to={item.href}
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
className={clsx(
|
||||
'flex items-center px-3 py-2.5 rounded-lg transition-all duration-200',
|
||||
isActive
|
||||
? 'bg-green-500/20 text-green-400 border border-green-500/30'
|
||||
: 'text-gray-400 hover:text-white hover:bg-gray-800'
|
||||
)}
|
||||
>
|
||||
<item.icon className={clsx('w-5 h-5 mr-3', isActive && 'text-green-400')} />
|
||||
{item.name}
|
||||
{item.name === 'Buy Signals' && (
|
||||
<span className="ml-auto badge-success">3</span>
|
||||
)}
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
|
||||
{/* Bottom section */}
|
||||
<div className="p-4 border-t border-gray-800">
|
||||
<div className="card p-3">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-xs text-gray-500">Market Sentiment</span>
|
||||
<span className="badge-danger">Fear</span>
|
||||
</div>
|
||||
<div className="h-2 bg-gray-800 rounded-full overflow-hidden">
|
||||
<motion.div
|
||||
className="h-full bg-gradient-to-r from-red-500 to-orange-500"
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width: '35%' }}
|
||||
transition={{ duration: 1, ease: 'easeOut' }}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-between mt-1 text-xs text-gray-600">
|
||||
<span>Extreme Fear</span>
|
||||
<span>Greed</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{/* Main content */}
|
||||
<div className="lg:pl-64">
|
||||
{/* Top bar */}
|
||||
<header className="sticky top-0 z-30 h-16 bg-gray-900/80 backdrop-blur-xl border-b border-gray-800">
|
||||
<div className="flex items-center justify-between h-full px-4">
|
||||
<button
|
||||
onClick={() => setSidebarOpen(true)}
|
||||
className="lg:hidden p-2 rounded-lg hover:bg-gray-800"
|
||||
>
|
||||
<Bars3Icon className="w-6 h-6" />
|
||||
</button>
|
||||
|
||||
<div className="flex items-center space-x-4">
|
||||
{/* Search */}
|
||||
<div className="hidden md:block">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search stocks..."
|
||||
className="input w-64"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-4">
|
||||
{/* Market status */}
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
|
||||
<span className="text-sm text-gray-400">Market Open</span>
|
||||
</div>
|
||||
|
||||
{/* Time */}
|
||||
<div className="hidden sm:block text-sm text-gray-500">
|
||||
{new Date().toLocaleTimeString()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Page content */}
|
||||
<main className="p-4 md:p-6 lg:p-8">
|
||||
<motion.div
|
||||
key={location.pathname}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user