]> git.digitality.be Git - pdw25-26/commitdiff
Mobile : Correction faute et autres
authorThibaud Moustier <thibaudmoustier0@gmail.com>
Sun, 1 Mar 2026 20:40:31 +0000 (21:40 +0100)
committerThibaud Moustier <thibaudmoustier0@gmail.com>
Sun, 1 Mar 2026 20:40:31 +0000 (21:40 +0100)
17 files changed:
Wallette/mobile/src/models/UserSettings.ts
Wallette/mobile/src/screens/AboutScreen.tsx
Wallette/mobile/src/screens/AlertsScreen.tsx
Wallette/mobile/src/screens/AuthScreen.tsx
Wallette/mobile/src/screens/DashboardScreen.tsx
Wallette/mobile/src/screens/HistoryScreen.tsx
Wallette/mobile/src/screens/SettingsScreen.tsx
Wallette/mobile/src/screens/StrategyScreen.tsx
Wallette/mobile/src/screens/TutorialScreen.tsx
Wallette/mobile/src/screens/WalletScreen.tsx
Wallette/mobile/src/services/api/authApi.ts
Wallette/mobile/src/services/api/dashboardApi.ts
Wallette/mobile/src/services/api/signalApi.ts
Wallette/mobile/src/services/api/strategyApi.ts
Wallette/mobile/src/services/api/walletApi.ts
Wallette/mobile/src/types/DashboardSummary.ts
Wallette/mobile/src/utils/settingsStorage.ts

index 97b718120f4bfd82fbac1f8438f714459536ff5d..e62d737dceeb01933e0ed16a5549aaf366df0a15 100644 (file)
@@ -1,27 +1,10 @@
-/**
- * UserSettings
- * ------------
- * Paramètres utilisateur stockés localement (AsyncStorage).
- * Step 1/2/3 : pas d'auth obligatoire, donc userId peut être absent.
- */
-export interface UserSettings {
-  // (optionnel) utile pour Socket.IO et futur Step 4 (auth)
-  userId?: string;
+export type Currency = "EUR" | "USDT";
 
-  // Paramètres UI / usage
-  currency: "EUR" | "USD";
+export interface UserSettings {
+  currency: Currency;
   favoriteSymbol: string;
-
-  // Préférences alertes
   alertPreference: "critical" | "all";
-
-  // Rafraîchissement dashboard
   refreshMode: "manual" | "auto";
-
-  // Notifications locales (Expo)
   notificationsEnabled: boolean;
-
-  // Stratégie choisie (persistée)
-  // Exemple: "RSI_SIMPLE"
   selectedStrategyKey: string;
 }
\ No newline at end of file
index 02e683ecc58cb9e77a067f859c5eec1ac59d9b34..5ceaa5f2fdd7fc0c2de55efe78155242df146929 100644 (file)
@@ -8,10 +8,10 @@ import { loadSession } from "../utils/sessionStorage";
 import { findUserById } from "../utils/authUsersStorage";
 
 /**
- * AboutScreen 
- * ------------------------
- * "Détails du compte" + sections À propos / Support / Version.
- * Les infos affichées viennent du compte local (AuthUsersStorage) :
+ * AboutScreen
+ * -----------
+ * Détails du compte + sections À propos / Support / Version.
+ * Les infos affichées viennent du compte local :
  * - displayName (optionnel)
  * - username
  * - email
@@ -48,7 +48,7 @@ export default function AboutScreen() {
       setEmail(user?.email ?? session.email);
     }
 
-    init();
+    void init();
 
     return () => {
       active = false;
@@ -70,7 +70,12 @@ export default function AboutScreen() {
         <Text style={styles.rowTitle}>{title}</Text>
         {!!subtitle && <Text style={ui.muted}>{subtitle}</Text>}
       </View>
-      <Ionicons name="chevron-forward-outline" size={18} color="#0f172a" style={{ opacity: 0.25 }} />
+      <Ionicons
+        name="chevron-forward-outline"
+        size={18}
+        color="#0f172a"
+        style={{ opacity: 0.25 }}
+      />
     </View>
   );
 
@@ -79,7 +84,7 @@ export default function AboutScreen() {
       <View style={ui.container}>
         <Text style={styles.title}>Détails du compte</Text>
 
-        {/* Carte profil (style WF-09) */}
+        {/* Carte profil */}
         <View style={ui.card}>
           <View style={styles.profileRow}>
             <Ionicons
