Files
marketscanner/frontend/src/components/Layout.tsx
mindesbunister 074787f067 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
2026-01-08 14:15:51 +01:00

173 lines
6.1 KiB
TypeScript

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>
)
}