refactor(web): normalize secondary ux surfaces
This commit is contained in:
parent
266b367322
commit
76d326c793
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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 />
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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 && (
|
||||||
|
|||||||
@ -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 */}
|
||||||
|
|||||||
@ -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 (
|
||||||
|
<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={{
|
<div style={{
|
||||||
background: '#14151a',
|
width: 44,
|
||||||
borderRadius: '28px',
|
height: 44,
|
||||||
border: '1px solid rgba(255, 255, 255, 0.05)',
|
borderRadius: 14,
|
||||||
padding: '32px',
|
background: 'var(--card-elevated)',
|
||||||
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',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
border: '1px solid rgba(255, 255, 255, 0.05)',
|
border: '1px solid var(--border)',
|
||||||
color: themeColor
|
color: themeColor
|
||||||
}}>
|
}}>
|
||||||
{isElite ? <Crown size={22} /> : isPro ? <Zap size={22} fill="currentColor" /> : <Lock size={20} />}
|
{isElite ? <Crown size={22} /> : isPro ? <Zap size={22} fill="currentColor" /> : <Lock size={20} />}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '2px' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||||
<span style={{ fontSize: '10px', fontWeight: 900, color: '#444', textTransform: 'uppercase', letterSpacing: '2px' }}>Tier Level</span>
|
<span style={{ fontSize: 10, fontWeight: 900, color: 'var(--muted-foreground)', textTransform: 'uppercase', letterSpacing: '2px' }}>
|
||||||
<span style={{ fontSize: '13px', fontWeight: 800, color: 'white', textTransform: 'uppercase', letterSpacing: '0.5px' }}>{policy.label}</span>
|
Tier level
|
||||||
|
</span>
|
||||||
|
<span style={{ fontSize: 13, fontWeight: 800, color: 'var(--foreground)', textTransform: 'uppercase', letterSpacing: '0.5px' }}>
|
||||||
|
{policy.label}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isElite && (
|
{isElite && (
|
||||||
<div style={{
|
<span className="stat-chip" data-tone="success">
|
||||||
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
|
Peak
|
||||||
</div>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ marginBottom: '24px', display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: '12px' }}>
|
<div style={{ marginBottom: 24, display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 12 }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'baseline', gap: '4px' }}>
|
<div style={{ display: 'flex', alignItems: 'baseline', gap: 4 }}>
|
||||||
<h3 style={{ fontSize: '42px', fontWeight: 950, color: 'white', letterSpacing: '-0.04em', margin: 0 }}>{price}</h3>
|
<h3 style={{ fontSize: 42, fontWeight: 950, color: 'var(--foreground)', letterSpacing: '-0.04em', margin: 0 }}>{price}</h3>
|
||||||
<span style={{ fontSize: '14px', color: '#444', fontWeight: 900, textTransform: 'uppercase' }}>/mo</span>
|
<span style={{ fontSize: 14, color: 'var(--muted-foreground)', fontWeight: 900, textTransform: 'uppercase' }}>/mo</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{
|
<div className="stat-chip">
|
||||||
display: 'inline-flex',
|
<Sparkles size={14} style={{ color: '#fbbf24' }} />
|
||||||
alignItems: 'center',
|
Professional grade DNA
|
||||||
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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p style={{
|
<p style={{
|
||||||
fontSize: '14px',
|
fontSize: 14,
|
||||||
color: '#888',
|
color: 'var(--muted-foreground)',
|
||||||
lineHeight: '1.6',
|
lineHeight: '1.6',
|
||||||
marginBottom: '28px',
|
marginBottom: 28,
|
||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
flex: 1
|
flex: 1
|
||||||
}}>
|
}}>
|
||||||
{description} Enhanced with Bytelyst's core strategy engine and isolated security nodes.
|
{description}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
gridTemplateColumns: '1fr 1fr',
|
gridTemplateColumns: '1fr 1fr',
|
||||||
gap: '14px',
|
gap: 14,
|
||||||
marginBottom: '28px'
|
marginBottom: 28
|
||||||
}}>
|
}}>
|
||||||
{specs.map((spec, i) => (
|
{specs.map((spec, i) => (
|
||||||
<div key={i} style={{
|
<div key={i} style={{
|
||||||
background: 'rgba(0,0,0,0.2)',
|
background: 'var(--card-elevated)',
|
||||||
border: '1px solid rgba(255,255,255,0.03)',
|
border: '1px solid var(--border)',
|
||||||
padding: '16px',
|
padding: 16,
|
||||||
borderRadius: '20px',
|
borderRadius: 20,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
gap: '6px',
|
gap: 6,
|
||||||
alignItems: 'flex-start'
|
alignItems: 'flex-start'
|
||||||
}}>
|
}}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', color: '#555', fontSize: '10px', fontWeight: 900, textTransform: 'uppercase', letterSpacing: '1px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8, color: 'var(--muted-foreground)', fontSize: 10, fontWeight: 900, textTransform: 'uppercase', letterSpacing: '1px' }}>
|
||||||
{spec.icon} {spec.label}
|
{spec.icon} {spec.label}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ color: 'white', fontWeight: 900, fontSize: '15px' }}>{spec.value}</div>
|
<div style={{ color: 'var(--foreground)', fontWeight: 900, fontSize: 15 }}>{spec.value}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px', marginBottom: '32px' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 10, marginBottom: 32 }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', fontSize: '12px', color: 'rgba(255,255,255,0.4)', fontWeight: 700 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12, fontSize: 12, color: 'var(--muted-foreground)', fontWeight: 700 }}>
|
||||||
<CheckCircle2 size={16} style={{ color: themeColor }} />
|
<CheckCircle2 size={16} style={{ color: themeColor }} />
|
||||||
{id === 'free' ? 'Safe Style Only' : id === 'pro' ? 'Safe & Balanced' : 'All Risk Styles Unlocked'}
|
{id === 'free' ? 'Safe style only' : id === 'pro' ? 'Safe and 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 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12, fontSize: 12, color: 'var(--muted-foreground)', fontWeight: 700 }}>
|
||||||
<CheckCircle2 size={16} style={{ color: themeColor }} />
|
<CheckCircle2 size={16} style={{ color: themeColor }} />
|
||||||
{id === 'free' ? '$100 Profit Limit' : id === 'pro' ? '$2,000 Profit Limit' : 'Zero Profit Restrictions'}
|
{id === 'free' ? '$100 profit limit' : id === 'pro' ? '$2,000 profit limit' : 'Zero profit restrictions'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ display: 'flex', gap: '12px', marginTop: 'auto' }}>
|
<div style={{ display: 'flex', gap: 12, marginTop: 'auto' }}>
|
||||||
<button
|
<Button
|
||||||
disabled={id !== 'elite'}
|
disabled={id !== 'elite'}
|
||||||
style={{
|
variant={isElite ? 'default' : 'outline'}
|
||||||
flex: 1,
|
size="lg"
|
||||||
height: '56px',
|
className="flex-1 rounded-[18px] text-[12px] font-black tracking-[1.5px]"
|
||||||
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} />}
|
{isElite ? 'ACTIVATE PREVIEW' : 'COMING SOON'}
|
||||||
</button>
|
{isElite && <ArrowUpRight size={18} strokeWidth={3} />}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{
|
<div style={{
|
||||||
position: 'absolute',
|
marginTop: 24,
|
||||||
bottom: 0,
|
height: 4,
|
||||||
left: 0,
|
borderRadius: 999,
|
||||||
right: 0,
|
|
||||||
height: '4px',
|
|
||||||
background: `linear-gradient(90deg, transparent, ${themeColor}, transparent)`,
|
background: `linear-gradient(90deg, transparent, ${themeColor}, transparent)`,
|
||||||
opacity: 0.2
|
opacity: 0.2
|
||||||
}} />
|
}} />
|
||||||
|
</CardContent>
|
||||||
<style>{`
|
</Card>
|
||||||
.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 = () => {
|
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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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"
|
|
||||||
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…
|
Loading code strategy editor…
|
||||||
</div>
|
</CardContent>
|
||||||
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user