@@ -90,7 +95,9 @@ export default function AboutScreen() {
             />
             <View style={{ flex: 1 }}>
               <Text style={styles.profileName}>{displayName}</Text>
-              <Text style={ui.muted} numberOfLines={1}>{email}</Text>
+              <Text style={ui.muted} numberOfLines={1}>
+                {email}
+              </Text>
               <Text style={[ui.muted, { marginTop: 2 }]} numberOfLines={1}>
                 @{username}
               </Text>
@@ -99,7 +106,9 @@ export default function AboutScreen() {
 
           <View style={styles.metaBox}>
             <Text style={styles.metaLabel}>UserId</Text>
-            <Text style={styles.metaValue} numberOfLines={1}>{userId}</Text>
+            <Text style={styles.metaValue} numberOfLines={1}>
+              {userId}
+            </Text>
           </View>
         </View>
 
@@ -107,11 +116,15 @@ export default function AboutScreen() {
         <View style={ui.card}>
           <Text style={ui.title}>À propos</Text>
 
-          <Row icon="sparkles-outline" title="Wall-e-tte" subtitle="Conseiller crypto — projet PDW 2025-2026" />
+          <Row
+            icon="sparkles-outline"
+            title="Wallette"
+            subtitle="Conseiller crypto — projet PDW 2025-2026"
+          />
           <Row
             icon="shield-checkmark-outline"
             title="Avertissement"
-            subtitle="Outil éducatif : ce n’est pas un conseil financier."
+            subtitle="Outil éducatif : ceci n’est pas un conseil financier."
           />
         </View>
 
@@ -119,14 +132,13 @@ export default function AboutScreen() {
         <View style={ui.card}>
           <Text style={ui.title}>Support</Text>
           <Row icon="help-circle-outline" title="Aide" subtitle="Comprendre les écrans et les signaux." />
-          <Row icon="mail-outline" title="Contact" subtitle="Support de l’équipe (à définir)." />
+          <Row icon="mail-outline" title="Contact" subtitle="Contactez l’équipe du projet." />
         </View>
 
         {/* Version */}
         <View style={ui.card}>
           <Text style={ui.title}>Version</Text>
           <Text style={ui.muted}>App : 1.0.0</Text>
-          <Text style={[ui.muted, { marginTop: 6 }]}>Build : Step 4 (sans API)</Text>
         </View>
 
         {/* Bouton discret (optionnel) */}
index e4ef5b0fc094076025cb7abaee080a50816d1373..dee535c9047d43375ee762892fea9a7fa45bf577 100644 (file)
@@ -5,28 +5,11 @@ import { SafeAreaView } from "react-native-safe-area-context";
 import { ui } from "../components/ui/uiStyles";
 import type { Alert } from "../types/Alert";
 import { alertStore } from "../services/alertStore";
-
-/**
- * ✅ API-ready (façade)
- * Aujourd'hui : clear local store
- * Demain : REST /api/alerts/events ... etc.
- */
 import { clearAlertsLocal } from "../services/api/alertsApi";
 
-/**
- * AlertsScreen
- * -----------
- * Liste des alertes reçues (Socket.IO) stockées dans alertStore.
- *
- * Objectifs :
- * - Affichage clair des enums (CRITICAL/WARNING/INFO, BUY/SELL/HOLD/STOP_LOSS)
- * - Tri : CRITICAL > WARNING > INFO, puis récent -> ancien
- * - Filtre (par défaut : CRITICAL)
- * - Clear avec confirmation
- */
 type Filter = "CRITICAL" | "WARNING" | "INFO" | "ALL";
 
-function severityRank(level: Alert["alertLevel"]): number {
+function severityRank(level?: Alert["alertLevel"]): number {
   switch (level) {
     case "CRITICAL":
       return 3;
@@ -38,7 +21,7 @@ function severityRank(level: Alert["alertLevel"]): number {
   }
 }
 
-function actionColor(action: Alert["action"]): string {
+function actionColor(action?: Alert["action"]): string {
   switch (action) {
     case "BUY":
       return "#16a34a";
@@ -52,7 +35,7 @@ function actionColor(action: Alert["action"]): string {
   }
 }
 
-function levelColor(level: Alert["alertLevel"]): string {
+function levelColor(level?: Alert["alertLevel"]): string {
   switch (level) {
     case "CRITICAL":
       return "#b91c1c";
@@ -64,8 +47,9 @@ function levelColor(level: Alert["alertLevel"]): string {
   }
 }
 
-function formatDate(ms: number): string {
-  return new Date(ms).toLocaleString();
+function formatDate(ms?: number): string {
+  const t = typeof ms === "number" && Number.isFinite(ms) ? ms : Date.now();
+  return new Date(t).toLocaleString();
 }
 
 export default function AlertsScreen() {
@@ -73,10 +57,8 @@ export default function AlertsScreen() {
   const [filter, setFilter] = useState<Filter>("CRITICAL");
 
   useEffect(() => {
-    // Load initial
     setItems(alertStore.getAll?.() ?? []);
 
-    // Live updates if subscribe exists
     const unsub = alertStore.subscribe?.((all: Alert[]) => {
       setItems(all);
     });
@@ -88,13 +70,11 @@ export default function AlertsScreen() {
 
   const filteredSorted = useMemo(() => {
     const base =
-      filter === "ALL" ? items : items.filter((a) => a.alertLevel === filter);
+      filter === "ALL" ? items : items.filter((a) => (a.alertLevel ?? "INFO") === filter);
 
     return [...base].sort((a, b) => {
-      // 1) severity
       const r = severityRank(b.alertLevel) - severityRank(a.alertLevel);
       if (r !== 0) return r;
-      // 2) timestamp desc
       return (b.timestamp ?? 0) - (a.timestamp ?? 0);
     });
   }, [items, filter]);
@@ -102,7 +82,7 @@ export default function AlertsScreen() {
   const handleClear = useCallback(() => {
     RNAlert.alert(
       "Supprimer les alertes ?",
-      "Cette action supprime les alertes stockées localement (alertStore).",
+      "Cette action supprime les alertes stockées sur l’app.",
       [
         { text: "Annuler", style: "cancel" },
         {
@@ -124,9 +104,7 @@ export default function AlertsScreen() {
         onPress={() => setFilter(value)}
         style={[styles.filterBtn, active && styles.filterBtnActive]}
       >
-        <Text style={[styles.filterText, active && styles.filterTextActive]}>
-          {label}
-        </Text>
+        <Text style={[styles.filterText, active && styles.filterTextActive]}>{label}</Text>
       </TouchableOpacity>
     );
   };
@@ -137,9 +115,8 @@ export default function AlertsScreen() {
         contentContainerStyle={ui.container}
         data={filteredSorted}
         keyExtractor={(it, idx) =>
-          // Alert n'a pas forcément d'id => fallback stable
           (it as any).id ??
-          `${it.timestamp}-${it.pair}-${it.action}-${it.alertLevel}-${idx}`
+          `${it.timestamp ?? "0"}-${it.pair ?? ""}-${it.action ?? ""}-${it.alertLevel ?? ""}-${idx}`
         }
         ListHeaderComponent={
           <View style={{ marginBottom: 12 }}>
@@ -147,19 +124,19 @@ export default function AlertsScreen() {
               <Text style={styles.screenTitle}>Alertes</Text>
 
               <TouchableOpacity onPress={handleClear} style={styles.clearBtn}>
-                <Text style={styles.clearBtnText}>Clear</Text>
+                <Text style={styles.clearBtnText}>Supprimer</Text>
               </TouchableOpacity>
             </View>
 
             <View style={styles.filtersRow}>
               <FilterButton value="CRITICAL" label="Critiques" />
-              <FilterButton value="WARNING" label="Warnings" />
-              <FilterButton value="INFO" label="Info" />
+              <FilterButton value="WARNING" label="Avertissements" />
+              <FilterButton value="INFO" label="Informations" />
               <FilterButton value="ALL" label="Toutes" />
             </View>
 
             <Text style={ui.muted}>
-              Tri : CRITICAL → WARNING → INFO, puis récent → ancien.
+              Tri : critique → avertissement → info, puis du plus récent au plus ancien.
             </Text>
           </View>
         }
@@ -173,33 +150,40 @@ export default function AlertsScreen() {
           const aColor = actionColor(item.action);
           const lColor = levelColor(item.alertLevel);
 
+          const confidence =
+            typeof item.confidence === "number" && Number.isFinite(item.confidence)
+              ? item.confidence
+              : 0;
+
           return (
             <View style={ui.card}>
               <View style={ui.rowBetween}>
-                <Text style={ui.valueBold}>{item.pair}</Text>
+                <Text style={ui.valueBold}>{item.pair ?? "—"}</Text>
                 <Text style={ui.muted}>{formatDate(item.timestamp)}</Text>
               </View>
 
               <View style={styles.badgesRow}>
                 <View style={[ui.badge, { backgroundColor: `${aColor}22`, marginTop: 0 }]}>
-                  <Text style={[ui.badgeText, { color: aColor }]}>{item.action}</Text>
+                  <Text style={[ui.badgeText, { color: aColor }]}>{item.action ?? "HOLD"}</Text>
                 </View>
 
                 <View style={[ui.badge, { backgroundColor: `${lColor}22`, marginTop: 0 }]}>
-                  <Text style={[ui.badgeText, { color: lColor }]}>{item.alertLevel}</Text>
+                  <Text style={[ui.badgeText, { color: lColor }]}>{item.alertLevel ?? "INFO"}</Text>
                 </View>
               </View>
 
               <View style={[ui.rowBetween, { marginTop: 10 }]}>
                 <Text style={ui.value}>Confiance</Text>
-                <Text style={ui.valueBold}>{(item.confidence * 100).toFixed(0)}%</Text>
+                <Text style={ui.valueBold}>{Math.round(confidence * 100)}%</Text>
               </View>
 
-              <Text style={[ui.muted, { marginTop: 10 }]}>{item.reason}</Text>
+              <Text style={[ui.muted, { marginTop: 10 }]} numberOfLines={4}>
+                {item.reason ?? item.message ?? "—"}
+              </Text>
 
-              {!!item.price && (
+              {typeof item.price === "number" && Number.isFinite(item.price) && (
                 <Text style={[ui.muted, { marginTop: 6 }]}>
-                  Prix (optionnel) : {item.price.toFixed(2)}
+                  Prix : {item.price.toFixed(2)}
                 </Text>
               )}
             </View>
index db3ab9eaab6a28f8bcfc0b021784f8625c48b6b7..30620bac9d71d2a977589b4d77386b25ec424a89 100644 (file)
@@ -6,28 +6,27 @@ import { ui } from "../components/ui/uiStyles";
 import { login as authLogin, register as authRegister } from "../services/api/authApi";
 
 /**
- * AuthScreen (Step local, sans API serveur)
- * ----------------------------------------
- * Mode Connexion + Mode Création de compte.
- * - Identifiant: email OU username
- * - Mot de passe obligatoire
+ * AuthScreen
+ * ----------
+ * Connexion + Création de compte.
+ * - Identifiant : email OU nom d’utilisateur
+ * - Mot de passe : minimum 6 caractères
  *
- * IMPORTANT :
- * - L'écran appelle authApi (façade).
- * - Plus tard, authApi sera remplacé par des appels REST,
- *   sans modifier cet écran.
+ * Note technique (code) :
+ * - L’écran appelle authApi (façade).
+ * - Le stockage de session est géré côté utils.
  */
 export default function AuthScreen({ onAuthenticated }: { onAuthenticated: () => void }) {
   const [mode, setMode] = useState<"login" | "register">("login");
   const [error, setError] = useState<string | null>(null);
 
   // Connexion
-  const [login, setLogin] = useState("demo@example.com");
+  const [login, setLogin] = useState("");
   const [password, setPassword] = useState("");
 
-  // Register
-  const [email, setEmail] = useState("demo@example.com");
-  const [username, setUsername] = useState("demo");
+  // Création compte
+  const [email, setEmail] = useState("");
+  const [username, setUsername] = useState("");
   const [displayName, setDisplayName] = useState("");
   const [password2, setPassword2] = useState("");
 
@@ -43,7 +42,7 @@ export default function AuthScreen({ onAuthenticated }: { onAuthenticated: () =>
     const p = password;
 
     if (!l) return setError("Veuillez entrer un email ou un nom d’utilisateur.");
-    if (!p || p.length < 6) return setError("Mot de passe invalide (min 6).");
+    if (!p || p.length < 6) return setError("Mot de passe invalide (minimum 6 caractères).");
 
     const res = await authLogin({ login: l, password: p });
     if (!res.ok) return setError(res.message);
@@ -61,8 +60,8 @@ export default function AuthScreen({ onAuthenticated }: { onAuthenticated: () =>
     const p2 = password2;
 
     if (!isValidEmail(e)) return setError("Email invalide.");
-    if (!isValidUsername(u)) return setError("Nom d’utilisateur invalide (3-20, lettres/chiffres/_).");
-    if (!p1 || p1.length < 6) return setError("Mot de passe trop court (min 6).");
+    if (!isValidUsername(u)) return setError("Nom d’utilisateur invalide (3 à 20, lettres/chiffres/_).");
+    if (!p1 || p1.length < 6) return setError("Mot de passe trop court (minimum 6 caractères).");
     if (p1 !== p2) return setError("Les mots de passe ne correspondent pas.");
 
     const res = await authRegister({
@@ -82,7 +81,7 @@ export default function AuthScreen({ onAuthenticated }: { onAuthenticated: () =>
       <View style={ui.container}>
         <Text style={styles.title}>{title}</Text>
 
-        {/* Tabs */}
+        {/* Onglets */}
         <View style={styles.tabsRow}>
           <TouchableOpacity
             style={[styles.tab, mode === "login" && styles.tabActive]}
@@ -113,7 +112,7 @@ export default function AuthScreen({ onAuthenticated }: { onAuthenticated: () =>
                 value={login}
                 onChangeText={setLogin}
                 autoCapitalize="none"
-                placeholder="ex: demo@example.com ou demo"
+                placeholder="ex : user@example.com ou pseudo"
                 style={styles.input}
               />
 
@@ -122,7 +121,7 @@ export default function AuthScreen({ onAuthenticated }: { onAuthenticated: () =>
                 value={password}
                 onChangeText={setPassword}
                 secureTextEntry
-                placeholder="min 6 caractères"
+                placeholder="minimum 6 caractères"
                 style={styles.input}
               />
 
@@ -131,10 +130,6 @@ export default function AuthScreen({ onAuthenticated }: { onAuthenticated: () =>
               <TouchableOpacity style={[ui.button, styles.fullButton]} onPress={handleLogin}>
                 <Text style={ui.buttonText}>Se connecter</Text>
               </TouchableOpacity>
-
-              <Text style={[ui.muted, { marginTop: 10 }]}>
-                (Mode local sans API serveur : destiné au développement / démo.)
-              </Text>
             </>
           ) : (
             <>
@@ -144,7 +139,7 @@ export default function AuthScreen({ onAuthenticated }: { onAuthenticated: () =>
                 onChangeText={setEmail}
                 autoCapitalize="none"
                 keyboardType="email-address"
-                placeholder="ex: demo@example.com"
+                placeholder="ex : user@example.com"
                 style={styles.input}
               />
 
@@ -153,15 +148,15 @@ export default function AuthScreen({ onAuthenticated }: { onAuthenticated: () =>
                 value={username}
                 onChangeText={setUsername}
                 autoCapitalize="none"
-                placeholder="ex: demo_123"
+                placeholder="ex : pseudo_123"
                 style={styles.input}
               />
 
-              <Text style={[ui.muted, { marginTop: 10 }]}>Nom / Prénom (optionnel)</Text>
+              <Text style={[ui.muted, { marginTop: 10 }]}>Nom / prénom (optionnel)</Text>
               <TextInput
                 value={displayName}
                 onChangeText={setDisplayName}
-                placeholder="ex: Thibaud M."
+                placeholder="ex : Thibaud"
                 style={styles.input}
               />
 
@@ -170,16 +165,16 @@ export default function AuthScreen({ onAuthenticated }: { onAuthenticated: () =>
                 value={password}
                 onChangeText={setPassword}
                 secureTextEntry
-                placeholder="min 6 caractères"
+                placeholder="minimum 6 caractères"
                 style={styles.input}
               />
 
-              <Text style={[ui.muted, { marginTop: 10 }]}>Confirmer</Text>
+              <Text style={[ui.muted, { marginTop: 10 }]}>Confirmer le mot de passe</Text>
               <TextInput
                 value={password2}
                 onChangeText={setPassword2}
                 secureTextEntry
-                placeholder="retaper le mot de passe"
+                placeholder="retapez le mot de passe"
                 style={styles.input}
               />
 
@@ -188,10 +183,6 @@ export default function AuthScreen({ onAuthenticated }: { onAuthenticated: () =>
               <TouchableOpacity style={[ui.button, styles.fullButton]} onPress={handleRegister}>
                 <Text style={ui.buttonText}>Créer le compte</Text>
               </TouchableOpacity>
-
-              <Text style={[ui.muted, { marginTop: 10 }]}>
-                (Sans API serveur : stockage local + hash SHA-256, pas de mot de passe en clair.)
-              </Text>
             </>
           )}
         </View>
index 8834cfd6a0733548dbe064b43e86dc7404349a37..bfb21ac1fde57420219f520a62eb85d0ec254b11 100644 (file)
@@ -27,7 +27,7 @@ import { loadSession } from "../utils/sessionStorage";
 import { loadSettings } from "../utils/settingsStorage";
 import { loadPortfolio, savePortfolio } from "../utils/portfolioStorage";
 
-import { SERVER_URL, API_BASE_URL, ENV_MODE } from "../config/env";
+import { SERVER_URL } from "../config/env";
 import { socketService } from "../services/socketService";
 import { alertStore } from "../services/alertStore";
 import { showAlertNotification } from "../services/notificationService";
@@ -38,6 +38,7 @@ import { getPortfolio as getPortfolioFromApi } from "../services/api/walletApi";
 
 type CryptoSymbol = "BTC" | "ETH" | "LTC";
 type TradeSide = "BUY" | "SELL";
+
 const CRYPTOS: CryptoSymbol[] = ["BTC", "ETH", "LTC"];
 
 function walletAddressKey(userId: string) {
@@ -75,7 +76,6 @@ export default function DashboardScreen() {
   });
 
   const [selectedCrypto, setSelectedCrypto] = useState<CryptoSymbol>("BTC");
-
   const [summary, setSummary] = useState<DashboardSummary | null>(null);
 
   const [walletAddress, setWalletAddress] = useState("");
@@ -90,8 +90,14 @@ export default function DashboardScreen() {
   const [tradeQty, setTradeQty] = useState("0.01");
   const [tradeInfo, setTradeInfo] = useState<string | null>(null);
 
-  const currency: "EUR" | "USD" = settings?.currency === "USD" ? "USD" : "EUR";
-  const pair = useMemo(() => `${selectedCrypto}/${currency}`, [selectedCrypto, currency]);
+  // ✅ Currency alignée DB/serveur : EUR ou USDT
+  const quote: "EUR" | "USDT" = settings?.currency === "USDT" ? "USDT" : "EUR";
+
+  // ✅ Pair affiché et utilisé côté serveur (DB ne connaît que BTC/EUR et BTC/USDT)
+  const serverPair = useMemo(() => `BTC/${quote}`, [quote]);
+
+  // UI pair : on peut afficher la crypto sélectionnée, mais sans inventer des paires serveur
+  const displayPair = useMemo(() => `${selectedCrypto}/${quote}`, [selectedCrypto, quote]);
 
   const selectedQty = useMemo(() => {
     const a = portfolio.assets.find((x) => normalizeSymbol(x.symbol) === selectedCrypto);
@@ -130,16 +136,16 @@ export default function DashboardScreen() {
       setWalletAddress("");
     }
 
-    // Dashboard summary (prix+signal)
+    // Dashboard summary (prix + signal) => dépend du pair BTC/EUR ou BTC/USDT
     try {
       const dash = await getDashboardSummary();
       setSummary(dash);
     } catch {
       setSummary(null);
-      setSoftError(`Signal/Prix indisponibles (API). DEV=${ENV_MODE}. Base REST: ${API_BASE_URL}`);
+      setSoftError("Données indisponibles pour le moment. Réessayez dans quelques secondes.");
     }
 
-    // Wallet API (si dispo) -> fusion
+    // Wallet API (source de vérité) -> cache local
     if (uid) {
       try {
         const apiPortfolio = await getPortfolioFromApi();
@@ -181,7 +187,7 @@ export default function DashboardScreen() {
     }, [refreshAll])
   );
 
-  // Socket live (✅ connect seulement si userId existe)
+  // Socket live (connect uniquement si session)
   useEffect(() => {
     let unsub: null | (() => void) = null;
     let alive = true;
@@ -204,7 +210,7 @@ export default function DashboardScreen() {
         setSocketConnected(true);
       } catch {
         if (!alive) return;
-        setSocketInfo("Socket indisponible (URL / serveur).");
+        setSocketInfo("Socket indisponible pour le moment.");
         return;
       }
 
@@ -244,7 +250,7 @@ export default function DashboardScreen() {
 
     const trimmed = walletAddress.trim();
     if (trimmed.length < 6) {
-      setWalletAddressInfo("Adresse trop courte. Vérifie la saisie.");
+      setWalletAddressInfo("Adresse trop courte. Vérifiez la saisie.");
       return;
     }
 
@@ -264,7 +270,7 @@ export default function DashboardScreen() {
 
     const qty = Number(tradeQty.replace(",", ".").trim());
     if (!Number.isFinite(qty) || qty <= 0) {
-      setTradeInfo("Quantité invalide (ex: 0.01).");
+      setTradeInfo("Quantité invalide (ex : 0.01).");
       return;
     }
 
@@ -277,7 +283,7 @@ export default function DashboardScreen() {
     if (tradeSide === "BUY") nextQty = currentQty + qty;
     else {
       if (qty > currentQty) {
-        setTradeInfo(`Vente impossible : tu n'as que ${currentQty.toFixed(6)} ${symbol}.`);
+        setTradeInfo(`Vente impossible : vous n'avez que ${currentQty.toFixed(6)} ${symbol}.`);
         return;
       }
       nextQty = currentQty - qty;
@@ -329,7 +335,7 @@ export default function DashboardScreen() {
 
         {/* Crypto + adresse */}
         <View style={ui.card}>
-          <Text style={ui.title}>Choisir une cryptomonnaie</Text>
+          <Text style={ui.title}>Choix de cryptomonnaie</Text>
 
           <View style={styles.cryptoRow}>
             {CRYPTOS.map((c) => {
@@ -353,7 +359,7 @@ export default function DashboardScreen() {
               setWalletAddressInfo(null);
               setWalletAddress(t);
             }}
-            placeholder="Entrez une adresse crypto"
+            placeholder="Entrez une adresse"
             style={styles.input}
             autoCapitalize="none"
             autoCorrect={false}
@@ -363,7 +369,9 @@ export default function DashboardScreen() {
             <Text style={styles.secondaryButtonText}>Enregistrer l’adresse</Text>
           </TouchableOpacity>
 
-          {!!walletAddressInfo && <Text style={[ui.muted, { marginTop: 8 }]}>{walletAddressInfo}</Text>}
+          {!!walletAddressInfo && (
+            <Text style={[ui.muted, { marginTop: 8 }]}>{walletAddressInfo}</Text>
+          )}
         </View>
 
         {/* Solde */}
@@ -372,17 +380,25 @@ export default function DashboardScreen() {
           <Text style={styles.bigValue}>
             {selectedQty.toFixed(6)} {selectedCrypto}
           </Text>
-          <Text style={ui.muted}>Portefeuille local (+ sync serveur si dispo)</Text>
+          <Text style={ui.muted}>Synchronisé avec le serveur quand disponible.</Text>
         </View>
 
         {/* Prix */}
         <View style={ui.card}>
           <Text style={ui.title}>Prix</Text>
-          <Text style={ui.muted}>{pair}</Text>
+          <Text style={ui.muted}>{displayPair}</Text>
+
           <Text style={styles.bigValue}>
-            {(summary?.price ?? 0).toFixed(2)} {currency}
+            {(summary?.price ?? 0).toFixed(2)} {quote}
           </Text>
 
+          {/* Info transparente : prix/signal serveur sont basés sur BTC (DB) */}
+          {selectedCrypto !== "BTC" && (
+            <Text style={[ui.muted, { marginTop: 8 }]}>
+              Note : les données serveur (prix/signal) sont disponibles uniquement pour BTC.
+            </Text>
+          )}
+
           <TouchableOpacity style={[ui.button, { marginTop: 10 }]} onPress={() => void refreshAll()}>
             <Text style={ui.buttonText}>Actualiser</Text>
           </TouchableOpacity>
@@ -400,6 +416,7 @@ export default function DashboardScreen() {
               <Text style={[ui.muted, { marginTop: 6 }]} numberOfLines={3}>
                 {summary.reason}
               </Text>
+              <Text style={[ui.muted, { marginTop: 6 }]}>{serverPair}</Text>
             </>
           ) : (
             <Text style={ui.muted}>Aucune donnée pour le moment.</Text>
@@ -462,7 +479,7 @@ export default function DashboardScreen() {
             </TouchableOpacity>
           </View>
           <Text style={[ui.muted, { marginTop: 10 }]} numberOfLines={2}>
-            Note : Acheter/Vendre = simulation (registre local). Pas de trading réel.
+            Note : acheter/vendre enregistre une opération locale. (Pas de trading réel)
           </Text>
         </View>
 
@@ -476,7 +493,7 @@ export default function DashboardScreen() {
                 </Text>
 
                 <Text style={ui.muted}>
-                  Prix : {(summary?.price ?? 0).toFixed(2)} {currency}
+                  Prix (serveur) : {(summary?.price ?? 0).toFixed(2)} {quote}
                 </Text>
 
                 <Text style={[ui.muted, { marginTop: 12 }]}>Quantité</Text>
index 8c29d507c8e17dc03068f7ae84ca4e0720e41c07..ad2aef560dddc60c99655bbc4994d3092a04dd85 100644 (file)
@@ -3,12 +3,7 @@ import { useEffect, useState } from "react";
 import { SafeAreaView } from "react-native-safe-area-context";
 
 import type { Signal, SignalAction, SignalCriticality } from "../types/Signal";
-
-/**
- * ✅ API-ready (façade)
- */
 import { getRecentSignals } from "../services/api/signalApi";
-
 import { ui } from "../components/ui/uiStyles";
 
 function getActionColor(action: SignalAction): string {
@@ -41,6 +36,12 @@ function formatDate(ms: number): string {
   return new Date(ms).toLocaleString();
 }
 
+function quoteFromPair(pair: string): string {
+  const p = String(pair ?? "");
+  const parts = p.split("/");
+  return parts.length === 2 ? parts[1] : "";
+}
+
 export default function HistoryScreen() {
   const [items, setItems] = useState<Signal[]>([]);
   const [loading, setLoading] = useState<boolean>(true);
@@ -57,13 +58,13 @@ export default function HistoryScreen() {
         const data = await getRecentSignals(20);
         if (active) setItems(data);
       } catch {
-        if (active) setError("Impossible de charger l'historique.");
+        if (active) setError("Impossible de charger l’historique pour le moment.");
       } finally {
         if (active) setLoading(false);
       }
     }
 
-    load();
+    void load();
 
     return () => {
       active = false;
@@ -73,7 +74,7 @@ export default function HistoryScreen() {
   if (loading) {
     return (
       <View style={ui.centered}>
-        <Text>Chargement de l'historique…</Text>
+        <Text>Chargement de lhistorique…</Text>
       </View>
     );
   }
@@ -91,7 +92,7 @@ export default function HistoryScreen() {
       <FlatList
         contentContainerStyle={ui.container}
         data={items}
-        keyExtractor={(it) => it.signalId}
+        keyExtractor={(it, idx) => String((it as any).signalId ?? (it as any).id ?? idx)}
         ListEmptyComponent={
           <View style={ui.card}>
             <Text style={ui.title}>Aucun signal</Text>
@@ -101,6 +102,7 @@ export default function HistoryScreen() {
         renderItem={({ item }) => {
           const actionColor = getActionColor(item.action);
           const critColor = getCriticalityColor(item.criticality);
+          const quote = quoteFromPair(item.pair);
 
           return (
             <View style={ui.card}>
@@ -109,7 +111,6 @@ export default function HistoryScreen() {
                 <Text style={ui.muted}>{formatDate(item.timestamp)}</Text>
               </View>
 
-              {/* Badges */}
               <View
                 style={{
                   flexDirection: "row",
@@ -138,7 +139,9 @@ export default function HistoryScreen() {
 
               <View style={[ui.rowBetween, { marginTop: 6 }]}>
                 <Text style={ui.value}>Prix au signal</Text>
-                <Text style={ui.valueBold}>{item.priceAtSignal.toFixed(2)}</Text>
+                <Text style={ui.valueBold}>
+                  {item.priceAtSignal.toFixed(2)} {quote}
+                </Text>
               </View>
 
               <Text style={[ui.muted, { marginTop: 10 }]}>{item.reason}</Text>
index b8790e71e79a008820cfa8a6dd8a197b92cb2da9..831f50a1e89849be8a22e8a6c8bb4ff7e90d9739 100644 (file)
@@ -10,17 +10,6 @@ import { requestNotificationPermission } from "../services/notificationService";
 import { ui } from "../components/ui/uiStyles";
 import { setSeenTutorial } from "../utils/tutorialStorage";
 
-/**
- * SettingsScreen
- * --------------
- * - Settings par userId (via settingsStorage)
- * - Notifications (local)
- * - Revoir le tutoriel (remet hasSeenTutorial à false)
- *
- * Note "API-ready" :
- * - Aujourd'hui : AsyncStorage (local)
- * - Demain : les mêmes actions pourront appeler une API (sans changer l'UI)
- */
 export default function SettingsScreen({
   onRequestTutorial,
 }: {
@@ -54,11 +43,6 @@ export default function SettingsScreen({
     );
   }
 
-  /**
-   * Sauvegarde "safe"
-   * - évite les doubles clics
-   * - affiche un message simple
-   */
   const persist = async (next: UserSettings, msg?: string) => {
     try {
       setSaving(true);
@@ -70,9 +54,10 @@ export default function SettingsScreen({
     }
   };
 
+  // EUR <-> USDT (aligné DB/serveur)
   const toggleCurrency = async () => {
     setInfoMessage(null);
-    const newCurrency = settings.currency === "EUR" ? "USD" : "EUR";
+    const newCurrency = settings.currency === "EUR" ? "USDT" : "EUR";
     await persist({ ...settings, currency: newCurrency }, `Devise : ${newCurrency} ✅`);
   };
 
@@ -85,7 +70,6 @@ export default function SettingsScreen({
   const toggleNotifications = async () => {
     setInfoMessage(null);
 
-    // OFF -> ON : on demande la permission
     if (!settings.notificationsEnabled) {
       const granted = await requestNotificationPermission();
       if (!granted) {
@@ -96,24 +80,13 @@ export default function SettingsScreen({
         return;
       }
 
-      await persist(
-        { ...settings, notificationsEnabled: true },
-        "Notifications activées ✅"
-      );
+      await persist({ ...settings, notificationsEnabled: true }, "Notifications activées ✅");
       return;
     }
 
-    // ON -> OFF
-    await persist(
-      { ...settings, notificationsEnabled: false },
-      "Notifications désactivées ✅"
-    );
+    await persist({ ...settings, notificationsEnabled: false }, "Notifications désactivées ✅");
   };
 
-  /**
-   * Bouton "Sauvegarder" : garde-fou
-   * (utile si on ajoute d'autres réglages plus tard)
-   */
   const handleSave = async () => {
     setInfoMessage(null);
     await persist(settings, "Paramètres sauvegardés ✅");
@@ -141,7 +114,6 @@ export default function SettingsScreen({
       <View style={ui.container}>
         <Text style={styles.screenTitle}>Paramètres</Text>
 
-        {/* Carte : Devise */}
         <View style={ui.card}>
           <Text style={ui.title}>Devise</Text>
           <Text style={ui.value}>Actuelle : {settings.currency}</Text>
@@ -155,7 +127,6 @@ export default function SettingsScreen({
           </TouchableOpacity>
         </View>
 
-        {/* Carte : Refresh */}
         <View style={ui.card}>
           <Text style={ui.title}>Rafraîchissement</Text>
           <Text style={ui.value}>Mode : {settings.refreshMode}</Text>
@@ -169,10 +140,11 @@ export default function SettingsScreen({
           </TouchableOpacity>
         </View>
 
-        {/* Carte : Notifications */}
         <View style={ui.card}>
           <Text style={ui.title}>Notifications</Text>
-          <Text style={ui.value}>Statut : {settings.notificationsEnabled ? "ON" : "OFF"}</Text>
+          <Text style={ui.value}>
+            Statut : {settings.notificationsEnabled ? "ON" : "OFF"}
+          </Text>
 
           <TouchableOpacity
             style={[ui.button, styles.fullButton, saving && styles.btnDisabled]}
@@ -187,7 +159,6 @@ export default function SettingsScreen({
           {!!infoMessage && <Text style={[ui.muted, { marginTop: 10 }]}>{infoMessage}</Text>}
         </View>
 
-        {/* Carte : Tutoriel */}
         <View style={ui.card}>
           <Text style={ui.title}>Tutoriel</Text>
           <Text style={ui.muted}>
@@ -199,7 +170,6 @@ export default function SettingsScreen({
           </TouchableOpacity>
         </View>
 
-        {/* Bouton Save (garde-fou) */}
         <TouchableOpacity
           style={[ui.button, styles.fullButton, styles.saveButton, saving && styles.btnDisabled]}
           onPress={handleSave}
@@ -213,26 +183,10 @@ export default function SettingsScreen({
 }
 
 const styles = StyleSheet.create({
-  safeArea: {
-    flex: 1,
-    backgroundColor: ui.screen.backgroundColor,
-  },
-  screenTitle: {
-    fontSize: 22,
-    fontWeight: "900",
-    marginBottom: 12,
-    color: "#0f172a",
-  },
-  fullButton: {
-    flexGrow: 0,
-    flexBasis: "auto",
-    width: "100%",
-    marginTop: 10,
-  },
-  saveButton: {
-    marginTop: 4,
-    marginBottom: 10,
-  },
+  safeArea: { flex: 1, backgroundColor: ui.screen.backgroundColor },
+  screenTitle: { fontSize: 22, fontWeight: "900", marginBottom: 12, color: "#0f172a" },
+  fullButton: { flexGrow: 0, flexBasis: "auto", width: "100%", marginTop: 10 },
+  saveButton: { marginTop: 4, marginBottom: 10 },
   secondaryButton: {
     marginTop: 10,
     paddingVertical: 12,
@@ -242,12 +196,6 @@ const styles = StyleSheet.create({
     alignItems: "center",
     backgroundColor: "#fff",
   },
-  secondaryButtonText: {
-    fontWeight: "900",
-    color: "#0f172a",
-    opacity: 0.85,
-  },
-  btnDisabled: {
-    opacity: 0.7,
-  },
+  secondaryButtonText: { fontWeight: "900", color: "#0f172a", opacity: 0.85 },
+  btnDisabled: { opacity: 0.7 },
 });
\ No newline at end of file
index c0583f6206be02ff17d7caabae30d23ba4661a71..160757e2b3fee518df08d9302c5239e74c716ad8 100644 (file)
@@ -33,7 +33,7 @@ export default function StrategyScreen() {
       }
     }
 
-    init();
+    void init();
     return () => {
       active = false;
     };
@@ -56,11 +56,11 @@ export default function StrategyScreen() {
       setBusy(true);
       setInfo(null);
 
-      // Pair alignée dashboard : BTC + devise settings
-      const currency = settings.currency === "USD" ? "USD" : "EUR";
-      const pair = `BTC/${currency}`;
+      // Pair alignée DB/serveur : EUR -> BTC/EUR ; USDT -> BTC/USDT
+      const quote = settings.currency === "USDT" ? "USDT" : "EUR";
+      const pair = `BTC/${quote}`;
 
-      // 1) Serveur d’abord (align Stéphane)
+      // 1) Serveur d’abord (source de vérité)
       await selectStrategy({ pair, mode: key, params: {} });
 
       // 2) Puis local (affichage immédiat)
@@ -75,7 +75,7 @@ export default function StrategyScreen() {
       await saveSettings(rollback);
       setSettings(rollback);
 
-      setInfo(`Erreur serveur stratégie : ${e?.message ?? "inconnue"}`);
+      setInfo(`Impossible de sélectionner la stratégie. (${e?.message ?? "Erreur inconnue"})`);
     } finally {
       setBusy(false);
     }
@@ -90,7 +90,9 @@ export default function StrategyScreen() {
         ListHeaderComponent={
           <View style={ui.card}>
             <Text style={ui.title}>Stratégie</Text>
-            <Text style={ui.muted}>Choisis une stratégie. Elle est enregistrée sur le serveur.</Text>
+            <Text style={ui.muted}>
+              Choisissez une stratégie. Elle est enregistrée sur le serveur.
+            </Text>
 
             <Text style={[ui.muted, { marginTop: 8 }]}>
               Actuelle : <Text style={styles.boldInline}>{settings.selectedStrategyKey}</Text>
@@ -115,11 +117,11 @@ export default function StrategyScreen() {
                 isSelected(item.key) && styles.btnSelected,
                 busy && styles.btnDisabled,
               ]}
-              onPress={() => handleSelect(item.key)}
+              onPress={() => void handleSelect(item.key)}
               disabled={busy}
             >
               <Text style={ui.buttonText}>
-                {isSelected(item.key) ? "Sélectionnée ✅" : busy ? "Envoi..." : "Sélectionner"}
+                {isSelected(item.key) ? "Sélectionnée ✅" : busy ? "Envoi" : "Sélectionner"}
               </Text>
             </TouchableOpacity>
           </View>
index 3f5f36cc6ad50c0a37d73e2ccabb4637027711fa..84a9c2018cf15dfd256e2d07fc9ea5c50c6fae32 100644 (file)
@@ -18,39 +18,67 @@ export default function TutorialScreen({ onDone }: { onDone: () => void }) {
     () => [
       {
         key: "intro",
-        title: "La crypto, c’est quoi ?",
+        title: "Bienvenue sur Wallette",
         text:
-          "Une cryptomonnaie est une monnaie numérique. Son prix varie selon l’offre et la demande, et peut bouger très vite.",
+          "Wallette vous aide à suivre le marché via des signaux et des alertes.\n\nObjectif : comprendre et observer. Pas “trader” pour de vrai.",
         icon: "sparkles-outline",
       },
       {
-        key: "types",
-        title: "Types de cryptos",
+        key: "crypto_basics",
+        title: "La crypto, c’est quoi ?",
         text:
-          "Exemples :\n• BTC : la plus connue\n• ETH : écosystème de contrats\n• Stablecoins (USDT/USDC) : valeur ≈ 1 USD\n\nLes stablecoins servent souvent d’intermédiaire pour convertir.",
-        icon: "layers-outline",
+          "Une cryptomonnaie est une monnaie numérique.\nSon prix varie selon l’offre et la demande, parfois très vite.",
+        icon: "planet-outline",
       },
       {
-        key: "role",
-        title: "Rôle de Wall-e-tte",
+        key: "pairs",
+        title: "Paires BTC/EUR et BTC/USDT",
         text:
-          "Wall-e-tte est un assistant : il aide à suivre le marché, les signaux et les alertes.\n\nCe n’est PAS un conseiller financier.",
-        icon: "shield-checkmark-outline",
+          "L’application utilise des paires de prix :\n• BTC/EUR\n• BTC/USDT\n\nUSDT est un “stablecoin” proche de 1 dollar.",
+        icon: "swap-horizontal-outline",
+      },
+      {
+        key: "signal",
+        title: "Signal du marché",
+        text:
+          "Le signal résume la décision d’une stratégie :\n• BUY (acheter)\n• SELL (vendre)\n• HOLD (attendre)\n• STOP_LOSS (se protéger)\n\nLe signal inclut une confiance (%) et une raison.",
+        icon: "pulse-outline",
+      },
+      {
+        key: "alerts",
+        title: "Alertes",
+        text:
+          "Les alertes sont envoyées en temps réel.\nElles peuvent être CRITICAL / WARNING / INFO.\n\nAstuce : commencez par les alertes CRITICAL.",
+        icon: "alert-circle-outline",
       },
       {
-        key: "app",
-        title: "Comment utiliser l’app",
+        key: "wallet",
+        title: "Portefeuille",
         text:
-          "• Dashboard : résumé (prix, stratégie, urgence)\n• Portefeuille : ajouter des cryptos + quantités\n• Alertes : tri + filtres (CRITICAL en priorité)\n• Historique : signaux récents",
-        icon: "apps-outline",
+          "L’écran Portefeuille affiche votre solde BTC et l’historique des transactions.\n\nLes données proviennent du serveur du projet.",
+        icon: "wallet-outline",
+      },
+      {
+        key: "strategy",
+        title: "Stratégie",
+        text:
+          "Une stratégie analyse le marché et produit des signaux.\n\nVous pouvez en sélectionner une dans l’écran “Stratégie”.",
+        icon: "analytics-outline",
       },
       {
         key: "settings",
-        title: "Paramètres importants",
+        title: "Paramètres",
         text:
-          "• Stratégie : choisir la méthode d’analyse\n• Notifications : recevoir les alertes\n• Devise : EUR/USD\n\nLe tutoriel reste accessible dans Paramètres.",
+          "Vous pouvez régler :\n• Devise : EUR ou USDT\n• Rafraîchissement : manuel ou auto\n• Notifications : activer/désactiver\n\nLe tutoriel reste accessible via Paramètres.",
         icon: "settings-outline",
       },
+      {
+        key: "warning",
+        title: "Avertissement",
+        text:
+          "Wallette est un outil éducatif.\nCe n’est pas un conseil financier.\n\nNe mettez jamais d’argent réel sur base d’un projet étudiant.",
+        icon: "shield-checkmark-outline",
+      },
     ],
     []
   );
@@ -91,7 +119,7 @@ export default function TutorialScreen({ onDone }: { onDone: () => void }) {
           </TouchableOpacity>
         </View>
 
-        {/* CONTENT (on laisse de la place en bas pour la barre fixe) */}
+        {/* CONTENT */}
         <View style={[styles.content, ui.container]}>
           <View style={styles.iconWrap}>
             <Ionicons name={slide.icon} size={38} color="#0f172a" style={{ opacity: 0.85 }} />
@@ -124,9 +152,7 @@ export default function TutorialScreen({ onDone }: { onDone: () => void }) {
               style={[styles.primaryBtn]}
               onPress={isLast ? finish : next}
             >
-              <Text style={styles.primaryText}>
-                {isLast ? "Terminer" : "Suivant"}
-              </Text>
+              <Text style={styles.primaryText}>{isLast ? "Terminer" : "Suivant"}</Text>
             </TouchableOpacity>
           </View>
         </View>
@@ -150,12 +176,11 @@ const styles = StyleSheet.create({
   small: { fontWeight: "900", color: "#0f172a", opacity: 0.6 },
   skip: { fontWeight: "900", color: "#0f172a", opacity: 0.75 },
 
-  // On réserve de la place pour la barre fixe
   content: {
     flex: 1,
     alignItems: "center",
     justifyContent: "center",
-    paddingBottom: 140, // ✅ réserve l’espace pour les boutons fixes
+    paddingBottom: 140,
   },
 
   iconWrap: {
@@ -180,7 +205,6 @@ const styles = StyleSheet.create({
     lineHeight: 20,
   },
 
-  // ✅ barre du bas fixée
   bottomFixed: {
     position: "absolute",
     left: 0,
@@ -221,7 +245,7 @@ const styles = StyleSheet.create({
     paddingVertical: 12,
     alignItems: "center",
     justifyContent: "center",
-    backgroundColor: "#16a34a", // ✅ un vert "crypto friendly"
+    backgroundColor: "#16a34a",
   },
   primaryText: { color: "#fff", fontWeight: "900" },
 
index 9ade6c1ba1d4608501a2ec59c0c6f4a4a82aa3d0..e12b51969d140fc4f07f45e9c0b074b3a7307f6a 100644 (file)
-import {
-  View,
-  Text,
-  StyleSheet,
-  TouchableOpacity,
-  TextInput,
-  Alert as RNAlert,
-  FlatList,
-} from "react-native";
+import { View, Text, StyleSheet, FlatList } from "react-native";
 import { useCallback, useMemo, useState } from "react";
 import { SafeAreaView } from "react-native-safe-area-context";
 import { useFocusEffect } from "@react-navigation/native";
 
 import { ui } from "../components/ui/uiStyles";
-import type { PortfolioAsset, PortfolioState } from "../models/Portfolio";
 import { loadSettings } from "../utils/settingsStorage";
 import type { UserSettings } from "../models/UserSettings";
+import type { PortfolioState } from "../models/Portfolio";
 
-/**
- * ✅ API-only (wallet)
- */
-import { getPortfolio, upsertPortfolio, resetPortfolio } from "../services/api/walletApi";
-
-/**
- * ✅ API-only (price)
- */
+import { getPortfolio, getPrimaryWalletId, getWalletEvents, type WalletEvent } from "../services/api/walletApi";
 import { getCurrentPrice } from "../services/api/priceApi";
 
 /**
- * WalletScreen (Step 3/4)
- * -----------------------
- * Multi-cryptos
- * - quantité par asset
- * - valeur globale
+ * WalletScreen (serveur/DB only)
+ * -----------------------------
+ * Affiche uniquement ce que le serveur fournit :
+ * - Wallet BTC (quantité)
+ * - Valeur estimée (prix BTC/EUR ou BTC/USDT)
+ * - Historique events du wallet (/wallets/:id/events)
  *
- * Prix : API-only (pas de mock)
+ * Aucun ajout/modif local ici (aligné consigne + DB).
  */
+
+function formatNumber(n: number, decimals = 6) {
+  return Number.isFinite(n) ? n.toFixed(decimals) : "0.000000";
+}
+
+function quoteFromSettings(s: UserSettings): "EUR" | "USDT" {
+  return s.currency === "USDT" ? "USDT" : "EUR";
+}
+
+function asNumber(x: any): number {
+  const n = typeof x === "number" ? x : Number(x);
+  return Number.isFinite(n) ? n : 0;
+}
+
+function formatDate(ms?: number) {
+  const t = typeof ms === "number" && Number.isFinite(ms) ? ms : Date.now();
+  return new Date(t).toLocaleString();
+}
+
 export default function WalletScreen() {
   const [portfolio, setPortfolio] = useState<PortfolioState | null>(null);
   const [settings, setSettings] = useState<UserSettings | null>(null);
 
-  // Prix chargés depuis l'API (par symbole)
-  const [prices, setPrices] = useState<Record<string, number>>({});
-  const [pricesError, setPricesError] = useState<string | null>(null);
+  const [walletId, setWalletId] = useState<string | null>(null);
 
-  // Ajout asset
-  const [symbolInput, setSymbolInput] = useState<string>("BTC");
-  const [qtyInput, setQtyInput] = useState<string>("0");
+  const [btcPrice, setBtcPrice] = useState<number | null>(null);
+  const [priceError, setPriceError] = useState<string | null>(null);
 
-  const [info, setInfo] = useState<string | null>(null);
+  const [events, setEvents] = useState<WalletEvent[]>([]);
+  const [eventsError, setEventsError] = useState<string | null>(null);
 
   useFocusEffect(
     useCallback(() => {
       let active = true;
 
       async function init() {
-        setInfo(null);
-        setPricesError(null);
+        setPriceError(null);
+        setEventsError(null);
 
-        const [p, s] = await Promise.all([getPortfolio(), loadSettings()]);
+        const s = await loadSettings();
         if (!active) return;
-
-        setPortfolio(p);
         setSettings(s);
 
-        // Charger les prix pour les assets actuels
+        // WalletId + Portfolio serveur
         try {
-          const entries = await Promise.all(
-            p.assets.map(async (a) => {
-              const pair = `${a.symbol}/${s.currency}`;
-              const cur = await getCurrentPrice(pair);
-              return [a.symbol, cur.price] as const;
-            })
-          );
-
+          const [id, p] = await Promise.all([getPrimaryWalletId(), getPortfolio()]);
           if (!active) return;
 
-          const map: Record<string, number> = {};
-          for (const [sym, pr] of entries) map[sym] = pr;
-          setPrices(map);
+          setWalletId(id);
+          setPortfolio(p);
+
+          // Prix BTC (pair DB only)
+          const quote = quoteFromSettings(s);
+          const pair = `BTC/${quote}`;
+          try {
+            const cur = await getCurrentPrice(pair);
+            if (!active) return;
+            setBtcPrice(cur.price);
+          } catch (e: any) {
+            if (!active) return;
+            setBtcPrice(null);
+            setPriceError(e?.message ?? "Impossible de charger le prix BTC.");
+          }
+
+          // Events
+          if (id) {
+            try {
+              const evts = await getWalletEvents(50);
+              if (!active) return;
+              setEvents(evts);
+            } catch (e: any) {
+              if (!active) return;
+              setEvents([]);
+              setEventsError(e?.message ?? "Impossible de charger l’historique des transactions.");
+            }
+          } else {
+            setEvents([]);
+          }
         } catch (e: any) {
           if (!active) return;
-          setPricesError(e?.message ?? "Impossible de charger les prix (API).");
+          setPortfolio({ assets: [], updatedAtMs: Date.now() });
+          setWalletId(null);
+          setEvents([]);
+          setEventsError(e?.message ?? "Impossible de charger le portefeuille.");
         }
       }
 
@@ -92,135 +116,18 @@ export default function WalletScreen() {
     }, [])
   );
 
-  const lastUpdatedLabel = useMemo(() => {
-    if (!portfolio) return "—";
-    return new Date(portfolio.updatedAtMs).toLocaleString();
+  const btcQty = useMemo(() => {
+    if (!portfolio) return 0;
+    const a = portfolio.assets.find((x) => x.symbol === "BTC");
+    return a?.quantity ?? 0;
   }, [portfolio]);
 
-  const parsedQty = useMemo(() => {
-    const normalized = qtyInput.replace(",", ".").trim();
-    const val = Number(normalized);
-    if (!Number.isFinite(val)) return null;
-    if (val < 0) return null;
-    return val;
-  }, [qtyInput]);
-
-  const normalizedSymbol = useMemo(() => symbolInput.toUpperCase().trim(), [symbolInput]);
+  const quote = useMemo(() => (settings ? quoteFromSettings(settings) : "EUR"), [settings]);
 
   const totalValue = useMemo(() => {
-    if (!portfolio) return 0;
-
-    return portfolio.assets.reduce((sum, a) => {
-      const price = prices[a.symbol];
-      if (typeof price !== "number") return sum; // prix absent => on ignore
-      return sum + a.quantity * price;
-    }, 0);
-  }, [portfolio, prices]);
-
-  const handleAddOrUpdate = async () => {
-    if (!portfolio || !settings) return;
-
-    setInfo(null);
-
-    if (!normalizedSymbol || normalizedSymbol.length < 2) {
-      setInfo("Symbole invalide (ex: BTC).");
-      return;
-    }
-
-    if (parsedQty === null) {
-      setInfo("Quantité invalide. Exemple : 0.25");
-      return;
-    }
-
-    // ✅ API-only : on vérifie que la paire est récupérable
-    try {
-      const pair = `${normalizedSymbol}/${settings.currency}`;
-      const cur = await getCurrentPrice(pair);
-
-      // on mémorise le prix reçu (utile pour affichage)
-      setPrices((prev) => ({ ...prev, [normalizedSymbol]: cur.price }));
-      setPricesError(null);
-    } catch (e: any) {
-      setInfo(`Prix indisponible (API) pour ${normalizedSymbol}/${settings.currency}.`);
-      setPricesError(e?.message ?? "Erreur API prix.");
-      return;
-    }
-
-    const existingIndex = portfolio.assets.findIndex((a) => a.symbol === normalizedSymbol);
-
-    let updatedAssets: PortfolioAsset[];
-    if (existingIndex >= 0) {
-      updatedAssets = portfolio.assets.map((a) =>
-        a.symbol === normalizedSymbol ? { ...a, quantity: parsedQty } : a
-      );
-    } else {
-      updatedAssets = [...portfolio.assets, { symbol: normalizedSymbol, quantity: parsedQty }];
-    }
-
-    const updated: PortfolioState = {
-      assets: updatedAssets,
-      updatedAtMs: Date.now(),
-    };
-
-    await upsertPortfolio(updated);
-    setPortfolio(updated);
-
-    setInfo(existingIndex >= 0 ? "Asset mis à jour ✅" : "Asset ajouté ✅");
-  };
-
-  const handleDelete = (symbol: string) => {
-    if (!portfolio) return;
-
-    RNAlert.alert(
-      `Supprimer ${symbol} ?`,
-      "Cette action retire l’asset du portefeuille.",
-      [
-        { text: "Annuler", style: "cancel" },
-        {
-          text: "Supprimer",
-          style: "destructive",
-          onPress: async () => {
-            const updated: PortfolioState = {
-              assets: portfolio.assets.filter((a) => a.symbol !== symbol),
-              updatedAtMs: Date.now(),
-            };
-
-            await upsertPortfolio(updated);
-            setPortfolio(updated);
-
-            // nettoyage prix local
-            setPrices((prev) => {
-              const next = { ...prev };
-              delete next[symbol];
-              return next;
-            });
-
-            setInfo(`${symbol} supprimé ✅`);
-          },
-        },
-      ]
-    );
-  };
-
-  const handleClear = () => {
-    RNAlert.alert(
-      "Réinitialiser le portefeuille ?",
-      "Cela supprime tous les assets du portefeuille.",
-      [
-        { text: "Annuler", style: "cancel" },
-        {
-          text: "Réinitialiser",
-          style: "destructive",
-          onPress: async () => {
-            const fresh = await resetPortfolio();
-            setPortfolio(fresh);
-            setPrices({});
-            setInfo("Portefeuille réinitialisé ✅");
-          },
-        },
-      ]
-    );
-  };
+    if (btcPrice === null) return null;
+    return btcQty * btcPrice;
+  }, [btcQty, btcPrice]);
 
   if (!portfolio || !settings) {
     return (
@@ -234,105 +141,91 @@ export default function WalletScreen() {
     <SafeAreaView style={styles.safeArea}>
       <FlatList
         contentContainerStyle={ui.container}
-        data={portfolio.assets}
-        keyExtractor={(it) => it.symbol}
+        data={events}
+        keyExtractor={(it, idx) => String(it.event_id ?? idx)}
         ListHeaderComponent={
           <View>
             <Text style={styles.screenTitle}>Portefeuille</Text>
 
-            {/* Résumé global */}
+            {/* Résumé wallet */}
             <View style={ui.card}>
               <Text style={ui.title}>Résumé</Text>
 
-              <View style={ui.rowBetween}>
-                <Text style={ui.value}>Valeur globale :</Text>
+              <Text style={[ui.muted, { marginTop: 6 }]}>
+                WalletId : <Text style={styles.boldInline}>{walletId ?? "—"}</Text>
+              </Text>
+
+              <View style={[ui.rowBetween, { marginTop: 10 }]}>
+                <Text style={ui.value}>Solde BTC</Text>
+                <Text style={ui.valueBold}>{formatNumber(btcQty, 6)} BTC</Text>
+              </View>
+
+              <View style={[ui.rowBetween, { marginTop: 6 }]}>
+                <Text style={ui.value}>Prix BTC</Text>
                 <Text style={ui.valueBold}>
-                  {totalValue.toFixed(2)} {settings.currency}
+                  {btcPrice !== null ? `${btcPrice.toFixed(2)} ${quote}` : "—"}
                 </Text>
               </View>
 
-              <Text style={[ui.muted, { marginTop: 6 }]}>
-                Dernière mise à jour : <Text style={styles.boldInline}>{lastUpdatedLabel}</Text>
-              </Text>
+              <View style={[ui.rowBetween, { marginTop: 6 }]}>
+                <Text style={ui.value}>Valeur estimée</Text>
+                <Text style={ui.valueBold}>
+                  {totalValue !== null ? `${totalValue.toFixed(2)} ${quote}` : "—"}
+                </Text>
+              </View>
 
-              {!!pricesError && (
-                <Text style={[ui.muted, { marginTop: 6 }]}>
-                  ⚠️ {pricesError}
+              {!!priceError && (
+                <Text style={[ui.muted, { marginTop: 10 }]}>
+                  {priceError}
                 </Text>
               )}
-
-              <TouchableOpacity style={styles.secondaryButton} onPress={handleClear}>
-                <Text style={styles.secondaryButtonText}>Réinitialiser</Text>
-              </TouchableOpacity>
             </View>
 
-            {/* Ajouter / modifier */}
+            {/* Historique */}
             <View style={ui.card}>
-              <Text style={ui.title}>Ajouter / Modifier un asset</Text>
-
-              <Text style={ui.muted}>Symbole (ex: BTC, ETH, SOL)</Text>
-              <TextInput
-                value={symbolInput}
-                onChangeText={setSymbolInput}
-                autoCapitalize="characters"
-                placeholder="BTC"
-                style={styles.input}
-              />
-
-              <Text style={[ui.muted, { marginTop: 10 }]}>Quantité</Text>
-              <TextInput
-                value={qtyInput}
-                onChangeText={setQtyInput}
-                keyboardType="decimal-pad"
-                placeholder="0.25"
-                style={styles.input}
-              />
-
-              <TouchableOpacity style={[ui.button, styles.fullButton]} onPress={handleAddOrUpdate}>
-                <Text style={ui.buttonText}>Enregistrer</Text>
-              </TouchableOpacity>
-
-              {!!info && <Text style={[ui.muted, { marginTop: 10 }]}>{info}</Text>}
+              <Text style={ui.title}>Historique des transactions</Text>
+              <Text style={ui.muted}>
+                Données issues du serveur (events du wallet).
+              </Text>
+
+              {!!eventsError && (
+                <Text style={[ui.muted, { marginTop: 10 }]}>
+                  {eventsError}
+                </Text>
+              )}
             </View>
 
-            <Text style={[ui.muted, { marginBottom: 10 }]}>Liste des assets :</Text>
+            <Text style={[ui.muted, { marginBottom: 10 }]}>Derniers événements :</Text>
           </View>
         }
         ListEmptyComponent={
           <View style={ui.card}>
-            <Text style={ui.title}>Aucun asset</Text>
-            <Text style={ui.muted}>Ajoutez BTC/ETH/SOL… pour commencer.</Text>
+            <Text style={ui.title}>Aucune transaction</Text>
+            <Text style={ui.muted}>Aucun événement enregistré pour l’instant.</Text>
           </View>
         }
         renderItem={({ item }) => {
-          const price = prices[item.symbol];
-          const value = typeof price === "number" ? item.quantity * price : null;
+          const type = String(item.event_type ?? "—").toUpperCase();
+          const qty = asNumber(item.quantity_base);
+          const price = asNumber(item.price_quote);
+          const q = String(item.quote_symbol ?? quote);
 
           return (
             <View style={ui.card}>
               <View style={ui.rowBetween}>
-                <Text style={ui.valueBold}>{item.symbol}</Text>
-                <TouchableOpacity onPress={() => handleDelete(item.symbol)}>
-                  <Text style={styles.deleteText}>Supprimer</Text>
-                </TouchableOpacity>
+                <Text style={ui.valueBold}>{type}</Text>
+                <Text style={ui.muted}>{formatDate(item.executed_at_ms)}</Text>
               </View>
 
               <View style={[ui.rowBetween, { marginTop: 8 }]}>
                 <Text style={ui.value}>Quantité</Text>
-                <Text style={ui.valueBold}>{item.quantity.toFixed(6)}</Text>
+                <Text style={ui.valueBold}>{formatNumber(qty, 6)} BTC</Text>
               </View>
 
               <View style={[ui.rowBetween, { marginTop: 6 }]}>
                 <Text style={ui.value}>Prix</Text>
                 <Text style={ui.valueBold}>
-                  {typeof price === "number" ? `${price.toFixed(2)} ${settings.currency}` : "—"}
-                </Text>
-              </View>
-
-              <View style={[ui.rowBetween, { marginTop: 6 }]}>
-                <Text style={ui.value}>Valeur</Text>
-                <Text style={ui.valueBold}>
-                  {value !== null ? `${value.toFixed(2)} ${settings.currency}` : "—"}
+                  {price > 0 ? `${price.toFixed(2)} ${q}` : "—"}
                 </Text>
               </View>
             </View>
@@ -344,55 +237,7 @@ export default function WalletScreen() {
 }
 
 const styles = StyleSheet.create({
-  safeArea: {
-    flex: 1,
-    backgroundColor: ui.screen.backgroundColor,
-  },
-  screenTitle: {
-    fontSize: 22,
-    fontWeight: "900",
-    marginBottom: 12,
-    color: "#0f172a",
-  },
-  boldInline: {
-    fontWeight: "900",
-    color: "#0f172a",
-  },
-
-  input: {
-    borderWidth: 1,
-    borderColor: "#e5e7eb",
-    borderRadius: 10,
-    paddingHorizontal: 12,
-    paddingVertical: 10,
-    marginTop: 8,
-    backgroundColor: "#fff",
-    color: "#0f172a",
-  },
-
-  fullButton: {
-    flexGrow: 0,
-    flexBasis: "auto",
-    width: "100%",
-    marginTop: 12,
-  },
-
-  secondaryButton: {
-    marginTop: 10,
-    paddingVertical: 10,
-    borderRadius: 10,
-    borderWidth: 1,
-    borderColor: "#e5e7eb",
-    alignItems: "center",
-    backgroundColor: "#fff",
-  },
-  secondaryButtonText: {
-    fontWeight: "900",
-    color: "#dc2626",
-  },
-
-  deleteText: {
-    fontWeight: "900",
-    color: "#dc2626",
-  },
+  safeArea: { flex: 1, backgroundColor: ui.screen.backgroundColor },
+  screenTitle: { fontSize: 22, fontWeight: "900", marginBottom: 12, color: "#0f172a" },
+  boldInline: { fontWeight: "900", color: "#0f172a" },
 });
\ No newline at end of file
index 347dd624989238c1fd8462e79c64c1b202d3dc14..7da29210283148dda0fe23808c0a5cddd8f845f9 100644 (file)
@@ -7,21 +7,18 @@ import { loadSession, saveSession, clearSession } from "../../utils/sessionStora
 /**
  * authApi.ts
  * ----------
- * Façade d'authentification.
+ * Décision projet : PAS d'auth serveur.
  *
- * Aujourd'hui (Step local) :
- * - login/register/logout en local (AsyncStorage)
+ * IMPORTANT :
+ * - Les services (alerts/signal/wallets) utilisent un userId FIXE côté serveur : "user-123".
+ * - Donc la session côté mobile doit utiliser ce userId pour toutes les routes API.
  *
- * Demain (API ready) :
- * - tu pourras remplacer l'implémentation par :
- *   POST /api/auth/login
- *   POST /api/auth/register
- *   GET  /api/auth/me
- *   POST /api/auth/logout
- *
- * L'UI ne change pas : elle appelle toujours authApi.
+ * L'auth locale sert uniquement à l'expérience utilisateur (écran de connexion),
+ * mais le userId utilisé pour l'API = "user-123" (align serveur).
  */
 
+const SERVER_USER_ID = "user-123";
+
 export type AuthResult =
   | { ok: true; user: AuthUser; session: Session }
   | { ok: false; message: string };
@@ -35,8 +32,9 @@ export async function register(params: {
   const res = await createUser(params);
   if (!res.ok) return { ok: false, message: res.message };
 
+  // ✅ userId aligné serveur
   const session: Session = {
-    userId: res.user.userId,
+    userId: SERVER_USER_ID,
     email: res.user.email,
     createdAtMs: Date.now(),
   };
@@ -53,8 +51,9 @@ export async function login(params: {
   const res = await verifyLogin(params);
   if (!res.ok) return { ok: false, message: res.message };
 
+  // ✅ userId aligné serveur
   const session: Session = {
-    userId: res.user.userId,
+    userId: SERVER_USER_ID,
     email: res.user.email,
     createdAtMs: Date.now(),
   };
@@ -69,19 +68,18 @@ export async function logout(): Promise<void> {
 }
 
 /**
- * Retourne l'utilisateur courant (si session active).
- * Utile pour afficher profil / sécuriser certaines actions.
+ * Retourne l'utilisateur courant (profil local).
+ * (Le userId serveur étant fixe, on conserve le profil local via la recherche par session.email si besoin.)
  */
 export async function getCurrentUser(): Promise<AuthUser | null> {
   const session = await loadSession();
-  if (!session?.userId) return null;
+  if (!session) return null;
 
-  return await findUserById(session.userId);
+  // On tente par userId local d'abord (si jamais), sinon on retombe sur findUserById.
+  // Note : si ton système local lie le profil à un autre userId, on peut améliorer plus tard.
+  return await findUserById((session as any).userId ?? SERVER_USER_ID);
 }
 
-/**
- * Juste la session (pratique pour guards).
- */
 export async function getSession(): Promise<Session | null> {
   return await loadSession();
 }
\ No newline at end of file
index 0cb2dbcd50f079613ef3c23483230f6954b530b8..2a1e8ab98cc1ca54b8ecc612a6fd92057d4523ac 100644 (file)
@@ -16,51 +16,36 @@ function safeLevel(level: any): AlertLevel {
   return "INFO";
 }
 
-/**
- * dashboardApi (contrat Stéphane - STRICT)
- * ---------------------------------------
- * - GET /api/price/current?pair=BTC/EUR
- * - GET /api/signal/current?userId=...&pair=BTC/EUR
- */
 export async function getDashboardSummary(): Promise<DashboardSummary> {
   const session = await loadSession();
   const userId = session?.userId;
   if (!userId) throw new Error("Session absente : impossible de charger le dashboard.");
 
   const settings = await loadSettings();
-  const currency: "EUR" | "USD" = settings.currency === "USD" ? "USD" : "EUR";
+  const quote = settings.currency === "USDT" ? "USDT" : "EUR";
+  const pair = `BTC/${quote}`;
 
-  // Dashboard strict : BTC seulement (align web). Adaptable plus tard.
-  const pair = `BTC/${currency}`;
-
-  // 1) Prix (service prix)
   const price = await getCurrentPrice(pair);
 
-  // 2) Signal (service stratégies)
   const sig = await apiGet<any>(
     `/signal/current?userId=${encodeURIComponent(userId)}&pair=${encodeURIComponent(pair)}`
   );
 
-  const decision = safeDecision(sig?.action ?? sig?.decision);
-  const alertLevel = safeLevel(sig?.alertLevel ?? sig?.level ?? sig?.criticality);
-  const confidence = typeof sig?.confidence === "number" ? sig.confidence : 0;
-  const reason = String(sig?.reason ?? sig?.message ?? "—");
-
-  const timestamp =
-    typeof sig?.timestamp === "number"
-      ? sig.timestamp
-      : typeof sig?.timestamp_ms === "number"
-      ? sig.timestamp_ms
-      : price.timestampMs;
+  const data = sig ?? null;
 
   return {
     pair,
     price: price.price,
     strategy: settings.selectedStrategyKey,
-    decision,
-    confidence,
-    reason,
-    alertLevel,
-    timestamp,
+    decision: safeDecision(data?.action ?? data?.decision),
+    confidence: typeof data?.confidence === "number" ? data.confidence : 0,
+    reason: String(data?.reason ?? data?.message ?? "—"),
+    alertLevel: safeLevel(data?.alertLevel ?? data?.level ?? data?.criticality),
+    timestamp:
+      typeof data?.timestamp_ms === "number"
+        ? data.timestamp_ms
+        : typeof data?.timestamp === "number"
+        ? data.timestamp
+        : price.timestampMs,
   };
 }
\ No newline at end of file
index 404429a4bb39edb2187ff1b2b0ca37eafeba2380..7122ca8df648430356052011394ad8a5c9f36661 100644 (file)
-import type { Signal } from "../../types/Signal";
 import { apiGet } from "./http";
 import { loadSession } from "../../utils/sessionStorage";
+import type { Signal, SignalAction, SignalCriticality, SignalStatus } from "../../types/Signal";
 
 /**
- * signalApi (API-only)
- * --------------------
- * NOTE: endpoint à confirmer/aligner avec le chef.
- * Proposition :
- * - GET /api/signal/recent?userId=...&limit=20
+ * signalApi (align serveur + gateway, robust)
+ * ------------------------------------------
+ * - Charge l'historique des signaux depuis strategy-service.
+ * - Normalise les champs (snake_case -> camelCase) pour coller à types/Signal.ts
+ * - Pas de mock.
  */
+
+function asArray(x: any): any[] {
+  if (Array.isArray(x)) return x;
+  if (Array.isArray(x?.signals)) return x.signals;
+  if (Array.isArray(x?.events)) return x.events;
+  if (Array.isArray(x?.items)) return x.items;
+  return [];
+}
+
+function safeAction(a: any): SignalAction {
+  const v = String(a ?? "HOLD").toUpperCase();
+  if (v === "BUY" || v === "SELL" || v === "STOP_LOSS") return v as SignalAction;
+  return "HOLD";
+}
+
+function safeCriticality(c: any): SignalCriticality {
+  const v = String(c ?? "INFO").toUpperCase();
+  if (v === "CRITICAL" || v === "WARNING") return v as SignalCriticality;
+  return "INFO";
+}
+
+function safeStatus(s: any): SignalStatus {
+  const v = String(s ?? "ACTIVE").toUpperCase();
+  if (v === "SUPERSEDED" || v === "EXECUTED" || v === "INVALID") return v as SignalStatus;
+  return "ACTIVE";
+}
+
+function toNumber(x: any, fallback = 0): number {
+  const n = typeof x === "number" ? x : Number(x);
+  return Number.isFinite(n) ? n : fallback;
+}
+
+function normalizeSignal(raw: any, idx: number): Signal {
+  // id
+  const id =
+    String(
+      raw?.signalId ??
+        raw?.signal_id ??
+        raw?.id ??
+        raw?.event_id ??
+        `sig_${Date.now()}_${idx}`
+    );
+
+  const pair = String(raw?.pair ?? raw?.pair_code ?? "BTC/EUR");
+
+  const timestamp = toNumber(raw?.timestamp ?? raw?.timestamp_ms ?? raw?.created_at_ms ?? Date.now(), Date.now());
+
+  const action = safeAction(raw?.action ?? raw?.decision);
+  const criticality = safeCriticality(raw?.criticality ?? raw?.alertLevel ?? raw?.level);
+  const status = safeStatus(raw?.status);
+
+  const confidence = Math.max(0, Math.min(1, toNumber(raw?.confidence ?? 0, 0)));
+
+  const reason = String(raw?.reason ?? raw?.message ?? "—");
+
+  const priceAtSignal = toNumber(
+    raw?.priceAtSignal ?? raw?.price_at_signal ?? raw?.price ?? raw?.current_price ?? 0,
+    0
+  );
+
+  return {
+    signalId: id,
+    pair,
+    timestamp,
+    action,
+    criticality,
+    status,
+    confidence,
+    reason,
+    priceAtSignal,
+  };
+}
+
 export async function getRecentSignals(limit = 20): Promise<Signal[]> {
   const session = await loadSession();
   const userId = session?.userId;
-  if (!userId) throw new Error("Session absente : impossible de charger l'historique.");
+  if (!userId) throw new Error("Session absente.");
 
-  const data = await apiGet<{ signals: Signal[] }>(
-    `/signal/recent?userId=${encodeURIComponent(userId)}&limit=${encodeURIComponent(String(limit))}`
-  );
+  const candidates = [
+    `/signal/recent?userId=${encodeURIComponent(userId)}&limit=${encodeURIComponent(String(limit))}`,
+    `/signal/events?userId=${encodeURIComponent(userId)}&limit=${encodeURIComponent(String(limit))}`,
+    `/signal/history?userId=${encodeURIComponent(userId)}&limit=${encodeURIComponent(String(limit))}`,
+  ];
+
+  let lastErr: unknown = null;
+
+  for (const path of candidates) {
+    try {
+      const data = await apiGet<any>(path);
+      const arr = asArray(data);
+
+      // si route existe mais vide -> ok
+      if (arr.length === 0 && (data || data === null)) return [];
+
+      return arr.map((x, i) => normalizeSignal(x, i));
+    } catch (e) {
+      lastErr = e;
+    }
+  }
 
-  return data.signals ?? [];
+  throw lastErr ?? new Error("Impossible de charger les signaux.");
 }
\ No newline at end of file
index d022938ca5fd312f3d50e72d51b5bff34ede29f6..336ab73d26d3bb3da4a45559ca6444e31f4593ec 100644 (file)
@@ -13,12 +13,12 @@ import type { StrategyKey } from "../../types/Strategy";
  */
 
 function pairToPairId(pair: string): number {
-  // Convention du projet : (MarketDataRepo mentionne pairId=1 pour BTC)
   const p = pair.trim().toUpperCase();
-  if (p.startsWith("BTC/")) return 1;
-  if (p.startsWith("ETH/")) return 2;
-  if (p.startsWith("LTC/")) return 3;
-  throw new Error(`Pair non supportée (pas de pairId) : ${pair}`);
+
+  if (p === "BTC/EUR") return 1;
+  if (p === "BTC/USDT") return 2;
+
+  throw new Error(`Pair non supportée par la DB (pairs) : ${pair}`);
 }
 
 export async function selectStrategy(params: {
index 2f1a4a2dade52c4edaec48d7b2fb67b14c74d684..9c96ca5380766c9933f1aa95745f5e24707ccf7c 100644 (file)
@@ -1,16 +1,21 @@
-import { apiGet } from "./http";
+import { apiGet, apiPost } from "./http";
 import { loadSession } from "../../utils/sessionStorage";
 import type { PortfolioState } from "../../models/Portfolio";
 
 /**
- * walletApi (contrat Stéphane - STRICT)
- * ------------------------------------
+ * walletApi (aligné wallet-service)
+ * --------------------------------
  * - GET /api/wallets?userId=...
+ *   -> { wallets:[...], count }
  * - GET /api/wallets/:id
+ *   -> { wallet:{...} }
  * - GET /api/wallets/:id/events
+ *   -> { events:[...], count }
+ * - POST /api/wallets/:id/events
+ *   -> body.event_type obligatoire
  */
 
-type WalletListResponse = any;
+type WalletListResponse = { wallets?: any[]; count?: number } | any;
 
 function pickWalletId(list: any): string | null {
   const arr =
@@ -24,6 +29,7 @@ function pickWalletId(list: any): string | null {
 
   const first = arr[0];
   return (
+    (typeof first?.wallet_id === "string" && first.wallet_id) ||
     (typeof first?.walletId === "string" && first.walletId) ||
     (typeof first?.id === "string" && first.id) ||
     (typeof first?._id === "string" && first._id) ||
@@ -31,57 +37,45 @@ function pickWalletId(list: any): string | null {
   );
 }
 
-function extractAssets(details: any): { symbol: string; quantity: number }[] {
-  const assetsArr =
-    (Array.isArray(details?.portfolio?.assets) && details.portfolio.assets) ||
-    (Array.isArray(details?.assets) && details.assets) ||
-    null;
-
-  if (assetsArr) {
-    return assetsArr
-      .filter((a: any) => a && typeof a.symbol === "string" && typeof a.quantity === "number")
-      .map((a: any) => ({ symbol: String(a.symbol).toUpperCase(), quantity: Number(a.quantity) }))
-      .filter((a: any) => Number.isFinite(a.quantity) && a.quantity > 0)
-      .sort((a: any, b: any) => a.symbol.localeCompare(b.symbol));
-  }
-
-  const balances = details?.balances;
-  if (balances && typeof balances === "object") {
-    return Object.entries(balances)
-      .filter(([, qty]) => typeof qty === "number" && Number.isFinite(qty) && qty > 0)
-      .map(([symbol, qty]) => ({ symbol: String(symbol).toUpperCase(), quantity: Number(qty) }))
-      .sort((a, b) => a.symbol.localeCompare(b.symbol));
-  }
-
-  return [];
-}
-
 export async function getPrimaryWalletId(): Promise<string | null> {
   const session = await loadSession();
   const userId = session?.userId;
   if (!userId) throw new Error("Session absente : impossible de charger les wallets.");
 
-  const list = await apiGet<WalletListResponse>(`/wallets?userId=${encodeURIComponent(userId)}`);
-  return pickWalletId(list);
+  const data = await apiGet<WalletListResponse>(`/wallets?userId=${encodeURIComponent(userId)}`);
+  return pickWalletId(data);
 }
 
 export async function getPortfolio(): Promise<PortfolioState> {
   const walletId = await getPrimaryWalletId();
   if (!walletId) return { assets: [], updatedAtMs: Date.now() };
 
-  const details = await apiGet<any>(`/wallets/${encodeURIComponent(walletId)}`);
-  const assets = extractAssets(details);
+  const data = await apiGet<any>(`/wallets/${encodeURIComponent(walletId)}`);
 
-  return { assets, updatedAtMs: Date.now() };
+  // wallet-service: { wallet: {...} }
+  const w = data?.wallet ?? data;
+
+  const symbol = String(w?.asset_symbol ?? w?.assetSymbol ?? "BTC").toUpperCase();
+  const qtyRaw = w?.quantity ?? "0";
+  const qty = Number(qtyRaw);
+
+  if (!Number.isFinite(qty) || qty <= 0) {
+    return { assets: [], updatedAtMs: Date.now() };
+  }
+
+  return {
+    assets: [{ symbol, quantity: qty }],
+    updatedAtMs: Date.now(),
+  };
 }
 
 export type WalletEvent = {
-  id?: string;
-  type?: string;       // BUY/SELL
-  symbol?: string;     // BTC
-  quantity?: number;
-  price?: number;
-  timestamp?: number;
+  event_id?: string;
+  event_type?: string;       // BUY/SELL/...
+  quantity_base?: string;    // "0.0100000000"
+  price_quote?: string;      // "42150.2300000000"
+  quote_symbol?: string;     // "EUR" ou "USDT"
+  executed_at_ms?: number;
 };
 
 export async function getWalletEvents(limit = 50): Promise<WalletEvent[]> {
@@ -92,8 +86,18 @@ export async function getWalletEvents(limit = 50): Promise<WalletEvent[]> {
     `/wallets/${encodeURIComponent(walletId)}/events?limit=${encodeURIComponent(String(limit))}`
   );
 
-  if (Array.isArray(data)) return data as WalletEvent[];
   if (Array.isArray(data?.events)) return data.events as WalletEvent[];
   if (Array.isArray(data?.items)) return data.items as WalletEvent[];
+  if (Array.isArray(data)) return data as WalletEvent[];
   return [];
+}
+
+export async function addWalletEvent(params: {
+  walletId: string;
+  event_type: "BUY" | "SELL";
+  quantity_base: string;   // ex: "0.0100000000"
+  price_quote?: string;    // requis pour BUY
+  quote_symbol?: "EUR" | "USDT"; // requis pour BUY
+}): Promise<void> {
+  await apiPost(`/wallets/${encodeURIComponent(params.walletId)}/events`, params);
 }
\ No newline at end of file
index 78af51b500e0b56c354aab67dd877cca8e7ecbc3..13db616c195cceeae14a967187aa6f0b79062a76 100644 (file)
@@ -1,16 +1,9 @@
-export type TradeDecision =
-    | "BUY"
-    | "SELL"
-    | "HOLD"
-    | "STOP_LOSS";
+export type TradeDecision = "BUY" | "SELL" | "HOLD" | "STOP_LOSS";
 
-export type AlertLevel =
-    | "CRITICAL"
-    | "WARNING"
-    | "INFO";
+export type AlertLevel = "CRITICAL" | "WARNING" | "INFO";
 
 export interface DashboardSummary {
-  pair: string;
+  pair: string;          // ex: BTC/EUR ou BTC/USDT
   price: number;
   strategy: string;
   decision: TradeDecision;
@@ -18,5 +11,4 @@ export interface DashboardSummary {
   reason: string;
   alertLevel: AlertLevel;
   timestamp: number;
-}
-
+}
\ No newline at end of file
index 1fe6b39e951ce4d66c910b8048a25dac6452cd10..00cc2b9d8ab2d9f6bc8a841f09fe7b14d50cc5ba 100644 (file)
@@ -2,9 +2,6 @@ import AsyncStorage from "@react-native-async-storage/async-storage";
 import type { UserSettings } from "../models/UserSettings";
 import { loadSession } from "./sessionStorage";
 
-/**
- * KEY devient "userSettings:<userId>"
- */
 function keyFor(userId: string) {
   return `userSettings:${userId}`;
 }
@@ -18,16 +15,30 @@ const DEFAULT_SETTINGS: UserSettings = {
   selectedStrategyKey: "RSI_SIMPLE",
 };
 
+function normalizeCurrency(input: any): UserSettings["currency"] {
+  const v = String(input ?? "").toUpperCase();
+
+  // Migration anciens formats
+  if (v === "USD") return "USDT";
+  if (v === "USDT") return "USDT";
+  return "EUR";
+}
+
 export async function loadSettings(): Promise<UserSettings> {
   const session = await loadSession();
-  if (!session) return DEFAULT_SETTINGS; // sécurité (normalement, App bloque sans session)
+  if (!session) return DEFAULT_SETTINGS;
 
   const raw = await AsyncStorage.getItem(keyFor(session.userId));
   if (!raw) return DEFAULT_SETTINGS;
 
   try {
     const parsed = JSON.parse(raw) as Partial<UserSettings>;
-    return { ...DEFAULT_SETTINGS, ...parsed };
+
+    return {
+      ...DEFAULT_SETTINGS,
+      ...parsed,
+      currency: normalizeCurrency((parsed as any).currency),
+    };
   } catch {
     return DEFAULT_SETTINGS;
   }
@@ -37,5 +48,11 @@ export async function saveSettings(settings: UserSettings): Promise<void> {
   const session = await loadSession();
   if (!session) return;
 
-  await AsyncStorage.setItem(keyFor(session.userId), JSON.stringify(settings));
+  // sécurité : on force la currency à un enum valide
+  const safe: UserSettings = {
+    ...settings,
+    currency: normalizeCurrency((settings as any).currency),
+  };
+
+  await AsyncStorage.setItem(keyFor(session.userId), JSON.stringify(safe));
 }
\ No newline at end of file