learning_ai_invt_trdg/web/src/components/PresetMarketplace.tsx

224 lines
9.9 KiB
TypeScript

import React, { useEffect, useMemo, useState } from 'react';
import { fetchMarketplacePresets } from '../lib/marketplaceApi';
import {
Activity,
ArrowUpRight,
CheckCircle,
Cpu,
Fingerprint,
Info,
LineChart,
Scale,
Shield,
TrendingUp,
Users,
Zap,
} from 'lucide-react';
import type { StrategyPreset } from '../lib/PresetRegistry';
import { STRATEGY_PRESETS } from '../lib/PresetRegistry';
import { Button } from './ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card';
import { PageHeader } from './ui/page-header';
interface PresetMarketplaceProps {
onSelect: (preset: StrategyPreset) => void;
onClose?: () => void;
}
const themeByRisk: Record<string, { tone: 'safe' | 'balanced' | 'aggressive'; label: string }> = {
safe: { tone: 'safe', label: 'Low Volatility' },
balanced: { tone: 'balanced', label: 'Balanced' },
aggressive: { tone: 'aggressive', label: 'High Conviction' },
};
function metricForPreset(preset: StrategyPreset) {
if (preset.riskStyleId === 'aggressive') {
return { performance: '+14.2%', volatility: 'High', icon: Zap, accent: 'var(--destructive)' };
}
if (preset.riskStyleId === 'safe') {
return { performance: '+4.8%', volatility: 'Low', icon: Shield, accent: 'var(--primary)' };
}
return { performance: '+8.5%', volatility: 'Medium', icon: Scale, accent: 'var(--ring)' };
}
const StrategyMarketplaceCard: React.FC<{
preset: StrategyPreset;
onSelect: (preset: StrategyPreset) => void;
index: number;
}> = ({ preset, onSelect, index }) => {
const theme = themeByRisk[preset.riskStyleId] || themeByRisk.balanced;
const metric = metricForPreset(preset);
const RiskIcon = metric.icon;
return (
<Card className="h-full rounded-[28px] transition duration-200 hover:-translate-y-1 hover:border-[var(--ring)]/30 hover:shadow-xl">
<CardHeader className="gap-5">
<div className="flex items-start justify-between gap-4">
<div className="flex items-center gap-3">
<div
className="flex h-11 w-11 items-center justify-center rounded-2xl border"
style={{
background: 'var(--accent-soft)',
borderColor: 'var(--border)',
color: metric.accent,
}}
>
<RiskIcon size={18} />
</div>
<div className="space-y-1">
<div className="text-[10px] font-semibold uppercase tracking-[0.2em] text-[var(--muted-foreground)]">
Strategy Profile
</div>
<div className="text-xs font-semibold uppercase tracking-wide text-[var(--foreground)]">
{preset.riskStyleId} strategy
</div>
</div>
</div>
<div className="stat-chip">V{index + 1}.4</div>
</div>
<div className="space-y-3">
<CardTitle className="text-2xl">{preset.name}</CardTitle>
<div
className="inline-flex w-fit items-center gap-2 rounded-lg border px-3 py-1.5 text-[11px] font-semibold uppercase tracking-wide"
style={{
background: 'var(--accent-soft)',
borderColor: 'var(--border)',
color: metric.accent,
}}
>
<Fingerprint size={13} />
{theme.label} {metric.performance}
</div>
<CardDescription className="text-sm leading-6">
{preset.description} Optimized for automated execution without changing your core risk budget.
</CardDescription>
</div>
</CardHeader>
<CardContent className="flex h-full flex-col gap-6">
<div className="grid grid-cols-2 gap-3">
{[
{ label: 'Growth', value: metric.performance, icon: <TrendingUp size={14} /> },
{ label: 'Latency', value: 'Low', icon: <Cpu size={14} /> },
{ label: 'Liquidity', value: 'Prime', icon: <Users size={14} /> },
{ label: 'Volatility', value: metric.volatility, icon: <Activity size={14} /> },
].map((spec) => (
<div
key={spec.label}
className="rounded-2xl border p-4"
style={{ background: 'var(--card-elevated)', borderColor: 'var(--border)' }}
>
<div className="mb-2 flex items-center gap-2 text-[10px] font-semibold uppercase tracking-[0.14em] text-[var(--muted-foreground)]">
{spec.icon}
{spec.label}
</div>
<div className="text-base font-semibold text-[var(--foreground)]">{spec.value}</div>
</div>
))}
</div>
<div className="space-y-2 text-sm text-[var(--muted-foreground)]">
<div className="flex items-center gap-2">
<CheckCircle size={15} className="text-[var(--primary)]" />
Logical invariant verified
</div>
<div className="flex items-center gap-2">
<CheckCircle size={15} className="text-[var(--primary)]" />
Risk-isolated execution
</div>
</div>
<div className="mt-auto flex gap-3">
<Button className="h-12 flex-1 rounded-2xl" onClick={() => onSelect(preset)}>
Use This Strategy
<ArrowUpRight size={15} />
</Button>
<Button
variant="outline"
size="icon"
className="h-12 w-12 rounded-2xl"
title="Preset information"
>
<Info size={18} />
</Button>
</div>
</CardContent>
</Card>
);
};
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 StrategyPreset[]);
} catch (e) {
console.error('Error fetching marketplace presets:', e);
}
};
void fetchCustomPresets();
}, []);
const allPresets = useMemo(() => [...STRATEGY_PRESETS, ...customPresets], [customPresets]);
return (
<div className="space-y-8">
<div className="flex flex-wrap items-start justify-between gap-4">
<PageHeader
title="Strategy Marketplace"
description="Browse reusable strategy profiles with preconfigured risk posture and execution bias."
/>
<div className="flex items-center gap-3">
<div className="stat-chip">{allPresets.length} presets</div>
{onClose ? (
<Button variant="outline" onClick={onClose}>
Return
</Button>
) : null}
</div>
</div>
<div className="grid grid-cols-1 gap-6 xl:grid-cols-3">
{allPresets.map((preset, idx) => (
<StrategyMarketplaceCard key={preset.id} preset={preset} index={idx} onSelect={onSelect} />
))}
<Card className="rounded-[28px] border-dashed">
<CardContent className="flex min-h-[560px] flex-col items-center justify-center gap-4 px-8 py-10 text-center">
<div
className="flex h-16 w-16 items-center justify-center rounded-3xl border"
style={{ background: 'var(--accent-soft)', borderColor: 'var(--border)' }}
>
<LineChart size={28} className="text-[var(--muted-foreground)]" />
</div>
<div className="space-y-2">
<div className="text-xs font-semibold uppercase tracking-[0.2em] text-[var(--muted-foreground)]">
Analyzing DNA
</div>
<p className="mx-auto max-w-xs text-sm text-[var(--muted-foreground)]">
Verification queue is active for new marketplace strategies.
</p>
</div>
</CardContent>
</Card>
</div>
</div>
);
};