import React, { useState, useEffect, useCallback } from 'react'; import { View, Text, ScrollView, Switch, StyleSheet, ActivityIndicator } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useRouter } from 'expo-router'; import { LinearGradient } from 'expo-linear-gradient'; import { Colors, Fonts, FontSize, BorderRadius, Shadows, Spacing } from '@/constants/theme'; import { formatCurrency } from '@/utils/format'; 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 TradeProfile { id: string; name: string; is_active: boolean; allocated_capital: number; risk_per_trade_percent: number; symbols: string; strategy_config?: { riskStyle?: string; execution?: { minRulePassRatio?: number }; riskLimits?: { dailyProfitTargetUsd?: number }; }; } const RISK_COLORS: Record = { aggressive: { color: Colors.accent.orange, label: 'Aggressive', icon: '\u{1F525}' }, balanced: { color: Colors.accent.green, label: 'Balanced', icon: '\u{2696}\u{FE0F}' }, safe: { color: Colors.accent.blue, label: 'Conservative', icon: '\u{1F6E1}\u{FE0F}' }, }; function StrategyCard({ profile, index, onToggle }: { profile: TradeProfile; index: number; onToggle: (id: string, isActive: boolean) => Promise; }) { const [isActive, setIsActive] = useState(profile.is_active); const [toggling, setToggling] = useState(false); const riskStyle = profile.strategy_config?.riskStyle || 'balanced'; const risk = RISK_COLORS[riskStyle] || RISK_COLORS.balanced; const dailyTarget = profile.strategy_config?.riskLimits?.dailyProfitTargetUsd ?? 0; const symbolList = profile.symbols ? profile.symbols.split(',').map(s => s.trim()) : []; const handleToggle = async (newValue: boolean) => { setIsActive(newValue); setToggling(true); try { await onToggle(profile.id, newValue); } catch { setIsActive(!newValue); } finally { setToggling(false); } }; return ( {profile.name} {symbolList.map(s => ( {s} ))} {formatCurrency(profile.allocated_capital)} allocated ยท {profile.risk_per_trade_percent}% risk/trade {dailyTarget > 0 && ( Daily target: {formatCurrency(dailyTarget)} )} ); } export default function StrategiesScreen() { const insets = useSafeAreaInsets(); const router = useRouter(); const { accessToken } = useMobileAuth(); const [profiles, setProfiles] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const fetchProfiles = useCallback(async () => { if (!accessToken) return; try { const res = await fetch(`${mobileRuntime.tradingApiUrl}/profiles`, { headers: { Authorization: `Bearer ${accessToken}`, 'x-request-id': createRequestId('mobile-strategies'), }, }); if (!res.ok) throw new Error(`Failed to load profiles (${res.status})`); const body = await res.json(); setProfiles(Array.isArray(body.profiles) ? body.profiles : []); setError(null); } catch (e) { setError(e instanceof Error ? e.message : 'Failed to load strategies'); } finally { setLoading(false); } }, [accessToken]); useEffect(() => { void fetchProfiles(); }, [fetchProfiles]); const handleToggle = async (id: string, isActive: boolean) => { if (!accessToken) return; const res = await fetch(`${mobileRuntime.tradingApiUrl}/profiles/${id}/active`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${accessToken}`, 'x-request-id': createRequestId('mobile-toggle'), }, body: JSON.stringify({ is_active: isActive }), }); if (!res.ok) throw new Error(`Toggle failed (${res.status})`); }; return ( STRATEGIES My Strategies {loading ? ( ) : error ? ( Failed to load strategies {error} ) : profiles.length === 0 ? ( No strategies yet Create a strategy from the marketplace or web dashboard. ) : ( profiles.map((p, i) => ( )) )} router.push('/marketplace')} style={styles.ctaWrapper} > EXPLORE MARKETPLACE ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: Colors.background.primary, }, headerSection: { padding: Spacing.screenPadding, paddingBottom: 0, }, sectionLabel: { fontFamily: Fonts.inter.black, fontSize: FontSize.micro, color: Colors.accent.green, letterSpacing: 4, marginBottom: 8, }, pageTitle: { fontFamily: Fonts.inter.black, fontSize: FontSize.hero, color: Colors.text.primary, letterSpacing: -0.5, marginBottom: 16, }, scroll: { flex: 1, }, content: { padding: Spacing.screenPadding, gap: 16, paddingBottom: 120, }, card: { borderRadius: BorderRadius.large, padding: Spacing.cardPaddingLarge, borderWidth: 1, borderColor: Colors.border.default, overflow: 'hidden', gap: 12, }, accentLine: { position: 'absolute', top: 0, left: 0, right: 0, height: 2, opacity: 0.6, }, cardHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, stratName: { fontFamily: Fonts.inter.black, fontSize: 18, color: Colors.text.primary, flex: 1, marginRight: 12, }, 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, }, capitalRow: { gap: 2, }, capitalLabel: { fontFamily: Fonts.mono.medium, fontSize: FontSize.bodySmall, color: Colors.text.secondary, }, ctaWrapper: { marginTop: 8, borderRadius: 18, shadowColor: 'rgba(0,255,136,0.4)', shadowOffset: { width: 0, height: 12 }, shadowOpacity: 1, shadowRadius: 36, elevation: 8, }, 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, }, });