import React, { useState, useEffect, useCallback } from 'react'; import { View, Text, ScrollView, Pressable, StyleSheet, ActivityIndicator, Alert } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useRouter } from 'expo-router'; import { LinearGradient } from 'expo-linear-gradient'; import { ArrowLeft } from 'lucide-react-native'; import { Colors, Fonts, FontSize, BorderRadius, Shadows, Spacing } from '@/constants/theme'; import AnimatedCard from '@/components/AnimatedCard'; import PillBadge from '@/components/PillBadge'; import PressableScale from '@/components/PressableScale'; import { useMobileAuth } from '@/providers/MobileAuthProvider'; import { mobileRuntime } from '@/lib/runtime'; import { createRequestId } from '../../shared/request-id.js'; interface MarketplacePreset { id: string; name: string; description: string; risk_style_id: string; recommended_assets: string[]; typical_trades_per_day: string; performance_tag: string; is_popular: boolean; strategy_config?: Record; } const RISK_COLORS: Record = { aggressive: { color: Colors.accent.orange, label: 'Aggressive' }, balanced: { color: Colors.accent.green, label: 'Balanced' }, safe: { color: Colors.accent.blue, label: 'Conservative' }, scalping: { color: Colors.accent.purple, label: 'Scalping' }, swing: { color: Colors.accent.amber, label: 'Swing' }, }; export default function MarketplaceScreen() { const insets = useSafeAreaInsets(); const router = useRouter(); const { accessToken, user } = useMobileAuth(); const [presets, setPresets] = useState([]); const [loading, setLoading] = useState(true); const [applyingId, setApplyingId] = useState(null); const fetchPresets = useCallback(async () => { if (!accessToken) return; try { const res = await fetch(`${mobileRuntime.tradingApiUrl}/marketplace-presets`, { headers: { Authorization: `Bearer ${accessToken}`, 'x-request-id': createRequestId('mobile-marketplace'), }, }); if (!res.ok) throw new Error(`Failed to load presets (${res.status})`); const body = await res.json(); setPresets(Array.isArray(body.presets) ? body.presets : []); } catch (e) { Alert.alert('Error', e instanceof Error ? e.message : 'Failed to load marketplace'); } finally { setLoading(false); } }, [accessToken]); useEffect(() => { void fetchPresets(); }, [fetchPresets]); const handleUseStrategy = async (preset: MarketplacePreset) => { if (!accessToken || !user?.id) return; setApplyingId(preset.id); try { const res = await fetch(`${mobileRuntime.tradingApiUrl}/profiles`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${accessToken}`, 'x-request-id': createRequestId('mobile-use-strategy'), }, body: JSON.stringify({ name: `${preset.name} (Mobile)`, user_id: user.id, allocated_capital: 1000, risk_per_trade_percent: 1, symbols: (preset.recommended_assets || []).join(','), is_active: false, strategy_config: preset.strategy_config || {}, }), }); if (!res.ok) { const body = await res.json().catch(() => ({})); throw new Error((body as { error?: string }).error || `Failed to create profile (${res.status})`); } Alert.alert('Strategy Added', `"${preset.name}" has been added to your strategies. You can activate it from the Strategies tab.`, [ { text: 'OK', onPress: () => router.back() }, ]); } catch (e) { Alert.alert('Error', e instanceof Error ? e.message : 'Failed to apply strategy'); } finally { setApplyingId(null); } }; return ( router.back()} style={styles.backBtn}> MARKETPLACE Strategy Marketplace {loading ? ( ) : ( {presets.length === 0 ? ( No strategies available Check back later or create a custom strategy from the web dashboard. ) : presets.map((preset, index) => { const risk = RISK_COLORS[preset.risk_style_id] || RISK_COLORS.balanced; const isApplying = applyingId === preset.id; return ( {preset.is_popular && ( Popular )} {preset.name} {preset.description} {preset.typical_trades_per_day} {(preset.recommended_assets || []).map(a => ( {a} ))} {preset.performance_tag} void handleUseStrategy(preset)} > {isApplying ? 'ADDING...' : 'USE STRATEGY'} ); })} )} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: Colors.background.primary, }, headerSection: { padding: Spacing.screenPadding, flexDirection: 'row', alignItems: 'center', gap: 14, }, backBtn: { width: 40, height: 40, borderRadius: 12, backgroundColor: Colors.background.card, alignItems: 'center', justifyContent: 'center', borderWidth: 1, borderColor: Colors.border.default, }, sectionLabel: { fontFamily: Fonts.inter.black, fontSize: FontSize.micro, color: Colors.accent.green, letterSpacing: 4, marginBottom: 4, }, pageTitle: { fontFamily: Fonts.inter.black, fontSize: FontSize.heading, color: Colors.text.primary, }, scroll: { flex: 1, }, content: { padding: Spacing.screenPadding, gap: 20, paddingBottom: 60, }, cardOuter: { borderRadius: 28, }, card: { backgroundColor: Colors.background.card, borderRadius: 28, padding: 32, borderWidth: 1, borderColor: Colors.border.default, gap: 14, }, popularBadge: { alignSelf: 'flex-start', backgroundColor: 'rgba(0,255,136,0.05)', borderWidth: 1, borderColor: 'rgba(0,255,136,0.1)', paddingHorizontal: 12, paddingVertical: 4, borderRadius: 8, }, popularText: { fontFamily: Fonts.inter.bold, fontSize: FontSize.badge, color: Colors.accent.green, }, presetName: { fontFamily: Fonts.inter.black, fontSize: 18, color: Colors.text.primary, }, presetDesc: { fontFamily: Fonts.inter.medium, fontSize: FontSize.body, color: Colors.text.secondary, lineHeight: 20, }, metaRow: { flexDirection: 'row', alignItems: 'center', gap: 12, }, tradesPerDay: { fontFamily: Fonts.mono.medium, fontSize: FontSize.bodySmall, color: Colors.text.secondary, }, assetPills: { flexDirection: 'row', gap: 6, flexWrap: 'wrap', }, assetPill: { backgroundColor: 'rgba(255,255,255,0.05)', paddingHorizontal: 10, paddingVertical: 4, borderRadius: BorderRadius.xs, }, assetText: { fontFamily: Fonts.mono.medium, fontSize: FontSize.micro, color: Colors.text.secondary, }, tagRow: { marginTop: 2, }, tagText: { fontFamily: Fonts.mono.bold, fontSize: FontSize.body, color: Colors.accent.green, }, ctaWrapper: { marginTop: 6, borderRadius: 18, shadowColor: 'rgba(0,255,136,0.4)', shadowOffset: { width: 0, height: 12 }, shadowOpacity: 1, shadowRadius: 36, elevation: 8, }, ctaDisabled: { opacity: 0.6, }, ctaButton: { height: 56, borderRadius: 18, alignItems: 'center', justifyContent: 'center', }, ctaText: { fontFamily: Fonts.inter.black, fontSize: FontSize.bodySmall, color: '#000', letterSpacing: 2, }, emptyState: { alignItems: 'center', justifyContent: 'center', paddingVertical: 60, gap: 10, }, emptyText: { fontFamily: Fonts.inter.bold, fontSize: FontSize.subheading, color: Colors.text.secondary, }, emptyHint: { fontFamily: Fonts.inter.medium, fontSize: FontSize.body, color: Colors.text.muted, textAlign: 'center', maxWidth: 260, lineHeight: 20, }, });