133 lines
3.5 KiB
TypeScript
133 lines
3.5 KiB
TypeScript
import React from 'react';
|
|
import { View, Text, StyleSheet } from 'react-native';
|
|
import { Colors, Fonts, FontSize, BorderRadius } from '@/constants/theme';
|
|
import AnimatedCard from '@/components/AnimatedCard';
|
|
import { useTradingData } from '@/providers/TradingDataProvider';
|
|
|
|
type AlertType = 'signal' | 'error' | 'pulse' | 'info';
|
|
|
|
const ALERT_COLORS: Record<AlertType, string> = {
|
|
signal: Colors.accent.green,
|
|
pulse: '#4da6ff',
|
|
error: Colors.accent.red,
|
|
info: '#888888',
|
|
};
|
|
|
|
const ALERT_ICONS: Record<AlertType, string> = {
|
|
signal: '\u{1F680}',
|
|
pulse: '\u{23F0}',
|
|
error: '\u{26A0}\u{FE0F}',
|
|
info: '\u{2139}\u{FE0F}',
|
|
};
|
|
|
|
export default function ActiveAlerts() {
|
|
const { botState } = useTradingData();
|
|
const alerts = (botState?.alerts || []).slice(0, 5).map((alert, index) => ({
|
|
id: `${alert.symbol}-${alert.timestamp}-${index}`,
|
|
...alert,
|
|
timestampLabel: formatTimeAgo(alert.timestamp),
|
|
}));
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<View style={styles.header}>
|
|
<Text style={styles.title}>RECENT ALERTS</Text>
|
|
<View style={styles.countBadge}>
|
|
<Text style={styles.countText}>{alerts.length}</Text>
|
|
</View>
|
|
</View>
|
|
|
|
{alerts.map((alert, index) => (
|
|
<AnimatedCard key={alert.id} index={index} style={styles.alertCard}>
|
|
<View style={[styles.leftBorder, { backgroundColor: ALERT_COLORS[alert.type] }]} />
|
|
<View style={styles.alertContent}>
|
|
<View style={styles.alertHeader}>
|
|
<Text style={styles.icon}>{ALERT_ICONS[alert.type]}</Text>
|
|
<Text style={styles.symbol}>{alert.symbol}</Text>
|
|
<Text style={styles.timestamp}>{alert.timestampLabel}</Text>
|
|
</View>
|
|
<Text style={styles.message}>{alert.message}</Text>
|
|
</View>
|
|
</AnimatedCard>
|
|
))}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
function formatTimeAgo(timestamp: number) {
|
|
const deltaSeconds = Math.max(0, Math.floor((Date.now() - timestamp) / 1000));
|
|
if (deltaSeconds < 60) return `${deltaSeconds}s ago`;
|
|
if (deltaSeconds < 3600) return `${Math.floor(deltaSeconds / 60)}m ago`;
|
|
return `${Math.floor(deltaSeconds / 3600)}h ago`;
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
gap: 10,
|
|
},
|
|
header: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 8,
|
|
marginBottom: 4,
|
|
},
|
|
title: {
|
|
fontFamily: Fonts.inter.black,
|
|
fontSize: FontSize.micro,
|
|
color: Colors.text.secondary,
|
|
letterSpacing: 3,
|
|
},
|
|
countBadge: {
|
|
backgroundColor: 'rgba(0,255,136,0.15)',
|
|
paddingHorizontal: 8,
|
|
paddingVertical: 2,
|
|
borderRadius: 6,
|
|
},
|
|
countText: {
|
|
fontFamily: Fonts.mono.bold,
|
|
fontSize: FontSize.micro,
|
|
color: Colors.accent.green,
|
|
},
|
|
alertCard: {
|
|
backgroundColor: 'rgba(255,255,255,0.03)',
|
|
borderRadius: BorderRadius.small,
|
|
borderWidth: 1,
|
|
borderColor: Colors.border.subtle,
|
|
flexDirection: 'row',
|
|
overflow: 'hidden',
|
|
},
|
|
leftBorder: {
|
|
width: 3,
|
|
},
|
|
alertContent: {
|
|
flex: 1,
|
|
padding: 14,
|
|
gap: 6,
|
|
},
|
|
alertHeader: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 8,
|
|
},
|
|
icon: {
|
|
fontSize: 14,
|
|
},
|
|
symbol: {
|
|
fontFamily: Fonts.inter.extraBold,
|
|
fontSize: FontSize.badge,
|
|
color: Colors.text.primary,
|
|
},
|
|
timestamp: {
|
|
fontFamily: Fonts.inter.medium,
|
|
fontSize: FontSize.micro,
|
|
color: Colors.text.secondary,
|
|
marginLeft: 'auto',
|
|
},
|
|
message: {
|
|
fontFamily: Fonts.inter.medium,
|
|
fontSize: FontSize.body,
|
|
color: Colors.text.primary,
|
|
lineHeight: 18,
|
|
},
|
|
});
|