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 { 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>
|
||||
);
|
||||
|
||||
@ -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 />
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 && (
|
||||
|
||||
@ -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' }}>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user