refactor(web): normalize secondary ux surfaces

This commit is contained in:
root 2026-05-06 03:27:52 +00:00
parent 266b367322
commit 76d326c793
7 changed files with 365 additions and 570 deletions

View File

@ -2,6 +2,7 @@ import { Link, Routes, Route, useLocation } from 'react-router-dom';
import { Sidebar } from './Sidebar'; import { Sidebar } from './Sidebar';
import { Header } from './Header'; import { Header } from './Header';
import { RightPanel } from './RightPanel'; import { RightPanel } from './RightPanel';
import { Button } from '../ui/button';
import { HomeView } from '../../views/HomeView'; import { HomeView } from '../../views/HomeView';
import { PortfolioView } from '../../views/PortfolioView'; import { PortfolioView } from '../../views/PortfolioView';
import { ResearchView } from '../../views/ResearchView'; import { ResearchView } from '../../views/ResearchView';
@ -21,40 +22,28 @@ function NotFoundView() {
style={{ style={{
minHeight: 420, minHeight: 420,
borderRadius: 24, borderRadius: 24,
border: '1px solid #E5E7EB', border: '1px solid var(--border)',
background: 'linear-gradient(135deg, #FFF7ED 0%, #EFF6FF 100%)', background: 'var(--hero-gradient)',
padding: '56px 32px', padding: '56px 32px',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
textAlign: 'center', textAlign: 'center',
color: '#111827', color: 'var(--foreground)',
}} }}
> >
<div style={{ fontSize: 12, fontWeight: 900, letterSpacing: '0.16em', color: '#2563EB', textTransform: 'uppercase' }}> <div style={{ fontSize: 12, fontWeight: 900, letterSpacing: '0.16em', color: 'var(--accent)', textTransform: 'uppercase' }}>
404 404
</div> </div>
<h1 id="not-found-title" style={{ margin: '10px 0 8px', fontSize: 34, fontWeight: 900 }}> <h1 id="not-found-title" style={{ margin: '10px 0 8px', fontSize: 34, fontWeight: 900 }}>
Route not found Route not found
</h1> </h1>
<p style={{ margin: 0, maxWidth: 520, color: '#4B5563', fontSize: 14, lineHeight: 1.6 }}> <p style={{ margin: 0, maxWidth: 520, color: 'var(--muted-foreground)', fontSize: 14, lineHeight: 1.6 }}>
No trading workspace exists at <code style={{ fontWeight: 800 }}>{location.pathname}</code>. The app is still running normally. No trading workspace exists at <code style={{ fontWeight: 800 }}>{location.pathname}</code>. The app is still running normally.
</p> </p>
<Link <Link to="/" style={{ marginTop: 24, textDecoration: 'none' }}>
to="/" <Button>Return home</Button>
style={{
marginTop: 24,
borderRadius: 999,
background: '#111827',
color: '#fff',
padding: '10px 18px',
fontSize: 13,
fontWeight: 800,
textDecoration: 'none',
}}
>
Return home
</Link> </Link>
</section> </section>
); );

View File

