learning_ai_invt_trdg/web/src/components/PresetMarketplace.tsx

402 lines
17 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { fetchMarketplacePresets } from '../lib/marketplaceApi';
import {
Activity,
ArrowUpRight,
Shield,
Zap,
Scale,
CheckCircle,
TrendingUp,
Info,
Dna,
Cpu,
Fingerprint,
Users,
LineChart
} from 'lucide-react';
import type { StrategyPreset } from '../lib/PresetRegistry';
import { STRATEGY_PRESETS } from '../lib/PresetRegistry';
interface PresetMarketplaceProps {
onSelect: (preset: StrategyPreset) => void;
onClose?: () => void;
}
const StrategyMarketplaceCard: React.FC<{
preset: StrategyPreset,
onSelect: (preset: StrategyPreset) => void,
index: number
}> = ({ preset, onSelect, index }) => {
const isSafe = preset.riskStyleId === 'safe';
const isBalanced = preset.riskStyleId === 'balanced';
const isAggressive = preset.riskStyleId === 'aggressive';
const themeColor = isSafe ? '#00ff88' : isBalanced ? '#3498db' : '#ff3366';
// Visual metadata
const performanceValue = isAggressive ? '+14.2%' : isSafe ? '+4.8%' : '+8.5%';
const volatilityRating = isAggressive ? 'High' : isSafe ? 'Low' : 'Med';
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="marketplace-card-hover">
{/* 1. Header Area - Perfectly Aligned */}
<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: 'rgba(255,255,255,0.5)'
}}>
{isSafe ? <Shield size={20} /> : isBalanced ? <Scale size={20} /> : <Zap size={20} />}
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '2px' }}>
<span style={{ fontSize: '10px', fontWeight: 900, color: '#444', textTransform: 'uppercase', letterSpacing: '2px' }}>Strategy Profile</span>
<span style={{ fontSize: '13px', fontWeight: 800, color: 'white', textTransform: 'uppercase', letterSpacing: '0.5px' }}>{preset.riskStyleId} Strategy</span>
</div>
</div>
<div style={{
background: 'rgba(0,0,0,0.3)',
border: '1px solid rgba(255,255,255,0.05)',
padding: '6px 12px',
borderRadius: '10px',
fontSize: '11px',
fontWeight: 900,
color: '#333'
}}>
V{index + 1}.4
</div>
</div>
{/* 2. Identity - Aligned Left */}
<div style={{ marginBottom: '24px', display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: '12px' }}>
<h3 style={{
fontSize: '24px',
fontWeight: 900,
color: 'white',
lineHeight: '1.1',
letterSpacing: '-0.02em',
margin: 0
}}>
{preset.name}
</h3>
<div style={{
display: 'inline-flex',
alignItems: 'center',
gap: '8px',
background: 'rgba(0, 255, 136, 0.05)',
padding: '6px 14px',
borderRadius: '8px',
fontSize: '11px',
color: '#00ff88',
fontWeight: 900,
textTransform: 'uppercase',
border: '1px solid rgba(0, 255, 136, 0.1)',
letterSpacing: '0.5px'
}}>
<Fingerprint size={14} /> Institutional Alpha {performanceValue}
</div>
</div>
<p style={{
fontSize: '14px',
color: '#888',
lineHeight: '1.6',
marginBottom: '28px',
textAlign: 'left',
flex: 1
}}>
{preset.description} Optimized for dominance and high-conviction momentum in volatile periods.
</p>
{/* 3. Specs Grid - Balanced Alignment */}
<div style={{
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gap: '14px',
marginBottom: '28px'
}}>
{[
{ label: 'Growth', value: performanceValue, icon: <TrendingUp size={14} />, color: '#00ff88' },
{ label: 'Latency', value: 'Low', icon: <Cpu size={14} />, color: '#3498db' },
{ label: 'Liquidity', value: 'Prime', icon: <Users size={14} />, color: '#ffaa00' },
{ label: 'Rating', value: volatilityRating, icon: <Activity size={14} />, color: '#ff3366' }
].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: '16px' }}>{spec.value}</div>
</div>
))}
</div>
{/* 4. Verifications - Left Justified */}
<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 }}>
<CheckCircle size={16} style={{ color: '#00ff88' }} /> Logical Invariant Verified
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', fontSize: '12px', color: 'rgba(255,255,255,0.4)', fontWeight: 700 }}>
<CheckCircle size={16} style={{ color: '#00ff88' }} /> Risk-Isolated Execution
</div>
</div>
{/* 5. Action - Consistent Alignment */}
<div style={{ display: 'flex', gap: '12px', marginTop: 'auto' }}>
<button
onClick={() => onSelect(preset)}
style={{
flex: 1,
height: '56px',
background: '#00ff88',
color: 'black',
borderRadius: '18px',
border: 'none',
fontWeight: 900,
fontSize: '12px',
textTransform: 'uppercase',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '10px',
boxShadow: '0 12px 36px -12px rgba(0, 255, 136, 0.4)',
transition: 'all 0.2s',
letterSpacing: '1px'
}}
className="clone-btn"
>
USE THIS STRATEGY <ArrowUpRight size={16} strokeWidth={3} />
</button>
<button style={{
width: '56px',
height: '56px',
borderRadius: '18px',
border: '1px solid rgba(255,255,255,0.05)',
background: 'rgba(255,255,255,0.02)',
color: 'rgba(255,255,255,0.4)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer'
}}>
<Info size={22} />
</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>{`
.marketplace-card-hover:hover {
border-color: rgba(0, 255, 136, 0.3) !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 PresetMarketplace: React.FC<PresetMarketplaceProps> = ({ onSelect, onClose }) => {
const [customPresets, setCustomPresets] = useState<StrategyPreset[]>([]);
useEffect(() => {
const fetchCustomPresets = async () => {
try {
const data = await fetchMarketplacePresets();
const mappedData = data.map((d: any) => ({
id: d.id,
name: d.name,
description: d.description,
riskStyleId: d.risk_style_id,
recommendedAssets: d.recommended_assets,
typicalTradesPerDay: d.typical_trades_per_day,
performanceTag: d.performance_tag,
isPopular: d.is_popular,
strategy_config: d.strategy_config
}));
setCustomPresets(mappedData as any);
} catch (e) {
console.error('Error fetching marketplace presets:', e);
}
};
fetchCustomPresets();
}, []);
const allPresets = [...STRATEGY_PRESETS, ...customPresets];
return (
<div style={{
maxWidth: '1400px',
margin: '0 auto',
padding: '0 20px 100px 20px',
animation: 'fadeIn 0.7s ease-out'
}}>
{/* Premium Header Alignment Fix */}
<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' /* Force consistent left alignment */
}}>
<div style={{
position: 'absolute',
top: 0,
right: 0,
opacity: 0.03,
pointerEvents: 'none',
transform: 'translate(40px, -20px)'
}}>
<Dna size={320} strokeWidth={1} />
</div>
{/* Removed indenting line for perfect optical left-alignment */}
<div style={{
display: 'inline-flex',
alignItems: 'center',
gap: '12px',
color: '#00ff88',
fontSize: '11px',
fontWeight: 900,
textTransform: 'uppercase',
letterSpacing: '4px',
marginBottom: '24px'
}}>
QUANTITATIVE REPOSITORY
<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'
}}>
Strategy<br />
<span style={{ color: '#00ff88' }}>Marketplace</span>
</h2>
<p style={{ fontSize: '20px', color: '#666', marginTop: '24px', maxWidth: '600px', fontWeight: 500, margin: '24px 0 0 0', textAlign: 'left' }}>
Institutional-grade algorithm DNA for automated retail deployment.
</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' }}>Profiles</span>
<span style={{ color: 'white', fontSize: '32px', fontWeight: 950, lineHeight: '1' }}>{allPresets.length}</span>
</div>
{onClose && (
<button
onClick={onClose}
style={{
background: 'none',
border: '1px solid rgba(255,255,255,0.1)',
color: 'white',
padding: '14px 36px',
borderRadius: '16px',
cursor: 'pointer',
fontWeight: 900,
fontSize: '12px',
textTransform: 'uppercase',
letterSpacing: '1.5px',
transition: 'all 0.2s'
}}
>
Return
</button>
)}
</div>
</div>
</div>
{/* Grid Layout - Perfectly Symmetrical */}
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))',
gap: '40px',
width: '100%'
}}>
{allPresets.map((preset, idx) => (
<StrategyMarketplaceCard key={preset.id} preset={preset} index={idx} onSelect={onSelect} />
))}
{/* DNA Loader Placeholder - Aligned Center */}
<div style={{
background: 'rgba(255,255,255,0.01)',
border: '2px dashed rgba(255,255,255,0.03)',
borderRadius: '28px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
minHeight: '620px',
padding: '40px',
textAlign: 'center'
}}>
<LineChart size={56} style={{ color: '#1a1a1a', marginBottom: '24px' }} />
<span style={{ color: '#222', fontWeight: 900, textTransform: 'uppercase', letterSpacing: '4px', fontSize: '12px' }}>Analyzing DNA</span>
<span style={{ color: '#111', fontSize: '13px', marginTop: '12px', maxWidth: '200px', fontWeight: 700 }}>Verification queue currently active for new strategies.</span>
</div>
</div>
<style>{`
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
`}</style>
</div>
);
};