=== Dockerfile === FROM node:18-alpine WORKDIR /app COPY package.json package-lock.json* ./ RUN npm install COPY . . RUN npm run build EXPOSE 3000 CMD ["npm", "start"] === package.json === { "name": "fireant-clone", "version": "0.1.0", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint" }, "dependencies": { "next": "14.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", "lucide-react": "^0.330.0", "recharts": "^2.12.0", "clsx": "^2.1.0", "tailwind-merge": "^2.2.1" }, "devDependencies": { "autoprefixer": "^10.4.17", "postcss": "^8.4.35", "tailwindcss": "^3.4.1", "typescript": "^5.3.3", "@types/node": "^20.11.17", "@types/react": "^18.2.55", "@types/react-dom": "^18.2.19" } } === next.config.js === /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, output: 'standalone', } module.exports = nextConfig === postcss.config.js === module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, }, } === tailwind.config.js === /** @type {import('tailwindcss').Config} */ module.exports = { content: [ "./pages/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}", ], theme: { extend: { colors: { fireant: { bg: '#0b0e11', panel: '#15191e', border: '#2a2e39', green: '#00f4b0', red: '#ff3747', yellow: '#ffd600', text: '#d1d5db', textMuted: '#6b7280', accent: '#2962ff' } }, fontSize: { xxs: '0.65rem', } }, }, plugins: [], } === styles/globals.css === @tailwind base; @tailwind components; @tailwind utilities; :root { --foreground-rgb: 255, 255, 255; --background-start-rgb: 11, 14, 17; --background-end-rgb: 11, 14, 17; } body { color: rgb(var(--foreground-rgb)); background: #0b0e11; overflow: hidden; /* App-like feel */ } /* Custom Scrollbar for the dashboard feel */ ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-track { background: #15191e; } ::-webkit-scrollbar-thumb { background: #2a2e39; border-radius: 3px; } ::-webkit-scrollbar-thumb:hover { background: #4b5563; } .text-up { color: #00f4b0; } .text-down { color: #ff3747; } .text-ref { color: #ffd600; } === components/Sidebar.jsx === import React from 'react'; import { LayoutDashboard, LineChart, Newspaper, Settings, User, Bell, Search, Briefcase, TrendingUp } from 'lucide-react'; const Sidebar = () => { const navItems = [ { icon: , label: 'Dashboard', active: true }, { icon: , label: 'Market' }, { icon: , label: 'Portfolio' }, { icon: , label: 'Technical' }, { icon: , label: 'News' }, { icon: , label: 'Alerts' }, ]; return (
FA
{navItems.map((item, index) => ( ))}
); }; export default Sidebar; === components/Header.jsx === import React from 'react'; import { Search, Menu, Bell, Download } from 'lucide-react'; const Header = () => { return (
VNINDEX 1,250.35 +12.5 (+1.01%)
VN30 1,260.10 -2.1 (-0.15%)
); }; export default Header; === components/ChartWidget.jsx === import React from 'react'; import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; const data = [ { time: '09:00', price: 85.2 }, { time: '09:30', price: 85.8 }, { time: '10:00', price: 86.1 }, { time: '10:30', price: 85.9 }, { time: '11:00', price: 86.5 }, { time: '11:30', price: 86.8 }, { time: '13:00', price: 87.2 }, { time: '13:30', price: 87.0 }, { time: '14:00', price: 87.5 }, { time: '14:30', price: 87.9 }, ]; const ChartWidget = ({ symbol = "FPT" }) => { return (
{symbol} Daily Chart
{['1D', '1W', '1M', '3M', '1Y'].map(t => ( ))}
O: 85.2 H: 87.9 L: 85.0 C: 87.9 Vol: 2.5M
); }; export default ChartWidget; === components/Watchlist.jsx === import React, { useState, useEffect } from 'react'; import { MoreHorizontal, Plus } from 'lucide-react'; const initialStocks = [ { symbol: 'VCB', ref: 89.5, price: 91.2, change: 1.7, pct: 1.9, vol: '1.2M' }, { symbol: 'FPT', ref: 105.0, price: 106.5, change: 1.5, pct: 1.4, vol: '2.5M' }, { symbol: 'HPG', ref: 28.0, price: 27.8, change: -0.2, pct: -0.7, vol: '15.4M' }, { symbol: 'VNM', ref: 68.5, price: 68.5, change: 0, pct: 0, vol: '800K' }, { symbol: 'TCB', ref: 35.2, price: 36.0, change: 0.8, pct: 2.2, vol: '5.1M' }, { symbol: 'SSI', ref: 34.5, price: 35.1, change: 0.6, pct: 1.7, vol: '8.2M' }, { symbol: 'MWG', ref: 45.0, price: 44.2, change: -0.8, pct: -1.7, vol: '3.4M' }, { symbol: 'STB', ref: 30.1, price: 30.5, change: 0.4, pct: 1.3, vol: '12.1M' }, { symbol: 'VPB', ref: 19.5, price: 19.6, change: 0.1, pct: 0.5, vol: '9.5M' }, { symbol: 'MSN', ref: 65.0, price: 64.5, change: -0.5, pct: -0.7, vol: '1.1M' }, ]; const Watchlist = () => { const [stocks, setStocks] = useState(initialStocks); // Simulate live data updates useEffect(() => { const interval = setInterval(() => { setStocks(current => current.map(stock => { if (Math.random() > 0.7) { const move = (Math.random() - 0.5) * 0.5; const newPrice = Number((stock.price + move).toFixed(2)); const change = Number((newPrice - stock.ref).toFixed(2)); const pct = Number(((change / stock.ref) * 100).toFixed(1)); return { ...stock, price: newPrice, change, pct }; } return stock; })); }, 2000); return () => clearInterval(interval); }, []); const getColor = (val) => { if (val > 0) return 'text-fireant-green'; if (val < 0) return 'text-fireant-red'; return 'text-fireant-yellow'; }; return (
Watchlist VN30
{stocks.map((s) => ( ))}
Symbol Price +/- % Vol
{s.symbol} {s.price.toFixed(2)} {s.change > 0 ? '+' : ''}{s.change.toFixed(2)} {s.pct > 0 ? '+' : ''}{s.pct}% {s.vol}
); }; export default Watchlist; === components/OrderBook.jsx === import React from 'react'; const OrderBook = () => { const bids = [ { price: 87.8, vol: '10.5K', w: '80%' }, { price: 87.7, vol: '5.2K', w: '40%' }, { price: 87.6, vol: '15.1K', w: '90%' }, { price: 87.5, vol: '20.0K', w: '100%' }, { price: 87.4, vol: '3.5K', w: '20%' }, ]; const asks = [ { price: 87.9, vol: '8.2K', w: '60%' }, { price: 88.0, vol: '12.4K', w: '75%' }, { price: 88.1, vol: '4.1K', w: '30%' }, { price: 88.2, vol: '9.8K', w: '65%' }, { price: 88.3, vol: '2.2K', w: '15%' }, ]; return (
Order Book
{/* Asks (Sellers) - Red */}
{asks.map((a, i) => (
{a.vol}
{a.price}
))}
87.90 Last Match
{/* Bids (Buyers) - Green */}
{bids.map((b, i) => (
{b.vol}
{b.price}
))}
); }; export default OrderBook; === components/NewsWidget.jsx === import React from 'react'; import { ExternalLink } from 'lucide-react'; const news = [ { id: 1, time: '14:30', title: 'VN-Index gains 12 points as banking stocks surge', source: 'CafeF' }, { id: 2, time: '13:15', title: 'Foreign investors net sold 500 billion VND today', source: 'Vietstock' }, { id: 3, time: '11:45', title: 'FPT reports 20% profit growth in Q1 2024', source: 'FireAnt' }, { id: 4, time: '10:20', title: 'Oil prices stabilize amidst global tensions', source: 'Reuters' }, { id: 5, time: '09:45', title: 'Morning brief: Market opens green, liquidity improves', source: 'VnEconomy' }, { id: 6, time: '09:00', title: 'State Bank continues to manage exchange rate flexibly', source: 'SBV' }, ]; const NewsWidget = () => { return (
Latest News
{news.map((item) => (
{item.time} {item.source}

{item.title}

))}
); }; export default NewsWidget; === components/Layout.jsx === import React from 'react'; import Sidebar from './Sidebar'; import Header from './Header'; const Layout = ({ children }) => { return (
{children}
); }; export default Layout; === pages/index.js === import React from 'react'; import Layout from '../components/Layout'; import Watchlist from '../components/Watchlist'; import ChartWidget from '../components/ChartWidget'; import OrderBook from '../components/OrderBook'; import NewsWidget from '../components/NewsWidget'; export default function Dashboard() { return ( {/* Main Grid Layout */}
{/* Left Column: Watchlist (Takes 3 cols, full height) */}
{/* Middle Column: Chart & Order Book */}
{/* Chart takes upper 2/3 */}
{/* Order Book takes lower 1/3 */}
{/* Another widget placeholder (e.g., Foreign Flow) */}
Foreign Flow
Net Buy
+45.2B
{/* Right Column: News & Events (Hidden on smaller tablets, visible on large screens) */}
Market Movers
VCB +2.5%
BID +1.8%
VHM -1.2%
); } === pages/_app.js === import '../styles/globals.css'; import Head from 'next/head'; function MyApp({ Component, pageProps }) { return ( <> FireAnt Dashboard Clone ); } export default MyApp; === pages/api/hello.js === // Next.js API route support: https://nextjs.org/docs/api-routes/introduction export default function handler(req, res) { res.status(200).json({ name: 'John Doe' }) }