402 lines
17 KiB
TypeScript
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>
|
|
);
|
|
};
|