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 { Header } from './Header';
import { RightPanel } from './RightPanel';
import { Button } from '../ui/button';
import { HomeView } from '../../views/HomeView';
import { PortfolioView } from '../../views/PortfolioView';
import { ResearchView } from '../../views/ResearchView';
@ -21,40 +22,28 @@ function NotFoundView() {
style={{
minHeight: 420,
borderRadius: 24,
border: '1px solid #E5E7EB',
background: 'linear-gradient(135deg, #FFF7ED 0%, #EFF6FF 100%)',
border: '1px solid var(--border)',
background: 'var(--hero-gradient)',
padding: '56px 32px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: '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
</div>
<h1 id="not-found-title" style={{ margin: '10px 0 8px', fontSize: 34, fontWeight: 900 }}>
Route not found
</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.
</p>
<Link
to="/"
style={{
marginTop: 24,
borderRadius: 999,
background: '#111827',
color: '#fff',
padding: '10px 18px',
fontSize: 13,
fontWeight: 800,
textDecoration: 'none',
}}
>
Return home
<Link to="/" style={{ marginTop: 24, textDecoration: 'none' }}>
<Button>Return home</Button>
</Link>
</section>
);

View File

@ -3,6 +3,7 @@ import { ArrowRight } from 'lucide-react';
import { useAppContext } from '../../context/AppContext';
import { fetchNews, type NewsArticle as MarketNewsArticle } from '../../lib/marketApi';
import { SkeletonBlock, SkeletonText } from '../Skeleton';
import { Button } from '../ui/button';
// ─── Portfolio Summary ────────────────────────────────────────────────────────
@ -25,15 +26,15 @@ function PortfolioSummary() {
<div style={{ padding: '16px 16px 12px' }}>
{/* Header */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
<span style={{ fontSize: 13, fontWeight: 700, color: '#111827' }}>Portfolio</span>
<span style={{ fontSize: 12, color: '#2563EB', cursor: 'pointer', fontWeight: 500 }}>
View All <ArrowRight size={12} style={{ display: 'inline', verticalAlign: 'middle' }} />
</span>
<span style={{ fontSize: 13, fontWeight: 700, color: 'var(--foreground)' }}>Portfolio</span>
<Button variant="ghost" size="sm" className="h-8 px-2 text-xs">
View All <ArrowRight size={12} />
</Button>
</div>
{/* Total value */}
<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)}
</div>
<div style={{
@ -49,10 +50,10 @@ function PortfolioSummary() {
display: 'grid',
gridTemplateColumns: '2fr 1.2fr 1fr 1.2fr',
gap: 4, paddingBottom: 6,
borderBottom: '1px solid #F3F4F6', marginBottom: 4,
borderBottom: '1px solid var(--border)', marginBottom: 4,
}}>
{['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}
</span>
))}
@ -60,7 +61,7 @@ function PortfolioSummary() {
{/* Rows */}
{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
</div>
) : (
@ -73,15 +74,15 @@ function PortfolioSummary() {
display: 'grid',
gridTemplateColumns: '2fr 1.2fr 1fr 1.2fr',
gap: 4, padding: '5px 0',
borderBottom: '1px solid #F9FAFB',
borderBottom: '1px solid var(--border)',
alignItems: 'center',
}}>
<span style={{ fontSize: 12, fontWeight: 700, color: '#111827' }}>{pos.symbol}</span>
<span style={{ fontSize: 12, color: '#374151' }}>{pos.currentPrice?.toFixed(2) ?? '—'}</span>
<span style={{ fontSize: 12, fontWeight: 700, color: 'var(--foreground)' }}>{pos.symbol}</span>
<span style={{ fontSize: 12, color: 'var(--foreground)' }}>{pos.currentPrice?.toFixed(2) ?? '—'}</span>
<span style={{ fontSize: 12, fontWeight: 600, color: pos_ ? '#16A34A' : '#DC2626' }}>
{pos_ ? '+' : ''}{pct.toFixed(2)}%
</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>
);
})}
@ -115,10 +116,10 @@ function NewsCard({ article }: { article: NewsArticle }) {
display: 'flex', gap: 10,
padding: '10px 16px',
textDecoration: 'none',
borderBottom: '1px solid #F3F4F6',
borderBottom: '1px solid var(--border)',
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')}
>
{img && (
@ -130,7 +131,7 @@ function NewsCard({ article }: { article: NewsArticle }) {
)}
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{
fontSize: 12, fontWeight: 600, color: '#111827',
fontSize: 12, fontWeight: 600, color: 'var(--foreground)',
lineHeight: 1.4,
display: '-webkit-box',
WebkitLineClamp: 2,
@ -140,7 +141,7 @@ function NewsCard({ article }: { article: NewsArticle }) {
}}>
{article.headline}
</div>
<div style={{ fontSize: 10, color: '#9CA3AF' }}>
<div style={{ fontSize: 10, color: 'var(--muted-foreground)' }}>
{article.source} · {timeAgo(article.created_at)}
</div>
</div>
@ -158,7 +159,7 @@ function NewsFeedSkeleton() {
display: 'flex',
gap: 10,
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 }} />
@ -200,10 +201,10 @@ function NewsFeed() {
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
padding: '12px 16px 8px',
}}>
<span style={{ fontSize: 13, fontWeight: 700, color: '#111827' }}>Latest News</span>
<span style={{ fontSize: 12, color: '#2563EB', fontWeight: 500, cursor: 'pointer' }}>
View All <ArrowRight size={12} style={{ display: 'inline', verticalAlign: 'middle' }} />
</span>
<span style={{ fontSize: 13, fontWeight: 700, color: 'var(--foreground)' }}>Latest News</span>
<Button variant="ghost" size="sm" className="h-8 px-2 text-xs">
View All <ArrowRight size={12} />
</Button>
</div>
{loading && <NewsFeedSkeleton />}
@ -213,7 +214,7 @@ function NewsFeed() {
)}
{!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'}
</div>
)}
@ -228,14 +229,14 @@ function NewsFeed() {
export function RightPanel() {
return (
<aside className="dashboard-right-panel" style={{
background: '#ffffff',
borderLeft: '1px solid #E5E7EB',
background: 'var(--card)',
borderLeft: '1px solid var(--border)',
display: 'flex',
flexDirection: 'column',
overflowY: 'auto',
flexShrink: 0,
}}>
<div style={{ borderBottom: '1px solid #E5E7EB' }}>
<div style={{ borderBottom: '1px solid var(--border)' }}>
<PortfolioSummary />
</div>
<NewsFeed />

View File

@ -218,6 +218,50 @@ body {
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 {
flex: 1;
display: flex;

View File

@ -4,6 +4,10 @@ import { BacktestRunnerPanel } from '../backtest/components/BacktestRunnerPanel'
import { BacktestComparePanel } from '../backtest/components/BacktestComparePanel';
import { useBacktestFeatureGate } from '../backtest/useBacktestFeatureGate';
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 {
previewAsCustomer?: boolean;
@ -71,36 +75,25 @@ export const BacktestTab: React.FC<BacktestTabProps> = ({ previewAsCustomer = fa
return (
<div style={{ maxWidth: '1200px', margin: '0 auto', padding: '0 20px 60px 20px' }}>
<div style={{ marginBottom: '24px' }}>
<h2 style={{ color: 'white', fontWeight: 900, letterSpacing: '-0.02em', marginBottom: '6px' }}>
Backtesting
</h2>
<p style={{ color: '#8b8b95', fontSize: '13px', margin: 0 }}>
Deterministic historical simulation. No real or paper orders are placed.
</p>
</div>
<PageHeader
title="Backtesting"
description="Deterministic historical simulation. No real or paper orders are placed."
/>
{showAccessPanel && (
<div
style={{
marginBottom: '16px',
padding: '14px 16px',
borderRadius: '14px',
background: 'rgba(17,24,39,0.45)',
border: '1px solid rgba(148,163,184,0.22)'
}}
>
<Card style={{ marginBottom: 16 }}>
<CardContent style={{ padding: '14px 16px' }}>
<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'}
</span>
<span style={{ fontSize: '11px', color: '#d1d5db', fontWeight: 700 }}>
<span className="stat-chip">
Preview Mode: {previewAsCustomer ? 'customer view' : 'off'}
</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)}
</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)}
</span>
</div>
@ -114,132 +107,45 @@ export const BacktestTab: React.FC<BacktestTabProps> = ({ previewAsCustomer = fa
Backtest access is enabled for this view.
</p>
)}
</div>
</CardContent>
</Card>
)}
{backtestGateLoading ? (
<div
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>
<Card><CardContent style={{ padding: 18, color: 'var(--muted-foreground)', fontSize: 12 }}>Resolving backtest access...</CardContent></Card>
) : !backtestEnabled ? (
<div
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>
<Card><CardContent style={{ padding: 18, color: '#DC2626', fontSize: 12 }}>Backtest is disabled for this account view.</CardContent></Card>
) : loadingProfiles ? (
<div
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>
<Card><CardContent style={{ padding: 18, color: 'var(--muted-foreground)', fontSize: 12 }}>Loading strategy profiles...</CardContent></Card>
) : profiles.length === 0 ? (
<div
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>
<Card><CardContent style={{ padding: 18, color: 'var(--muted-foreground)', fontSize: 12 }}>Create a strategy profile first, then run backtests from this tab.</CardContent></Card>
) : (
<div style={{ display: 'grid', gap: '12px' }}>
<div
style={{
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'
}}
>
<div className="pill-group">
<Button type="button" variant={mode === 'single' ? 'default' : 'ghost'} size="sm" onClick={() => setMode('single')}>
Single Run
</button>
<button
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'
}}
>
</Button>
<Button type="button" variant={mode === 'compare' ? 'default' : 'ghost'} size="sm" onClick={() => setMode('compare')}>
Compare Strategies
</button>
</Button>
</div>
{mode === 'single' ? (
<>
<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
</span>
<select
<Select
value={selectedProfileId}
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) => (
<option key={item.id} value={item.id}>
{item.name} ({item.symbols})
</option>
))}
</select>
</Select>
</label>
{selectedProfile && (

View File

@ -1,8 +1,10 @@
import React from 'react';
import { PresetMarketplace } from '../components/PresetMarketplace';
import type { StrategyPreset } from '../lib/PresetRegistry';
import type { BotState } from '../hooks/useWebSocket';
import { TrendingUp, Brain } from 'lucide-react';
import React from 'react';
import { PresetMarketplace } from '../components/PresetMarketplace';
import type { StrategyPreset } from '../lib/PresetRegistry';
import type { BotState } from '../hooks/useWebSocket';
import { TrendingUp, Brain } from 'lucide-react';
import { PageHeader } from '../components/ui/page-header';
import { Card, CardContent } from '../components/ui/card';
interface MarketplaceTabProps {
onClone: (preset: StrategyPreset) => void;
@ -21,12 +23,16 @@ export const MarketplaceTab: React.FC<MarketplaceTabProps> = ({ onClone, botStat
.sort((a, b) => Math.abs(botState.symbols[b].change24h || 0) - Math.abs(botState.symbols[a].change24h || 0))
.slice(0, 6);
return (
<div style={{ animation: 'fadeIn 0.5s ease-out' }}>
{/* Contextual Intelligence Row */}
<div style={{
display: 'grid',
return (
<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 */}
<div style={{
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gap: '20px',
maxWidth: '1400px',
@ -34,74 +40,68 @@ export const MarketplaceTab: React.FC<MarketplaceTabProps> = ({ onClone, botStat
padding: '0 20px'
}}>
{/* AI Best Setups Panel */}
<div style={{
background: 'linear-gradient(135deg, rgba(0, 255, 136, 0.04), rgba(0,0,0,0))',
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={{ width: '32px', height: '32px', borderRadius: '10px', background: 'rgba(0,255,136,0.1)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Brain size={16} color="#00ff88" />
</div>
<div>
<div style={{ fontSize: '11px', fontWeight: 900, color: '#00ff88', textTransform: 'uppercase', letterSpacing: '2px' }}>🧠 Best AI Setups</div>
<div style={{ fontSize: '10px', color: '#444', fontWeight: 700, marginTop: '1px' }}>High-confidence signals find the right strategy below</div>
</div>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
<Card className="hero-surface">
<CardContent style={{ padding: '24px 28px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '20px' }}>
<div style={{ width: '32px', height: '32px', borderRadius: '10px', background: 'var(--accent-soft)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Brain size={16} color="var(--accent)" />
</div>
<div>
<div className="section-title">Best AI Setups</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 style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
{aiSetups.map(s => {
const conf = botState.symbols[s]?.rules['AIAnalysisRule']?.metadata?.confidence || 0;
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)' }}>
<span style={{ fontWeight: 800, fontSize: '13px', color: 'white' }}>{s}</span>
<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' }} />
<span style={{ fontSize: '12px', fontWeight: 900, color: '#00ff88', fontFamily: 'monospace' }}>{conf}%</span>
</div>
</div>
);
})}
{aiSetups.length === 0 && (
<div style={{ textAlign: 'center', padding: '20px', color: '#333', fontSize: '12px', fontStyle: 'italic' }}>AI analysis scanning markets...</div>
)}
</div>
</div>
<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: 'var(--foreground)' }}>{s}</span>
<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' }} />
<span style={{ fontSize: '12px', fontWeight: 900, color: 'var(--accent)', fontFamily: 'monospace' }}>{conf}%</span>
</div>
</div>
);
})}
{aiSetups.length === 0 && (
<div style={{ textAlign: 'center', padding: '20px', color: 'var(--muted-foreground)', fontSize: '12px', fontStyle: 'italic' }}>AI analysis scanning markets...</div>
)}
</div>
</CardContent>
</Card>
{/* Top Volatile Panel */}
<div style={{
background: 'linear-gradient(135deg, rgba(255, 170, 0, 0.03), rgba(0,0,0,0))',
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={{ width: '32px', height: '32px', borderRadius: '10px', background: 'rgba(255,170,0,0.1)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<TrendingUp size={16} color="#ffaa00" />
</div>
<div>
<div style={{ fontSize: '11px', fontWeight: 900, color: '#ffaa00', textTransform: 'uppercase', letterSpacing: '2px' }}>🔥 Top Volatile (24h)</div>
<div style={{ fontSize: '10px', color: '#444', fontWeight: 700, marginTop: '1px' }}>Most active markets match with strategies below</div>
</div>
</div>
<Card className="hero-surface">
<CardContent style={{ padding: '24px 28px' }}>
<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' }}>
<TrendingUp size={16} color="#ffaa00" />
</div>
<div>
<div className="section-title">Top Volatile (24h)</div>
<div style={{ fontSize: '10px', color: 'var(--muted-foreground)', fontWeight: 700, marginTop: '1px' }}>Most active markets match with strategies below</div>
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8px' }}>
{topVolatile.map(s => {
const change = botState.symbols[s]?.change24h || 0;
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)' }}>
<span style={{ fontWeight: 800, fontSize: '12px', color: '#bbb' }}>{s.split('/')[0]}</span>
<span style={{ fontSize: '12px', fontWeight: 900, fontFamily: 'monospace', color: change >= 0 ? '#00ff88' : '#ff3366' }}>
{change >= 0 ? '+' : ''}{change.toFixed(2)}%
</span>
<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: 'var(--foreground)' }}>{s.split('/')[0]}</span>
<span style={{ fontSize: '12px', fontWeight: 900, fontFamily: 'monospace', color: change >= 0 ? '#00ff88' : '#ff3366' }}>
{change >= 0 ? '+' : ''}{change.toFixed(2)}%
</span>
</div>
);
})}
{topVolatile.length === 0 && (
<div style={{ gridColumn: '1/-1', textAlign: 'center', padding: '20px', color: '#333', fontSize: '12px', fontStyle: 'italic' }}>Scanning markets...</div>
)}
</div>
</div>
</div>
<div style={{ gridColumn: '1/-1', textAlign: 'center', padding: '20px', color: 'var(--muted-foreground)', fontSize: '12px', fontStyle: 'italic' }}>Scanning markets...</div>
)}
</div>
</CardContent>
</Card>
</div>
{/* Strategy Marketplace Grid */}
<div style={{ maxWidth: '1400px', margin: '0 auto', padding: '0 0 40px 0' }}>

View File

@ -1,316 +1,183 @@
import React from 'react';
import {
Crown,
Zap,
Lock,
CheckCircle2,
Sparkles,
Dna,
ArrowUpRight,
Fingerprint,
Cpu,
Target,
Activity
} from 'lucide-react';
import { TIER_POLICIES } from '../lib/TierPolicy';
const PlanCard: React.FC<{
id: string;
policy: any;
}> = ({ id, policy }) => {
const isElite = id === 'elite';
const isPro = id === 'pro';
const themeColor = isElite ? '#00ff88' : isPro ? '#3498db' : '#929292';
const price = id === 'free' ? '$0' : id === 'pro' ? '$49' : '$199';
const description = id === 'free' ? 'Perfect for learning the fundamentals of automated trading with zero risk.' :
id === 'pro' ? 'For serious traders looking to scale their operations with more bots and flexibility.' :
'The full Bytelyst ecosystem with zero restrictions and maximum performance.';
const specs = [
{ label: 'Latency', value: id === 'free' ? 'Standard' : id === 'pro' ? 'Priority' : 'Real-time', icon: <Cpu size={14} /> },
{ label: 'Capacity', value: id === 'free' ? '1 Node' : id === 'pro' ? '5 Nodes' : 'Unlimited', icon: <Target size={14} /> },
{ label: 'Access', value: id === 'free' ? 'Basic' : id === 'pro' ? 'Full' : 'Exclusive', icon: <Fingerprint size={14} /> },
{ label: 'Performance', value: id === 'free' ? 'Capped' : id === 'pro' ? 'Advanced' : 'Unlimited', icon: <Activity size={14} /> }
];
return (
<div style={{
background: '#14151a',
borderRadius: '28px',
border: '1px solid rgba(255, 255, 255, 0.05)',
padding: '32px',
display: 'flex',
flexDirection: 'column',
position: 'relative',
overflow: 'hidden',
transition: 'all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275)',
cursor: 'default',
height: '100%',
minHeight: '620px'
}} className="membership-card-hover">
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '28px' }}>
<div style={{ display: 'flex', gap: '14px', alignItems: 'center' }}>
<div style={{
width: '44px',
height: '44px',
borderRadius: '14px',
background: 'rgba(255,255,255,0.03)',
display: 'flex',
alignItems: 'center',
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 style={{ color: 'white', fontWeight: 900, fontSize: '15px' }}>{spec.value}</div>
</div>
))}
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px', marginBottom: '32px' }}>
<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 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' ? '$100 Profit Limit' : id === 'pro' ? '$2,000 Profit Limit' : 'Zero Profit Restrictions'}
</div>
</div>
<div style={{ display: 'flex', gap: '12px', marginTop: 'auto' }}>
<button
disabled={id !== 'elite'}
style={{
flex: 1,
height: '56px',
background: isElite ? '#00ff88' : 'rgba(255,255,255,0.02)',
color: isElite ? 'black' : '#444',
borderRadius: '18px',
border: isElite ? 'none' : '1px solid rgba(255,255,255,0.05)',
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={{
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
height: '4px',
background: `linear-gradient(90deg, transparent, ${themeColor}, transparent)`,
opacity: 0.2
}} />
<style>{`
.membership-card-hover:hover {
border-color: ${themeColor}40 !important;
transform: translateY(-8px);
box-shadow: 0 40px 80px -20px rgba(0,0,0,0.8) !important;
background: #1a1b21 !important;
}
.clone-btn:hover {
filter: brightness(1.1);
transform: scale(1.02);
}
`}</style>
</div>
);
};
export const MembershipTab: React.FC = () => {
return (
<div style={{
maxWidth: '1400px',
margin: '0 auto',
padding: '0 20px 100px 20px',
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={{
display: 'inline-flex',
alignItems: 'center',
gap: '12px',
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 style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))',
gap: '40px',
width: '100%'
}}>
{Object.entries(TIER_POLICIES).map(([id, policy]) => (
<PlanCard key={id} id={id} policy={policy} />
))}
</div>
<style>{`
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
`}</style>
</div>
);
};
import React from 'react';
import {
Crown,
Zap,
Lock,
CheckCircle2,
Sparkles,
Fingerprint,
Cpu,
Target,
Activity,
ArrowUpRight
} from 'lucide-react';
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<{
id: string;
policy: any;
}> = ({ id, policy }) => {
const isElite = id === 'elite';
const isPro = id === 'pro';
const themeColor = isElite ? '#00ff88' : isPro ? '#3498db' : '#929292';
const price = id === 'free' ? '$0' : id === 'pro' ? '$49' : '$199';
const description = id === 'free'
? 'Perfect for learning the fundamentals of automated trading with zero risk.'
: 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 = [
{ label: 'Latency', value: id === 'free' ? 'Standard' : id === 'pro' ? 'Priority' : 'Real-time', icon: <Cpu size={14} /> },
{ label: 'Capacity', value: id === 'free' ? '1 Node' : id === 'pro' ? '5 Nodes' : 'Unlimited', icon: <Target size={14} /> },
{ label: 'Access', value: id === 'free' ? 'Basic' : id === 'pro' ? 'Full' : 'Exclusive', icon: <Fingerprint size={14} /> },
{ label: 'Performance', value: id === 'free' ? 'Capped' : id === 'pro' ? 'Advanced' : 'Unlimited', icon: <Activity size={14} /> }
];
return (
<Card className="h-full rounded-[28px]">
<CardContent className="flex min-h-[620px] flex-col p-8">
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 28 }}>
<div style={{ display: 'flex', gap: 14, alignItems: 'center' }}>
<div style={{
width: 44,
height: 44,
borderRadius: 14,
background: 'var(--card-elevated)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
border: '1px solid var(--border)',
color: themeColor
}}>
{isElite ? <Crown size={22} /> : isPro ? <Zap size={22} fill="currentColor" /> : <Lock size={20} />}
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
<span style={{ fontSize: 10, fontWeight: 900, color: 'var(--muted-foreground)', textTransform: 'uppercase', letterSpacing: '2px' }}>
Tier level
</span>
<span style={{ fontSize: 13, fontWeight: 800, color: 'var(--foreground)', textTransform: 'uppercase', letterSpacing: '0.5px' }}>
{policy.label}
</span>
</div>
</div>
{isElite && (
<span className="stat-chip" data-tone="success">
Peak
</span>
)}
</div>
<div style={{ marginBottom: 24, display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 12 }}>
<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>
<p style={{
fontSize: 14,
color: 'var(--muted-foreground)',
lineHeight: '1.6',
marginBottom: 28,
textAlign: 'left',
flex: 1
}}>
{description}
</p>
<div style={{
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gap: 14,
marginBottom: 28
}}>
{specs.map((spec, i) => (
<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>
<div style={{ display: 'flex', flexDirection: 'column', gap: 10, marginBottom: 32 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 12, fontSize: 12, color: 'var(--muted-foreground)', fontWeight: 700 }}>
<CheckCircle2 size={16} style={{ color: themeColor }} />
{id === 'free' ? 'Safe style only' : id === 'pro' ? 'Safe and balanced' : 'All risk styles unlocked'}
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 12, fontSize: 12, color: 'var(--muted-foreground)', fontWeight: 700 }}>
<CheckCircle2 size={16} style={{ color: themeColor }} />
{id === 'free' ? '$100 profit limit' : id === 'pro' ? '$2,000 profit limit' : 'Zero profit restrictions'}
</div>
</div>
<div style={{ display: 'flex', gap: 12, marginTop: 'auto' }}>
<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 = () => {
return (
<div style={{ maxWidth: 1400, margin: '0 auto', padding: '0 20px 100px 20px' }}>
<PageHeader
title="Membership Plans"
description="Upgrade your algorithmic edge with advanced strategies, higher capacity, and priority execution."
/>
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', marginBottom: 28 }}>
<span className="stat-chip">3 tiers</span>
<span className="stat-chip">Preview pricing</span>
<span className="stat-chip">Product direction only</span>
</div>
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))',
gap: 32,
width: '100%'
}}>
{Object.entries(TIER_POLICIES).map(([id, policy]) => (
<PlanCard key={id} id={id} policy={policy} />
))}
</div>
</div>
);
};

View File

@ -7,6 +7,7 @@ import { VisualRuleBuilder, type VisualRule } from '../components/strategy/Visua
import { createTradeProfile } from '../lib/profileApi';
import { BacktestRunnerPanel } from '../backtest/components/BacktestRunnerPanel';
import { PageHeader } from '../components/ui/page-header';
import { Card, CardContent } from '../components/ui/card';
type ResearchTab = 'Strategies' | 'Visual Builder' | 'Code Editor' | 'Signals' | 'Backtesting';
@ -105,10 +106,10 @@ export function ResearchView() {
{tab === 'Visual Builder' && (
<div>
<div style={{ marginBottom: 16 }}>
<div style={{ fontSize: 15, fontWeight: 700, color: '#111827', marginBottom: 4 }}>
<div className="section-title">
Visual Rule Builder
</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.
</div>
</div>
@ -134,10 +135,10 @@ export function ResearchView() {
{tab === 'Code Editor' && (
<div>
<div style={{ marginBottom: 16 }}>
<div style={{ fontSize: 15, fontWeight: 700, color: '#111827', marginBottom: 4 }}>
<div className="section-title">
Code Strategy Editor
</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.
</div>
</div>
@ -164,23 +165,10 @@ export function ResearchView() {
function CodeStrategyEditorFallback() {
return (
<div
role="status"
aria-label="Loading code strategy editor"
style={{
minHeight: 420,
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>
<Card role="status" aria-label="Loading code strategy editor" className="hero-surface min-h-[420px]">
<CardContent className="flex min-h-[420px] items-center justify-center text-sm font-semibold text-[var(--muted-foreground)]">
Loading code strategy editor
</CardContent>
</Card>
);
}