function walletAddressKey(userId: string) {
return `walletAddress:${userId}`;
}
+
function normalizeSymbol(s: string) {
return s.trim().toUpperCase();
}
+
function mergePortfolios(base: PortfolioState, patch: PortfolioState): PortfolioState {
const map = new Map<string, number>();
for (const a of base.assets) map.set(normalizeSymbol(a.symbol), a.quantity);
const [userId, setUserId] = useState<string | null>(null);
const [settings, setSettings] = useState<UserSettings | null>(null);
- const [portfolio, setPortfolio] = useState<PortfolioState>({ assets: [], updatedAtMs: Date.now() });
+ const [portfolio, setPortfolio] = useState<PortfolioState>({
+ assets: [],
+ updatedAtMs: Date.now(),
+ });
const [selectedCrypto, setSelectedCrypto] = useState<CryptoSymbol>("BTC");
const urgentAlert = useMemo(() => {
if (liveAlerts.length === 0) return null;
+
const critical = liveAlerts.find((a) => String(a.alertLevel).toUpperCase() === "CRITICAL");
if (critical) return critical;
+
const warning = liveAlerts.find((a) => String(a.alertLevel).toUpperCase() === "WARNING");
if (warning) return warning;
+
return liveAlerts[0];
}, [liveAlerts]);
setWalletAddress("");
}
+ // Dashboard summary (prix+signal)
try {
const dash = await getDashboardSummary();
setSummary(dash);
setSoftError(`Signal/Prix indisponibles (API). DEV=${ENV_MODE}. Base REST: ${API_BASE_URL}`);
}
+ // Wallet API (si dispo) -> fusion
if (uid) {
try {
const apiPortfolio = await getPortfolioFromApi();
}
}
+ // Alertes REST (liste)
if (uid) {
try {
const history = await getAlertHistory(10);
if (alive) setLoading(false);
}
})();
+
return () => {
alive = false;
};
}, [refreshAll])
);
- // ✅ FIX string|null : on connecte socket seulement si uid existe
+ // Socket live (✅ connect seulement si userId existe)
useEffect(() => {
let unsub: null | (() => void) = null;
let alive = true;
</View>
)}
+ {/* Compte */}
<View style={ui.card}>
<Text style={ui.title}>Compte utilisateur</Text>
<View style={ui.rowBetween}>
- <Text style={ui.muted}>UserId : <Text style={styles.bold}>{userId ?? "—"}</Text></Text>
+ <Text style={ui.muted}>
+ UserId : <Text style={styles.bold}>{userId ?? "—"}</Text>
+ </Text>
<Text style={ui.muted}>Socket : {socketConnected ? "ON ✅" : "OFF ⚠️"}</Text>
</View>
{!!socketInfo && <Text style={[ui.muted, { marginTop: 6 }]}>{socketInfo}</Text>}
</View>
+ {/* Crypto + adresse */}
<View style={ui.card}>
<Text style={ui.title}>Choisir une cryptomonnaie</Text>
{!!walletAddressInfo && <Text style={[ui.muted, { marginTop: 8 }]}>{walletAddressInfo}</Text>}
</View>
+ {/* Solde */}
+ <View style={ui.card}>
+ <Text style={ui.title}>Solde</Text>
+ <Text style={styles.bigValue}>
+ {selectedQty.toFixed(6)} {selectedCrypto}
+ </Text>
+ <Text style={ui.muted}>Portefeuille local (+ sync serveur si dispo)</Text>
+ </View>
+
+ {/* Prix */}
<View style={ui.card}>
<Text style={ui.title}>Prix</Text>
<Text style={ui.muted}>{pair}</Text>
- <Text style={styles.bigValue}>{(summary?.price ?? 0).toFixed(2)} {currency}</Text>
+ <Text style={styles.bigValue}>
+ {(summary?.price ?? 0).toFixed(2)} {currency}
+ </Text>
+
<TouchableOpacity style={[ui.button, { marginTop: 10 }]} onPress={() => void refreshAll()}>
<Text style={ui.buttonText}>Actualiser</Text>
</TouchableOpacity>
</View>
+ {/* Signal */}
<View style={ui.card}>
<Text style={ui.title}>Signal du marché</Text>
{summary ? (
<>
<Text style={styles.signalDecision}>{summary.decision}</Text>
- <Text style={ui.muted}>{summary.alertLevel} — Confiance {Math.round(summary.confidence * 100)}%</Text>
- <Text style={[ui.muted, { marginTop: 6 }]} numberOfLines={3}>{summary.reason}</Text>
+ <Text style={ui.muted}>
+ {summary.alertLevel} — Confiance {Math.round(summary.confidence * 100)}%
+ </Text>
+ <Text style={[ui.muted, { marginTop: 6 }]} numberOfLines={3}>
+ {summary.reason}
+ </Text>
</>
) : (
<Text style={ui.muted}>Aucune donnée pour le moment.</Text>
)}
+ </View>
- {urgentAlert && (
- <View style={styles.urgentBox}>
- <Text style={styles.urgentTitle}>
- {String(urgentAlert.alertLevel ?? "INFO")} — {String(urgentAlert.action ?? "HOLD")}
- </Text>
- <Text style={ui.muted} numberOfLines={2}>
- {urgentAlert.reason ?? urgentAlert.message ?? "—"}
- </Text>
+ {/* Alertes (bloc dédié) */}
+ <TouchableOpacity activeOpacity={0.85} onPress={() => navigation.navigate("Alerts" as never)}>
+ <View style={ui.card}>
+ <View style={ui.rowBetween}>
+ <Text style={ui.title}>Alertes</Text>
+ <Text style={ui.muted}>Ouvrir</Text>
</View>
- )}
- </View>
+ {urgentAlert ? (
+ <>
+ <Text style={[styles.urgentTitle, { marginTop: 10 }]}>
+ {String(urgentAlert.alertLevel ?? "INFO")} — {String(urgentAlert.action ?? "HOLD")}
+ </Text>
+
+ <Text style={ui.muted} numberOfLines={2}>
+ {urgentAlert.reason ?? urgentAlert.message ?? "—"}
+ </Text>
+
+ <Text style={ui.muted}>
+ {urgentAlert.pair ?? ""}{" "}
+ {typeof urgentAlert.confidence === "number"
+ ? `— ${Math.round(urgentAlert.confidence * 100)}%`
+ : ""}
+ </Text>
+ </>
+ ) : (
+ <Text style={[ui.muted, { marginTop: 10 }]}>Aucune alerte pour le moment.</Text>
+ )}
+ </View>
+ </TouchableOpacity>
+
+ {/* Stratégie */}
<TouchableOpacity activeOpacity={0.85} onPress={() => navigation.navigate("Strategy" as never)}>
<View style={ui.card}>
<View style={ui.rowBetween}>
</View>
</TouchableOpacity>
+ {/* Actions */}
<View style={ui.card}>
<Text style={ui.title}>Actions</Text>
<View style={styles.buySellRow}>
<Text style={styles.buySellText}>Vendre</Text>
</TouchableOpacity>
</View>
+ <Text style={[ui.muted, { marginTop: 10 }]} numberOfLines={2}>
+ Note : Acheter/Vendre = simulation (registre local). Pas de trading réel.
+ </Text>
</View>
+ {/* Modal BUY/SELL */}
<Modal visible={tradeOpen} transparent animationType="fade" onRequestClose={() => setTradeOpen(false)}>
<View style={styles.modalBackdrop}>
<KeyboardAvoidingView behavior={Platform.OS === "ios" ? "padding" : undefined} style={styles.modalWrap}>
{tradeSide === "BUY" ? "Acheter" : "Vendre"} {selectedCrypto}
</Text>
- <Text style={ui.muted}>Prix : {(summary?.price ?? 0).toFixed(2)} {currency}</Text>
+ <Text style={ui.muted}>
+ Prix : {(summary?.price ?? 0).toFixed(2)} {currency}
+ </Text>
<Text style={[ui.muted, { marginTop: 12 }]}>Quantité</Text>
<TextInput
const styles = StyleSheet.create({
safeArea: { flex: 1, backgroundColor: ui.screen.backgroundColor },
+
bannerWarn: { borderColor: "#ca8a04" },
bannerText: { color: "#ca8a04", fontWeight: "900" },
bold: { fontWeight: "900", color: "#0f172a" },
+
bigValue: { marginTop: 10, fontSize: 22, fontWeight: "900", color: "#0f172a" },
signalDecision: { marginTop: 10, fontSize: 26, fontWeight: "900", color: "#0f172a" },
},
secondaryButtonText: { fontWeight: "900", color: "#0f172a", opacity: 0.9 },
- urgentBox: {
- marginTop: 12,
- borderWidth: 1,
- borderColor: "#e5e7eb",
- borderRadius: 12,
- padding: 10,
- backgroundColor: "#fff",
- },
urgentTitle: { fontWeight: "900", color: "#0f172a" },
buySellRow: { flexDirection: "row", gap: 10, marginTop: 10 },
modalCard: { backgroundColor: "#fff", borderRadius: 16, padding: 16, borderWidth: 1, borderColor: "#e5e7eb" },
modalTitle: { fontSize: 18, fontWeight: "900", color: "#0f172a" },
modalError: { marginTop: 10, fontWeight: "800", color: "#dc2626" },
+
modalButtonsRow: { flexDirection: "row", gap: 10, marginTop: 14 },
modalBtn: { flex: 1, paddingVertical: 12, borderRadius: 12, alignItems: "center" },
modalBtnSecondary: { backgroundColor: "#fff", borderWidth: 1, borderColor: "#e5e7eb" },