@ -3,6 +3,7 @@ import { ArrowRight } from 'lucide-react';
import { useAppContext } from '../../context/AppContext'; import { useAppContext } from '../../context/AppContext';
import { fetchNews, type NewsArticle as MarketNewsArticle } from '../../lib/marketApi'; import { fetchNews, type NewsArticle as MarketNewsArticle } from '../../lib/marketApi';
import { SkeletonBlock, SkeletonText } from '../Skeleton'; import { SkeletonBlock, SkeletonText } from '../Skeleton';
import { Button } from '../ui/button';
// ─── Portfolio Summary ──────────────────────────────────────────────────────── // ─── Portfolio Summary ────────────────────────────────────────────────────────
@ -25,15 +26,15 @@ function PortfolioSummary() {
<div style={{ padding: '16px 16px 12px' }}> <div style={{ padding: '16px 16px 12px' }}>
{/* Header */} {/* Header */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
<span style={{ fontSize: 13, fontWeight: 700, color: '#111827' }}>Portfolio</span> <span style={{ fontSize: 13, fontWeight: 700, color: 'var(--foreground)' }}>Portfolio</span>
<span style={{ fontSize: 12, color: '#2563EB', cursor: 'pointer', fontWeight: 500 }}> <Button variant="ghost" size="sm" className="h-8 px-2 text-xs">
View All <ArrowRight size={12} style={{ display: 'inline', verticalAlign: 'middle' }} /> View All <ArrowRight size={12} />
</span> </Button>
</div> </div>
{/* Total value */} {/* Total value */}
<div style={{ marginBottom: 12 }}> <div style={{ marginBottom: 12 }}>
<div style={{ fontSize: 20, fontWeight: 800, color: '#111827', letterSpacing: '-0.5px' }}> <div style={{ fontSize: 20, fontWeight: 800, color: 'var(--foreground)', letterSpacing: '-0.5px' }}>
{fmt$(totalValue)} {fmt$(totalValue)}
</div> </div>
<div style={{ <div style={{
@ -49,10 +50,10 @@ function PortfolioSummary() {
display: 'grid', display: 'grid',
gridTemplateColumns: '2fr 1.2fr 1fr 1.2fr', gridTemplateColumns: '2fr 1.2fr 1fr 1.2fr',
gap: 4, paddingBottom: 6, gap: 4, paddingBottom: 6,
borderBottom: '1px solid #F3F4F6', marginBottom: 4, borderBottom: '1px solid var(--border)', marginBottom: 4,
}}> }}>
{['Symbol','Price','Change','Value'].map(h => ( {['Symbol','Price','Change','Value'].map(h => (
<span key={h} style={{ fontSize: 10, color: '#9CA3AF', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.05em' }}> <span key={h} style={{ fontSize: 10, color: 'var(--muted-foreground)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.05em' }}>
{h} {h}
</span> </span>
))} ))}
@ -60,7 +61,7 @@ function PortfolioSummary() {
{/* Rows */} {/* Rows */}
{positions.length === 0 ? ( {positions.length === 0 ? (
<div style={{ fontSize: 12, color: '#9CA3AF', padding: '12px 0', textAlign: 'center' }}> <div style={{ fontSize: 12, color: 'var(--muted-foreground)', padding: '12px 0', textAlign: 'center' }}>
No open positions No open positions
</div> </div>
) : ( ) : (
@ -73,15 +74,15 @@ function PortfolioSummary() {
display: 'grid', display: 'grid',
gridTemplateColumns: '2fr 1.2fr 1fr 1.2fr', gridTemplateColumns: '2fr 1.2fr 1fr 1.2fr',
gap: 4, padding: '5px 0', gap: 4, padding: '5px 0',
borderBottom: '1px solid #F9FAFB', borderBottom: '1px solid var(--border)',
alignItems: 'center', alignItems: 'center',
}}> }}>
<span style={{ fontSize: 12, fontWeight: 700, color: '#111827' }}>{pos.symbol}</span> <span style={{ fontSize: 12, fontWeight: 700, color: 'var(--foreground)' }}>{pos.symbol}</span>
<span style={{ fontSize: 12, color: '#374151' }}>{pos.currentPrice?.toFixed(2) ?? '—'}</span> <span style={{ fontSize: 12, color: 'var(--foreground)' }}>{pos.currentPrice?.toFixed(2) ?? '—'}</span>
<span style={{ fontSize: 12, fontWeight: 600, color: pos_ ? '#16A34A' : '#DC2626' }}> <span style={{ fontSize: 12, fontWeight: 600, color: pos_ ? '#16A34A' : '#DC2626' }}>
{pos_ ? '+' : ''}{pct.toFixed(2)}% {pos_ ? '+' : ''}{pct.toFixed(2)}%
</span> </span>
<span style={{ fontSize: 12, color: '#374151' }}>{fmt$(pos.marketValue ?? 0)}</span> <span style={{ fontSize: 12, color: 'var(--foreground)' }}>{fmt$(pos.marketValue ?? 0)}</span>
</div> </div>
); );
})} })}
@ -115,10 +116,10 @@ function NewsCard({ article }: { article: NewsArticle }) {
display: 'flex', gap: 10, display: 'flex', gap: 10,
padding: '10px 16px', padding: '10px 16px',
textDecoration: 'none', textDecoration: 'none',
borderBottom: '1px solid #F3F4F6', borderBottom: '1px solid var(--border)',
transition: 'background 0.1s', transition: 'background 0.1s',
}} }}
onMouseEnter={e => (e.currentTarget.style.background = '#F9FAFB')} onMouseEnter={e => (e.currentTarget.style.background = 'var(--muted)')}
onMouseLeave={e => (e.currentTarget.style.background = 'transparent')} onMouseLeave={e => (e.currentTarget.style.background = 'transparent')}
> >
{img && ( {img && (
@ -130,7 +131,7 @@ function NewsCard({ article }: { article: NewsArticle }) {
)} )}
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
<div style={{ <div style={{
fontSize: 12, fontWeight: 600, color: '#111827', fontSize: 12, fontWeight: 600, color: 'var(--foreground)',
lineHeight: 1.4, lineHeight: 1.4,
display: '-webkit-box', display: '-webkit-box',
WebkitLineClamp: 2, WebkitLineClamp: 2,
@ -140,7 +141,7 @@ function NewsCard({ article }: { article: NewsArticle }) {
}}> }}>
{article.headline} {article.headline}
</div> </div>
<div style={{ fontSize: 10, color: '#9CA3AF' }}> <div style={{ fontSize: 10, color: 'var(--muted-foreground)' }}>
{article.source} · {timeAgo(article.created_at)} {article.source} · {timeAgo(article.created_at)}
</div> </div>
</div> </div>
@ -158,7 +159,7 @@ function NewsFeedSkeleton() {
display: 'flex', display: 'flex',
gap: 10, gap: 10,
padding: '10px 16px', padding: '10px 16px',
borderBottom: i < 2 ? '1px solid #F9FAFB' : 'none', borderBottom: i < 2 ? '1px solid var(--border)' : 'none',
}} }}
> >
<SkeletonBlock width={48} height={48} radius={10} style={{ flexShrink: 0 }} /> <SkeletonBlock width={48} height={48} radius={10} style={{ flexShrink: 0 }} />
@ -200,10 +201,10 @@ function NewsFeed() {
display: 'flex', justifyContent: 'space-between', alignItems: 'center', display: 'flex', justifyContent: 'space-between', alignItems: 'center',
padding: '12px 16px 8px', padding: '12px 16px 8px',
}}> }}>
<span style={{ fontSize: 13, fontWeight: 700, color: '#111827' }}>Latest News</span> <span style={{ fontSize: 13, fontWeight: 700, color: 'var(--foreground)' }}>Latest News</span>
<span style={{ fontSize: 12, color: '#2563EB', fontWeight: 500, cursor: 'pointer' }}> <Button variant="ghost" size="sm" className="h-8 px-2 text-xs">
View All <ArrowRight size={12} style={{ display: 'inline', verticalAlign: 'middle' }} /> View All <ArrowRight size={12} />
</span> </Button>
</div> </div>
{loading && <NewsFeedSkeleton />} {loading && <NewsFeedSkeleton />}
@ -213,7 +214,7 @@ function NewsFeed() {
)} )}
{!loading && !error && news.length === 0 && ( {!loading && !error && news.length === 0 && (
<div style={{ fontSize: 12, color: '#9CA3AF', padding: '16px', textAlign: 'center' }}> <div style={{ fontSize: 12, color: 'var(--muted-foreground)', padding: '16px', textAlign: 'center' }}>
{activeSymbol ? `No news found for ${activeSymbol}` : 'Search a ticker to see news'} {activeSymbol ? `No news found for ${activeSymbol}` : 'Search a ticker to see news'}
</div> </div>
)} )}
@ -228,14 +229,14 @@ function NewsFeed() {
export function RightPanel() { export function RightPanel() {
return ( return (
<aside className="dashboard-right-panel" style={{ <aside className="dashboard-right-panel" style={{
background: '#ffffff', background: 'var(--card)',
borderLeft: '1px solid #E5E7EB', borderLeft: '1px solid var(--border)',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
overflowY: 'auto', overflowY: 'auto',
flexShrink: 0, flexShrink: 0,
}}> }}>
<div style={{ borderBottom: '1px solid #E5E7EB' }}> <div style={{ borderBottom: '1px solid var(--border)' }}>
<PortfolioSummary /> <PortfolioSummary />
</div> </div>
<NewsFeed /> <NewsFeed />

View File

@ -218,6 +218,50 @@ body {
box-shadow: var(--card-shadow); box-shadow: var(--card-shadow);
} }
.section-title {
font-size: 15px;
font-weight: 700;
color: var(--foreground);
margin-bottom: 4px;
}
.section-description {
font-size: 13px;
color: var(--muted-foreground);
}
.pill-group {
display: inline-flex;
gap: 6px;
padding: 4px;
width: fit-content;
border-radius: 999px;
border: 1px solid var(--border);
background: var(--card);
box-shadow: var(--card-shadow);
}
.stat-chip {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
border-radius: 999px;
border: 1px solid var(--border);
background: var(--card-elevated);
color: var(--muted-foreground);
font-size: 11px;
font-weight: 700;
}
.stat-chip[data-tone="success"] {
color: #16a34a;
}
.stat-chip[data-tone="danger"] {
color: #dc2626;
}
.trading-sidebar-nav { .trading-sidebar-nav {
flex: 1; flex: 1;
display: flex; display: flex;

View File

@ -4,6 +4,10 @@ import { BacktestRunnerPanel } from '../backtest/components/BacktestRunnerPanel'
import { BacktestComparePanel } from '../backtest/components/BacktestComparePanel'; import { BacktestComparePanel } from '../backtest/components/BacktestComparePanel';
import { useBacktestFeatureGate } from '../backtest/useBacktestFeatureGate'; import { useBacktestFeatureGate } from '../backtest/useBacktestFeatureGate';
import { fetchTradeProfiles } from '../lib/profileApi'; import { fetchTradeProfiles } from '../lib/profileApi';
import { PageHeader } from '../components/ui/page-header';
import { Card, CardContent } from '../components/ui/card';
import { Button } from '../components/ui/button';
import { Select } from '../components/ui/select';
interface BacktestTabProps { interface BacktestTabProps {
previewAsCustomer?: boolean; previewAsCustomer?: boolean;
@ -71,36 +75,25 @@ export const BacktestTab: React.FC<BacktestTabProps> = ({ previewAsCustomer = fa
return ( return (
<div style={{ maxWidth: '1200px', margin: '0 auto', padding: '0 20px 60px 20px' }}> <div style={{ maxWidth: '1200px', margin: '0 auto', padding: '0 20px 60px 20px' }}>
<div style={{ marginBottom: '24px' }}> <PageHeader
<h2 style={{ color: 'white', fontWeight: 900, letterSpacing: '-0.02em', marginBottom: '6px' }}> title="Backtesting"
Backtesting description="Deterministic historical simulation. No real or paper orders are placed."
</h2> />
<p style={{ color: '#8b8b95', fontSize: '13px', margin: 0 }}>
Deterministic historical simulation. No real or paper orders are placed.
</p>
</div>
{showAccessPanel && ( {showAccessPanel && (
<div <Card style={{ marginBottom: 16 }}>
style={{ <CardContent style={{ padding: '14px 16px' }}>
marginBottom: '16px',
padding: '14px 16px',
borderRadius: '14px',
background: 'rgba(17,24,39,0.45)',
border: '1px solid rgba(148,163,184,0.22)'
}}
>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px', marginBottom: '10px' }}> <div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px', marginBottom: '10px' }}>
<span style={{ fontSize: '11px', color: '#d1d5db', fontWeight: 700 }}> <span className="stat-chip">
Role: {isAdminAccount ? 'admin' : 'customer'} Role: {isAdminAccount ? 'admin' : 'customer'}
</span> </span>
<span style={{ fontSize: '11px', color: '#d1d5db', fontWeight: 700 }}> <span className="stat-chip">
Preview Mode: {previewAsCustomer ? 'customer view' : 'off'} Preview Mode: {previewAsCustomer ? 'customer view' : 'off'}
</span> </span>
<span style={{ fontSize: '11px', color: runtimeFlags.enableBacktest ? '#34d399' : '#f87171', fontWeight: 700 }}> <span className="stat-chip" data-tone={runtimeFlags.enableBacktest ? 'success' : 'danger'}>
ENABLE_BACKTEST: {String(runtimeFlags.enableBacktest)} ENABLE_BACKTEST: {String(runtimeFlags.enableBacktest)}
</span> </span>
<span style={{ fontSize: '11px', color: runtimeFlags.customerEnabled ? '#34d399' : '#f87171', fontWeight: 700 }}> <span className="stat-chip" data-tone={runtimeFlags.customerEnabled ? 'success' : 'danger'}>
BACKTEST_CUSTOMER_ENABLED: {String(runtimeFlags.customerEnabled)} BACKTEST_CUSTOMER_ENABLED: {String(runtimeFlags.customerEnabled)}
</span> </span>
</div> </div>
@ -114,132 +107,45 @@ export const BacktestTab: React.FC<BacktestTabProps> = ({ previewAsCustomer = fa
Backtest access is enabled for this view. Backtest access is enabled for this view.
</p> </p>
)} )}
</div> </CardContent>
</Card>
)} )}
{backtestGateLoading ? ( {backtestGateLoading ? (
<div <Card><CardContent style={{ padding: 18, color: 'var(--muted-foreground)', fontSize: 12 }}>Resolving backtest access...</CardContent></Card>
style={{
padding: '18px',
borderRadius: '14px',
background: 'rgba(24,24,27,0.55)',
border: '1px solid rgba(113,113,122,0.25)',
color: '#a1a1aa',
fontSize: '12px'
}}
>
Resolving backtest access...
</div>
) : !backtestEnabled ? ( ) : !backtestEnabled ? (
<div <Card><CardContent style={{ padding: 18, color: '#DC2626', fontSize: 12 }}>Backtest is disabled for this account view.</CardContent></Card>
style={{
padding: '18px',
borderRadius: '14px',
background: 'rgba(127,29,29,0.22)',
border: '1px solid rgba(248,113,113,0.3)',
color: '#fecaca',
fontSize: '12px'
}}
>
Backtest is disabled for this account view.
</div>
) : loadingProfiles ? ( ) : loadingProfiles ? (
<div <Card><CardContent style={{ padding: 18, color: 'var(--muted-foreground)', fontSize: 12 }}>Loading strategy profiles...</CardContent></Card>
style={{
padding: '18px',
borderRadius: '14px',
background: 'rgba(24,24,27,0.55)',
border: '1px solid rgba(113,113,122,0.25)',
color: '#a1a1aa',
fontSize: '12px'
}}
>
Loading strategy profiles...
</div>
) : profiles.length === 0 ? ( ) : profiles.length === 0 ? (
<div <Card><CardContent style={{ padding: 18, color: 'var(--muted-foreground)', fontSize: 12 }}>Create a strategy profile first, then run backtests from this tab.</CardContent></Card>
style={{
padding: '18px',
borderRadius: '14px',
background: 'rgba(24,24,27,0.55)',
border: '1px solid rgba(113,113,122,0.25)',
color: '#a1a1aa',
fontSize: '12px'
}}
>
Create a strategy profile first, then run backtests from this tab.
</div>
) : ( ) : (
<div style={{ display: 'grid', gap: '12px' }}> <div style={{ display: 'grid', gap: '12px' }}>
<div <div className="pill-group">
style={{ <Button type="button" variant={mode === 'single' ? 'default' : 'ghost'} size="sm" onClick={() => setMode('single')}>
display: 'inline-flex',
gap: '6px',
background: 'rgba(24,24,27,0.85)',
border: '1px solid rgba(148,163,184,0.25)',
borderRadius: '999px',
padding: '4px',
width: 'fit-content'
}}
>
<button
type="button"
onClick={() => setMode('single')}
style={{
border: 'none',
borderRadius: '999px',
padding: '8px 12px',
fontSize: '11px',
fontWeight: 800,
cursor: 'pointer',
background: mode === 'single' ? '#22d3ee' : 'transparent',
color: mode === 'single' ? '#06141b' : '#cbd5e1'
}}
>
Single Run Single Run
</button> </Button>
<button <Button type="button" variant={mode === 'compare' ? 'default' : 'ghost'} size="sm" onClick={() => setMode('compare')}>
type="button"
onClick={() => setMode('compare')}
style={{
border: 'none',
borderRadius: '999px',
padding: '8px 12px',
fontSize: '11px',
fontWeight: 800,
cursor: 'pointer',
background: mode === 'compare' ? '#818cf8' : 'transparent',
color: mode === 'compare' ? '#0f1022' : '#cbd5e1'
}}
>
Compare Strategies Compare Strategies
</button> </Button>
</div> </div>
{mode === 'single' ? ( {mode === 'single' ? (
<> <>
<label style={{ display: 'grid', gap: '6px' }}> <label style={{ display: 'grid', gap: '6px' }}>
<span style={{ fontSize: '11px', color: '#9ca3af', fontWeight: 800, textTransform: 'uppercase' }}> <span style={{ fontSize: '11px', color: 'var(--muted-foreground)', fontWeight: 800, textTransform: 'uppercase' }}>
Strategy Profile Strategy Profile
</span> </span>
<select <Select
value={selectedProfileId} value={selectedProfileId}
onChange={(event) => setSelectedProfileId(event.target.value)} onChange={(event) => setSelectedProfileId(event.target.value)}
style={{
padding: '10px 12px',
borderRadius: '10px',
border: '1px solid rgba(148,163,184,0.25)',
background: 'rgba(24,24,27,0.85)',
color: '#fff',
fontSize: '12px'
}}
> >
{profiles.map((item) => ( {profiles.map((item) => (
<option key={item.id} value={item.id}> <option key={item.id} value={item.id}>
{item.name} ({item.symbols}) {item.name} ({item.symbols})
</option> </option>
))} ))}
</select> </Select>
</label> </label>
{selectedProfile && ( {selectedProfile && (

View File

@ -3,6 +3,8 @@ import { PresetMarketplace } from '../components/PresetMarketplace';
import type { StrategyPreset } from '../lib/PresetRegistry'; import type { StrategyPreset } from '../lib/PresetRegistry';
import type { BotState } from '../hooks/useWebSocket'; import type { BotState } from '../hooks/useWebSocket';
import { TrendingUp, Brain } from 'lucide-react'; import { TrendingUp, Brain } from 'lucide-react';
import { PageHeader } from '../components/ui/page-header';
import { Card, CardContent } from '../components/ui/card';
interface MarketplaceTabProps { interface MarketplaceTabProps {
onClone: (preset: StrategyPreset) => void; onClone: (preset: StrategyPreset) => void;
@ -23,6 +25,10 @@ export const MarketplaceTab: React.FC<MarketplaceTabProps> = ({ onClone, botStat
return ( return (
<div style={{ animation: 'fadeIn 0.5s ease-out' }}> <div style={{ animation: 'fadeIn 0.5s ease-out' }}>
<PageHeader
title="Marketplace"
description="Explore curated strategy presets and compare them against current market context."
/>
{/* Contextual Intelligence Row */} {/* Contextual Intelligence Row */}
<div style={{ <div style={{
@ -34,62 +40,55 @@ export const MarketplaceTab: React.FC<MarketplaceTabProps> = ({ onClone, botStat
padding: '0 20px' padding: '0 20px'
}}> }}>
{/* AI Best Setups Panel */} {/* AI Best Setups Panel */}
<div style={{ <Card className="hero-surface">
background: 'linear-gradient(135deg, rgba(0, 255, 136, 0.04), rgba(0,0,0,0))', <CardContent style={{ padding: '24px 28px' }}>
border: '1px solid rgba(0, 255, 136, 0.1)',
borderRadius: '24px',
padding: '24px 28px'
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '20px' }}> <div style={{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '20px' }}>
<div style={{ width: '32px', height: '32px', borderRadius: '10px', background: 'rgba(0,255,136,0.1)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}> <div style={{ width: '32px', height: '32px', borderRadius: '10px', background: 'var(--accent-soft)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Brain size={16} color="#00ff88" /> <Brain size={16} color="var(--accent)" />
</div> </div>
<div> <div>
<div style={{ fontSize: '11px', fontWeight: 900, color: '#00ff88', textTransform: 'uppercase', letterSpacing: '2px' }}>🧠 Best AI Setups</div> <div className="section-title">Best AI Setups</div>
<div style={{ fontSize: '10px', color: '#444', fontWeight: 700, marginTop: '1px' }}>High-confidence signals find the right strategy below</div> <div style={{ fontSize: '10px', color: 'var(--muted-foreground)', fontWeight: 700, marginTop: '1px' }}>High-confidence signals find the right strategy below</div>
</div> </div>
</div> </div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}> <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
{aiSetups.map(s => { {aiSetups.map(s => {
const conf = botState.symbols[s]?.rules['AIAnalysisRule']?.metadata?.confidence || 0; const conf = botState.symbols[s]?.rules['AIAnalysisRule']?.metadata?.confidence || 0;
return ( return (
<div key={s} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '10px 14px', background: 'rgba(0,0,0,0.2)', borderRadius: '12px', border: '1px solid rgba(255,255,255,0.03)' }}> <div key={s} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '10px 14px', background: 'var(--card-elevated)', borderRadius: '12px', border: '1px solid var(--border)' }}>
<span style={{ fontWeight: 800, fontSize: '13px', color: 'white' }}>{s}</span> <span style={{ fontWeight: 800, fontSize: '13px', color: 'var(--foreground)' }}>{s}</span>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}> <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<div style={{ width: `${Math.min(conf, 60)}px`, height: '3px', background: `hsl(${conf * 1.2}, 100%, 55%)`, borderRadius: '99px' }} /> <div style={{ width: `${Math.min(conf, 60)}px`, height: '3px', background: `hsl(${conf * 1.2}, 100%, 55%)`, borderRadius: '99px' }} />
<span style={{ fontSize: '12px', fontWeight: 900, color: '#00ff88', fontFamily: 'monospace' }}>{conf}%</span> <span style={{ fontSize: '12px', fontWeight: 900, color: 'var(--accent)', fontFamily: 'monospace' }}>{conf}%</span>
</div> </div>
</div> </div>
); );
})} })}
{aiSetups.length === 0 && ( {aiSetups.length === 0 && (
<div style={{ textAlign: 'center', padding: '20px', color: '#333', fontSize: '12px', fontStyle: 'italic' }}>AI analysis scanning markets...</div> <div style={{ textAlign: 'center', padding: '20px', color: 'var(--muted-foreground)', fontSize: '12px', fontStyle: 'italic' }}>AI analysis scanning markets...</div>
)} )}
</div> </div>
</div> </CardContent>
</Card>
{/* Top Volatile Panel */} {/* Top Volatile Panel */}
<div style={{ <Card className="hero-surface">
background: 'linear-gradient(135deg, rgba(255, 170, 0, 0.03), rgba(0,0,0,0))', <CardContent style={{ padding: '24px 28px' }}>
border: '1px solid rgba(255, 170, 0, 0.08)',
borderRadius: '24px',
padding: '24px 28px'
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '20px' }}> <div style={{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '20px' }}>
<div style={{ width: '32px', height: '32px', borderRadius: '10px', background: 'rgba(255,170,0,0.1)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}> <div style={{ width: '32px', height: '32px', borderRadius: '10px', background: 'rgba(255,170,0,0.1)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<TrendingUp size={16} color="#ffaa00" /> <TrendingUp size={16} color="#ffaa00" />
</div> </div>
<div> <div>
<div style={{ fontSize: '11px', fontWeight: 900, color: '#ffaa00', textTransform: 'uppercase', letterSpacing: '2px' }}>🔥 Top Volatile (24h)</div> <div className="section-title">Top Volatile (24h)</div>
<div style={{ fontSize: '10px', color: '#444', fontWeight: 700, marginTop: '1px' }}>Most active markets match with strategies below</div> <div style={{ fontSize: '10px', color: 'var(--muted-foreground)', fontWeight: 700, marginTop: '1px' }}>Most active markets match with strategies below</div>
</div> </div>
</div> </div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8px' }}> <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8px' }}>
{topVolatile.map(s => { {topVolatile.map(s => {
const change = botState.symbols[s]?.change24h || 0; const change = botState.symbols[s]?.change24h || 0;
return ( return (
<div key={s} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '10px 14px', background: 'rgba(0,0,0,0.2)', borderRadius: '12px', border: '1px solid rgba(255,255,255,0.03)' }}> <div key={s} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '10px 14px', background: 'var(--card-elevated)', borderRadius: '12px', border: '1px solid var(--border)' }}>
<span style={{ fontWeight: 800, fontSize: '12px', color: '#bbb' }}>{s.split('/')[0]}</span> <span style={{ fontWeight: 800, fontSize: '12px', color: 'var(--foreground)' }}>{s.split('/')[0]}</span>
<span style={{ fontSize: '12px', fontWeight: 900, fontFamily: 'monospace', color: change >= 0 ? '#00ff88' : '#ff3366' }}> <span style={{ fontSize: '12px', fontWeight: 900, fontFamily: 'monospace', color: change >= 0 ? '#00ff88' : '#ff3366' }}>
{change >= 0 ? '+' : ''}{change.toFixed(2)}% {change >= 0 ? '+' : ''}{change.toFixed(2)}%
</span> </span>
@ -97,10 +96,11 @@ export const MarketplaceTab: React.FC<MarketplaceTabProps> = ({ onClone, botStat
); );
})} })}
{topVolatile.length === 0 && ( {topVolatile.length === 0 && (
<div style={{ gridColumn: '1/-1', textAlign: 'center', padding: '20px', color: '#333', fontSize: '12px', fontStyle: 'italic' }}>Scanning markets...</div> <div style={{ gridColumn: '1/-1', textAlign: 'center', padding: '20px', color: 'var(--muted-foreground)', fontSize: '12px', fontStyle: 'italic' }}>Scanning markets...</div>
)} )}
</div> </div>
</div> </CardContent>
</Card>
</div> </div>
{/* Strategy Marketplace Grid */} {/* Strategy Marketplace Grid */}

View File

@ -5,14 +5,16 @@ import {
Lock, Lock,
CheckCircle2, CheckCircle2,
Sparkles, Sparkles,
Dna,
ArrowUpRight,
Fingerprint, Fingerprint,
Cpu, Cpu,
Target, Target,
Activity Activity,
ArrowUpRight
} from 'lucide-react'; } from 'lucide-react';
import { TIER_POLICIES } from '../lib/TierPolicy'; import { TIER_POLICIES } from '../lib/TierPolicy';
import { PageHeader } from '../components/ui/page-header';
import { Card, CardContent } from '../components/ui/card';
import { Button } from '../components/ui/button';
const PlanCard: React.FC<{ const PlanCard: React.FC<{
id: string; id: string;
@ -23,9 +25,11 @@ const PlanCard: React.FC<{
const themeColor = isElite ? '#00ff88' : isPro ? '#3498db' : '#929292'; const themeColor = isElite ? '#00ff88' : isPro ? '#3498db' : '#929292';
const price = id === 'free' ? '$0' : id === 'pro' ? '$49' : '$199'; const price = id === 'free' ? '$0' : id === 'pro' ? '$49' : '$199';
const description = id === 'free' ? 'Perfect for learning the fundamentals of automated trading with zero risk.' : const description = id === 'free'
id === 'pro' ? 'For serious traders looking to scale their operations with more bots and flexibility.' : ? 'Perfect for learning the fundamentals of automated trading with zero risk.'
'The full Bytelyst ecosystem with zero restrictions and maximum performance.'; : id === 'pro'
? 'For serious traders looking to scale with more bots and more flexibility.'
: 'The full Bytelyst ecosystem with zero restrictions and maximum performance.';
const specs = [ const specs = [
{ label: 'Latency', value: id === 'free' ? 'Standard' : id === 'pro' ? 'Priority' : 'Real-time', icon: <Cpu size={14} /> }, { label: 'Latency', value: id === 'free' ? 'Standard' : id === 'pro' ? 'Priority' : 'Real-time', icon: <Cpu size={14} /> },
@ -35,282 +39,145 @@ const PlanCard: React.FC<{
]; ];
return ( return (
<div style={{ <Card className="h-full rounded-[28px]">
background: '#14151a', <CardContent className="flex min-h-[620px] flex-col p-8">
borderRadius: '28px', <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 28 }}>
border: '1px solid rgba(255, 255, 255, 0.05)', <div style={{ display: 'flex', gap: 14, alignItems: 'center' }}>
padding: '32px', <div style={{
display: 'flex', width: 44,
flexDirection: 'column', height: 44,
position: 'relative', borderRadius: 14,
overflow: 'hidden', background: 'var(--card-elevated)',
transition: 'all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275)', display: 'flex',
cursor: 'default', alignItems: 'center',
height: '100%', justifyContent: 'center',
minHeight: '620px' border: '1px solid var(--border)',
}} className="membership-card-hover"> color: themeColor
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '28px' }}> {isElite ? <Crown size={22} /> : isPro ? <Zap size={22} fill="currentColor" /> : <Lock size={20} />}
<div style={{ display: 'flex', gap: '14px', alignItems: 'center' }}> </div>
<div style={{ <div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
width: '44px', <span style={{ fontSize: 10, fontWeight: 900, color: 'var(--muted-foreground)', textTransform: 'uppercase', letterSpacing: '2px' }}>
height: '44px', Tier level
borderRadius: '14px', </span>
background: 'rgba(255,255,255,0.03)', <span style={{ fontSize: 13, fontWeight: 800, color: 'var(--foreground)', textTransform: 'uppercase', letterSpacing: '0.5px' }}>
display: 'flex', {policy.label}
alignItems: 'center', </span>
justifyContent: 'center',
border: '1px solid rgba(255, 255, 255, 0.05)',
color: themeColor
}}>
{isElite ? <Crown size={22} /> : isPro ? <Zap size={22} fill="currentColor" /> : <Lock size={20} />}
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '2px' }}>
<span style={{ fontSize: '10px', fontWeight: 900, color: '#444', textTransform: 'uppercase', letterSpacing: '2px' }}>Tier Level</span>
<span style={{ fontSize: '13px', fontWeight: 800, color: 'white', textTransform: 'uppercase', letterSpacing: '0.5px' }}>{policy.label}</span>
</div>
</div>
{isElite && (
<div style={{
background: 'rgba(0, 255, 136, 0.1)',
border: '1px solid rgba(0, 255, 136, 0.2)',
padding: '6px 12px',
borderRadius: '10px',
fontSize: '10px',
fontWeight: 900,
color: '#00ff88',
textTransform: 'uppercase'
}}>
Peak
</div>
)}
</div>
<div style={{ marginBottom: '24px', display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: '12px' }}>
<div style={{ display: 'flex', alignItems: 'baseline', gap: '4px' }}>
<h3 style={{ fontSize: '42px', fontWeight: 950, color: 'white', letterSpacing: '-0.04em', margin: 0 }}>{price}</h3>
<span style={{ fontSize: '14px', color: '#444', fontWeight: 900, textTransform: 'uppercase' }}>/mo</span>
</div>
<div style={{
display: 'inline-flex',
alignItems: 'center',
gap: '8px',
background: 'rgba(255, 255, 255, 0.03)',
padding: '6px 14px',
borderRadius: '8px',
fontSize: '11px',
color: '#929292',
fontWeight: 900,
textTransform: 'uppercase',
border: '1px solid rgba(255, 255, 255, 0.05)',
letterSpacing: '0.5px'
}}>
<Sparkles size={14} style={{ color: '#fbbf24' }} /> Professional Grade DNA
</div>
</div>
<p style={{
fontSize: '14px',
color: '#888',
lineHeight: '1.6',
marginBottom: '28px',
textAlign: 'left',
flex: 1
}}>
{description} Enhanced with Bytelyst's core strategy engine and isolated security nodes.
</p>
<div style={{
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gap: '14px',
marginBottom: '28px'
}}>
{specs.map((spec, i) => (
<div key={i} style={{
background: 'rgba(0,0,0,0.2)',
border: '1px solid rgba(255,255,255,0.03)',
padding: '16px',
borderRadius: '20px',
display: 'flex',
flexDirection: 'column',
gap: '6px',
alignItems: 'flex-start'
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', color: '#555', fontSize: '10px', fontWeight: 900, textTransform: 'uppercase', letterSpacing: '1px' }}>
{spec.icon} {spec.label}
</div> </div>
<div style={{ color: 'white', fontWeight: 900, fontSize: '15px' }}>{spec.value}</div>
</div> </div>
))} {isElite && (
</div> <span className="stat-chip" data-tone="success">
Peak
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px', marginBottom: '32px' }}> </span>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', fontSize: '12px', color: 'rgba(255,255,255,0.4)', fontWeight: 700 }}> )}
<CheckCircle2 size={16} style={{ color: themeColor }} />
{id === 'free' ? 'Safe Style Only' : id === 'pro' ? 'Safe & Balanced' : 'All Risk Styles Unlocked'}
</div> </div>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', fontSize: '12px', color: 'rgba(255,255,255,0.4)', fontWeight: 700 }}>
<CheckCircle2 size={16} style={{ color: themeColor }} /> <div style={{ marginBottom: 24, display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 12 }}>
{id === 'free' ? '$100 Profit Limit' : id === 'pro' ? '$2,000 Profit Limit' : 'Zero Profit Restrictions'} <div style={{ display: 'flex', alignItems: 'baseline', gap: 4 }}>
<h3 style={{ fontSize: 42, fontWeight: 950, color: 'var(--foreground)', letterSpacing: '-0.04em', margin: 0 }}>{price}</h3>
<span style={{ fontSize: 14, color: 'var(--muted-foreground)', fontWeight: 900, textTransform: 'uppercase' }}>/mo</span>
</div>
<div className="stat-chip">
<Sparkles size={14} style={{ color: '#fbbf24' }} />
Professional grade DNA
</div>
</div> </div>
</div>
<div style={{ display: 'flex', gap: '12px', marginTop: 'auto' }}> <p style={{
<button fontSize: 14,
disabled={id !== 'elite'} color: 'var(--muted-foreground)',
style={{ lineHeight: '1.6',
flex: 1, marginBottom: 28,
height: '56px', textAlign: 'left',
background: isElite ? '#00ff88' : 'rgba(255,255,255,0.02)', flex: 1
color: isElite ? 'black' : '#444', }}>
borderRadius: '18px', {description}
border: isElite ? 'none' : '1px solid rgba(255,255,255,0.05)', </p>
fontWeight: 900,
fontSize: '12px',
textTransform: 'uppercase',
cursor: isElite ? 'pointer' : 'not-allowed',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '10px',
boxShadow: isElite ? '0 12px 36px -12px rgba(0, 255, 136, 0.4)' : 'none',
transition: 'all 0.2s',
letterSpacing: '1.5px'
}}
className={isElite ? "clone-btn" : ""}
>
{isElite ? 'ACTIVATE PREVIEW' : 'COMING SOON'} {isElite && <ArrowUpRight size={18} strokeWidth={3} />}
</button>
</div>
<div style={{ <div style={{
position: 'absolute', display: 'grid',
bottom: 0, gridTemplateColumns: '1fr 1fr',
left: 0, gap: 14,
right: 0, marginBottom: 28
height: '4px', }}>
background: `linear-gradient(90deg, transparent, ${themeColor}, transparent)`, {specs.map((spec, i) => (
opacity: 0.2 <div key={i} style={{
}} /> background: 'var(--card-elevated)',
border: '1px solid var(--border)',
padding: 16,
borderRadius: 20,
display: 'flex',
flexDirection: 'column',
gap: 6,
alignItems: 'flex-start'
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, color: 'var(--muted-foreground)', fontSize: 10, fontWeight: 900, textTransform: 'uppercase', letterSpacing: '1px' }}>
{spec.icon} {spec.label}
</div>
<div style={{ color: 'var(--foreground)', fontWeight: 900, fontSize: 15 }}>{spec.value}</div>
</div>
))}
</div>
<style>{` <div style={{ display: 'flex', flexDirection: 'column', gap: 10, marginBottom: 32 }}>
.membership-card-hover:hover { <div style={{ display: 'flex', alignItems: 'center', gap: 12, fontSize: 12, color: 'var(--muted-foreground)', fontWeight: 700 }}>
border-color: ${themeColor}40 !important; <CheckCircle2 size={16} style={{ color: themeColor }} />
transform: translateY(-8px); {id === 'free' ? 'Safe style only' : id === 'pro' ? 'Safe and balanced' : 'All risk styles unlocked'}
box-shadow: 0 40px 80px -20px rgba(0,0,0,0.8) !important; </div>
background: #1a1b21 !important; <div style={{ display: 'flex', alignItems: 'center', gap: 12, fontSize: 12, color: 'var(--muted-foreground)', fontWeight: 700 }}>
} <CheckCircle2 size={16} style={{ color: themeColor }} />
.clone-btn:hover { {id === 'free' ? '$100 profit limit' : id === 'pro' ? '$2,000 profit limit' : 'Zero profit restrictions'}
filter: brightness(1.1); </div>
transform: scale(1.02); </div>
}
`}</style> <div style={{ display: 'flex', gap: 12, marginTop: 'auto' }}>
</div> <Button
disabled={id !== 'elite'}
variant={isElite ? 'default' : 'outline'}
size="lg"
className="flex-1 rounded-[18px] text-[12px] font-black tracking-[1.5px]"
>
{isElite ? 'ACTIVATE PREVIEW' : 'COMING SOON'}
{isElite && <ArrowUpRight size={18} strokeWidth={3} />}
</Button>
</div>
<div style={{
marginTop: 24,
height: 4,
borderRadius: 999,
background: `linear-gradient(90deg, transparent, ${themeColor}, transparent)`,
opacity: 0.2
}} />
</CardContent>
</Card>
); );
}; };
export const MembershipTab: React.FC = () => { export const MembershipTab: React.FC = () => {
return ( return (
<div style={{ <div style={{ maxWidth: 1400, margin: '0 auto', padding: '0 20px 100px 20px' }}>
maxWidth: '1400px', <PageHeader
margin: '0 auto', title="Membership Plans"
padding: '0 20px 100px 20px', description="Upgrade your algorithmic edge with advanced strategies, higher capacity, and priority execution."
animation: 'fadeIn 0.7s ease-out' />
}}>
<div style={{
display: 'flex',
flexDirection: 'column',
marginBottom: '60px',
padding: '60px 0',
borderBottom: '1px solid rgba(255, 255, 255, 0.05)',
position: 'relative',
alignItems: 'flex-start'
}}>
<div style={{
position: 'absolute',
top: 0,
right: 0,
opacity: 0.03,
pointerEvents: 'none',
transform: 'translate(40px, -20px)'
}}>
<Dna size={320} strokeWidth={1} />
</div>
<div style={{ <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', marginBottom: 28 }}>
display: 'inline-flex', <span className="stat-chip">3 tiers</span>
alignItems: 'center', <span className="stat-chip">Preview pricing</span>
gap: '12px', <span className="stat-chip">Product direction only</span>
color: '#00ff88',
fontSize: '11px',
fontWeight: 900,
textTransform: 'uppercase',
letterSpacing: '4px',
marginBottom: '24px'
}}>
PRICING PHILOSOPHY
<div style={{ width: '30px', height: '1px', background: '#00ff88', opacity: 0.2 }} />
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', width: '100%', flexWrap: 'wrap', gap: '32px' }}>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
<h2 style={{
fontSize: '84px',
fontWeight: 950,
color: 'white',
letterSpacing: '-0.04em',
lineHeight: '0.9',
margin: 0,
textTransform: 'uppercase'
}}>
Membership<br />
<span style={{ color: '#00ff88' }}>Plans</span>
</h2>
<p style={{ fontSize: '20px', color: '#666', marginTop: '24px', maxWidth: '600px', fontWeight: 500, margin: '24px 0 0 0', textAlign: 'left' }}>
Upgrade your algorithmic edge with advanced strategies and priority execution.
</p>
</div>
<div style={{ display: 'flex', gap: '16px', alignItems: 'center' }}>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', padding: '0 24px', borderLeft: '1px solid rgba(255, 255, 255, 0.08)' }}>
<span style={{ color: '#444', fontSize: '11px', fontWeight: 900, textTransform: 'uppercase', letterSpacing: '2px' }}>Tiers</span>
<span style={{ color: 'white', fontSize: '32px', fontWeight: 950, lineHeight: '1' }}>3</span>
</div>
<div style={{
background: 'rgba(251, 191, 36, 0.05)',
border: '1px solid rgba(251, 191, 36, 0.1)',
padding: '12px 24px',
borderRadius: '16px',
color: '#fbbf24',
fontSize: '11px',
fontWeight: 900,
textTransform: 'uppercase',
letterSpacing: '1px'
}}>
Global Preview Mode Active
</div>
</div>
</div>
</div> </div>
<div style={{ <div style={{
display: 'grid', display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))', gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))',
gap: '40px', gap: 32,
width: '100%' width: '100%'
}}> }}>
{Object.entries(TIER_POLICIES).map(([id, policy]) => ( {Object.entries(TIER_POLICIES).map(([id, policy]) => (
<PlanCard key={id} id={id} policy={policy} /> <PlanCard key={id} id={id} policy={policy} />
))} ))}
</div> </div>
<style>{`
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
`}</style>
</div> </div>
); );
}; };

View File

@ -7,6 +7,7 @@ import { VisualRuleBuilder, type VisualRule } from '../components/strategy/Visua
import { createTradeProfile } from '../lib/profileApi'; import { createTradeProfile } from '../lib/profileApi';
import { BacktestRunnerPanel } from '../backtest/components/BacktestRunnerPanel'; import { BacktestRunnerPanel } from '../backtest/components/BacktestRunnerPanel';
import { PageHeader } from '../components/ui/page-header'; import { PageHeader } from '../components/ui/page-header';
import { Card, CardContent } from '../components/ui/card';
type ResearchTab = 'Strategies' | 'Visual Builder' | 'Code Editor' | 'Signals' | 'Backtesting'; type ResearchTab = 'Strategies' | 'Visual Builder' | 'Code Editor' | 'Signals' | 'Backtesting';
@ -105,10 +106,10 @@ export function ResearchView() {
{tab === 'Visual Builder' && ( {tab === 'Visual Builder' && (
<div> <div>
<div style={{ marginBottom: 16 }}> <div style={{ marginBottom: 16 }}>
<div style={{ fontSize: 15, fontWeight: 700, color: '#111827', marginBottom: 4 }}> <div className="section-title">
Visual Rule Builder Visual Rule Builder
</div> </div>
<div style={{ fontSize: 13, color: '#6B7280' }}> <div className="section-description">
Build a trading strategy by composing IF/THEN rules. Drag rows to reorder. Click "Save Strategy" to store it. Build a trading strategy by composing IF/THEN rules. Drag rows to reorder. Click "Save Strategy" to store it.
</div> </div>
</div> </div>
@ -134,10 +135,10 @@ export function ResearchView() {
{tab === 'Code Editor' && ( {tab === 'Code Editor' && (
<div> <div>
<div style={{ marginBottom: 16 }}> <div style={{ marginBottom: 16 }}>
<div style={{ fontSize: 15, fontWeight: 700, color: '#111827', marginBottom: 4 }}> <div className="section-title">
Code Strategy Editor Code Strategy Editor
</div> </div>
<div style={{ fontSize: 13, color: '#6B7280' }}> <div className="section-description">
Write a custom strategy function in JavaScript. Click "Run Backtest" to test it against historical data. Write a custom strategy function in JavaScript. Click "Run Backtest" to test it against historical data.
</div> </div>
</div> </div>
@ -164,23 +165,10 @@ export function ResearchView() {
function CodeStrategyEditorFallback() { function CodeStrategyEditorFallback() {
return ( return (
<div <Card role="status" aria-label="Loading code strategy editor" className="hero-surface min-h-[420px]">
role="status" <CardContent className="flex min-h-[420px] items-center justify-center text-sm font-semibold text-[var(--muted-foreground)]">
aria-label="Loading code strategy editor" Loading code strategy editor
style={{ </CardContent>
minHeight: 420, </Card>
border: '1px solid #E5E7EB',
borderRadius: 12,
background: 'linear-gradient(135deg, #F8FAFC, #EEF2FF)',
color: '#4B5563',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 13,
fontWeight: 700,
}}
>
Loading code strategy editor
</div>
); );
} }