]> git.digitality.be Git - pdw25-26/commitdiff
Mobile : modification portfolio et setting
authorThibaud Moustier <thibaudmoustier0@gmail.com>
Wed, 25 Feb 2026 19:26:43 +0000 (20:26 +0100)
committerThibaud Moustier <thibaudmoustier0@gmail.com>
Wed, 25 Feb 2026 19:26:43 +0000 (20:26 +0100)
Wallette/mobile/App.tsx
Wallette/mobile/src/screens/AuthScreen.tsx [new file with mode: 0644]
Wallette/mobile/src/screens/DashboardScreen.tsx
Wallette/mobile/src/screens/SettingsScreen.tsx
Wallette/mobile/src/utils/portfolioStorage.ts
Wallette/mobile/src/utils/sessionStorage.ts [new file with mode: 0644]
Wallette/mobile/src/utils/settingsStorage.ts

index 8f9403ff4f5030d083c2464201f265c112703819..0e33c56024e3cf5cde106a21796410ad9e74aad6 100644 (file)
@@ -1,7 +1,8 @@
 import { NavigationContainer } from "@react-navigation/native";
 import { createNativeStackNavigator } from "@react-navigation/native-stack";
-import { TouchableOpacity } from "react-native";
+import { TouchableOpacity, View, Text } from "react-native";
 import { Ionicons } from "@expo/vector-icons";
+import { useEffect, useState } from "react";
 
 import DashboardScreen from "./src/screens/DashboardScreen";
 import SettingsScreen from "./src/screens/SettingsScreen";
@@ -9,8 +10,10 @@ import HistoryScreen from "./src/screens/HistoryScreen";
 import AlertsScreen from "./src/screens/AlertsScreen";
 import StrategyScreen from "./src/screens/StrategyScreen";
 import WalletScreen from "./src/screens/WalletScreen";
+import AuthScreen from "./src/screens/AuthScreen";
+
+import { loadSession, clearSession } from "./src/utils/sessionStorage";
 
-// Types des routes (pour éviter les erreurs de navigation)
 export type RootStackParamList = {
   Dashboard: undefined;
   Settings: undefined;
@@ -23,6 +26,46 @@ export type RootStackParamList = {
 const Stack = createNativeStackNavigator<RootStackParamList>();
 
 export default function App() {
+  const [ready, setReady] = useState(false);
+  const [isAuthed, setIsAuthed] = useState(false);
+
+  useEffect(() => {
+    let active = true;
+
+    async function init() {
+      const s = await loadSession();
+      if (!active) return;
+      setIsAuthed(!!s);
+      setReady(true);
+    }
+
+    init();
+
+    return () => {
+      active = false;
+    };
+  }, []);
+
+  if (!ready) {
+    return (
+      <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
+        <Text>Initialisation…</Text>
+      </View>
+    );
+  }
+
+  // Pas connecté -> écran Auth
+  if (!isAuthed) {
+    return (
+      <AuthScreen
+        onAuthenticated={() => {
+          setIsAuthed(true);
+        }}
+      />
+    );
+  }
+
+  // Connecté -> stack normal
   return (
     <NavigationContainer>
       <Stack.Navigator id="MainStack" initialRouteName="Dashboard">
@@ -32,42 +75,34 @@ export default function App() {
           options={({ navigation }) => ({
             title: "Dashboard",
             headerRight: () => (
-              <TouchableOpacity onPress={() => navigation.navigate("Settings")}>
-                <Ionicons name="settings-outline" size={22} color="#0f172a" />
-              </TouchableOpacity>
+              <View style={{ flexDirection: "row", gap: 12 }}>
+                {/* ⚙️ Settings */}
+                <TouchableOpacity onPress={() => navigation.navigate("Settings")}>
+                  <Ionicons name="settings-outline" size={22} color="#0f172a" />
+                </TouchableOpacity>
+
+                {/* ⎋ Logout */}
+                <TouchableOpacity
+                  onPress={async () => {
+                    await clearSession();
+                    setIsAuthed(false);
+                  }}
+                >
+                  <Ionicons name="log-out-outline" size={22} color="#0f172a" />
+                </TouchableOpacity>
+              </View>
             ),
           })}
         />
 
-        <Stack.Screen
-          name="Wallet"
-          component={WalletScreen}
-          options={{ title: "Portefeuille" }}
-        />
-
-        <Stack.Screen
-          name="Strategy"
-          component={StrategyScreen}
-          options={{ title: "Stratégie" }}
-        />
-
-        <Stack.Screen
-          name="Alerts"
-          component={AlertsScreen}
-          options={{ title: "Alertes" }}
-        />
-
-        <Stack.Screen
-          name="History"
-          component={HistoryScreen}
-          options={{ title: "Historique" }}
-        />
-
-        <Stack.Screen
-          name="Settings"
-          component={SettingsScreen}
-          options={{ title: "Paramètres" }}
-        />
+        <Stack.Screen name="Wallet" component={WalletScreen} options={{ title: "Portefeuille" }} />
+        <Stack.Screen name="Strategy" component={StrategyScreen} options={{ title: "Stratégie" }} />
+        <Stack.Screen name="Alerts" component={AlertsScreen} options={{ title: "Alertes" }} />
+        <Stack.Screen name="History" component={HistoryScreen} options={{ title: "Historique" }} />
+        <Stack.Screen name="Settings" options={{ title: "Paramètres" }}
+>
+  {() => <SettingsScreen onLogout={() => setIsAuthed(false)} />}
+</Stack.Screen>
       </Stack.Navigator>
     </NavigationContainer>
   );
diff --git a/Wallette/mobile/src/screens/AuthScreen.tsx b/Wallette/mobile/src/screens/AuthScreen.tsx
new file mode 100644 (file)
index 0000000..8e15dac
--- /dev/null
@@ -0,0 +1,112 @@
+import { View, Text, StyleSheet, TextInput, TouchableOpacity } from "react-native";
+import { useMemo, useState } from "react";
+import { SafeAreaView } from "react-native-safe-area-context";
+
+import { ui } from "../components/ui/uiStyles";
+import { saveSession } from "../utils/sessionStorage";
+
+/**
+ * AuthScreen (Step 4 - sans API)
+ * ------------------------------
+ * Simule un login local :
+ * - Email -> userId stable
+ * - Session stockée en AsyncStorage
+ *
+ * Plus tard : on remplace par une auth REST réelle.
+ */
+export default function AuthScreen({ onAuthenticated }: { onAuthenticated: () => void }) {
+  const [email, setEmail] = useState<string>("demo@example.com");
+  const [error, setError] = useState<string | null>(null);
+
+  const normalizedEmail = useMemo(() => email.trim().toLowerCase(), [email]);
+
+  const isValidEmail = useMemo(() => {
+    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(normalizedEmail);
+  }, [normalizedEmail]);
+
+  const makeUserId = (mail: string) => `user_${mail.replace(/[^a-z0-9]/g, "_")}`;
+
+  const handleLogin = async () => {
+    setError(null);
+
+    if (!isValidEmail) {
+      setError("Email invalide.");
+      return;
+    }
+
+    const userId = makeUserId(normalizedEmail);
+
+    await saveSession({
+      userId,
+      email: normalizedEmail,
+      createdAtMs: Date.now(),
+    });
+
+    onAuthenticated();
+  };
+
+  return (
+    <SafeAreaView style={styles.safeArea}>
+      <View style={ui.container}>
+        <Text style={styles.title}>Connexion</Text>
+
+        <View style={ui.card}>
+          <Text style={ui.title}>Email</Text>
+
+          <TextInput
+            value={email}
+            onChangeText={setEmail}
+            autoCapitalize="none"
+            keyboardType="email-address"
+            placeholder="ex: demo@example.com"
+            style={styles.input}
+          />
+
+          {!!error && <Text style={styles.errorText}>{error}</Text>}
+
+          <TouchableOpacity style={[ui.button, styles.fullButton]} onPress={handleLogin}>
+            <Text style={ui.buttonText}>Se connecter</Text>
+          </TouchableOpacity>
+
+          <Text style={[ui.muted, { marginTop: 10 }]}>
+            Step 4 (sans API) : session locale. Plus tard, auth réelle via serveur.
+          </Text>
+        </View>
+      </View>
+    </SafeAreaView>
+  );
+}
+
+const styles = StyleSheet.create({
+  safeArea: {
+    flex: 1,
+    backgroundColor: ui.screen.backgroundColor,
+  },
+  title: {
+    fontSize: 22,
+    fontWeight: "900",
+    marginBottom: 12,
+    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,
+  },
+  errorText: {
+    marginTop: 10,
+    color: "#dc2626",
+    fontWeight: "900",
+  },
+});
\ No newline at end of file
index f1655133bb0b6228a9df07f42a4b48a663fcfd3d..d1e60f44ca681eaff5a1e11d383bb27e07d9e454 100644 (file)
@@ -13,6 +13,7 @@ import { Ionicons } from "@expo/vector-icons";
 
 import type { DashboardSummary } from "../types/DashboardSummary";
 import { fetchDashboardSummary } from "../services/dashboardService";
+
 import { loadSettings } from "../utils/settingsStorage";
 import type { UserSettings } from "../models/UserSettings";
 
@@ -24,22 +25,18 @@ import type { Alert } from "../types/Alert";
 import { alertStore } from "../services/alertStore";
 import { showAlertNotification } from "../services/notificationService";
 
-// ✅ Step 3 portfolio
 import { loadPortfolio } from "../utils/portfolioStorage";
 import type { PortfolioState, PortfolioAsset } from "../models/Portfolio";
 import { getMockPrice } from "../mocks/prices.mock";
 
+import { loadSession } from "../utils/sessionStorage";
+
 /**
- * DashboardScreen (WF-01) — Step 3
- * --------------------------------
- * - Portefeuille multi-cryptos (portfolioStep3, AsyncStorage)
- * - Valeur globale = somme(quantity * prix mock)
- * - Cartes cliquables (chevron subtil) :
- *   * Portefeuille -> Wallet
- *   * Urgence -> Alertes
- *   * Prix BTC -> Historique
- * - Conseiller : bouton -> Stratégie
- * - Socket.IO non bloquant + notifications locales
+ * DashboardScreen — Step 4
+ * ------------------------
+ * - Multi-users : userId vient de la session (AsyncStorage)
+ * - Settings/Portfolio sont déjà "scopés" par userId (via storages)
+ * - Socket.IO auth utilise userId de session
  */
 export default function DashboardScreen() {
   const { height } = useWindowDimensions();
@@ -47,8 +44,6 @@ export default function DashboardScreen() {
 
   const [summary, setSummary] = useState<DashboardSummary | null>(null);
   const [settings, setSettings] = useState<UserSettings | null>(null);
-
-  // ✅ Step 3 portfolio
   const [portfolio, setPortfolio] = useState<PortfolioState | null>(null);
 
   const [loading, setLoading] = useState<boolean>(true);
@@ -64,10 +59,6 @@ export default function DashboardScreen() {
 
   const navigation = useNavigation();
 
-  /**
-   * Load initial + reload au focus (retour depuis Settings/Strategy/Wallet)
-   * On recharge aussi le portfolio local (Step 3).
-   */
   useFocusEffect(
     useCallback(() => {
       let isActive = true;
@@ -88,7 +79,6 @@ export default function DashboardScreen() {
           setSummary(dashboardData);
           setSettings(userSettings);
           setPortfolio(portfolioData);
-
           setLastRefreshMs(Date.now());
         } catch {
           if (isActive) setError("Impossible de charger le dashboard.");
@@ -105,9 +95,6 @@ export default function DashboardScreen() {
     }, [])
   );
 
-  /**
-   * Refresh auto (si activé)
-   */
   useEffect(() => {
     if (!settings) return;
     if (settings.refreshMode !== "auto") return;
@@ -132,9 +119,6 @@ export default function DashboardScreen() {
     };
   }, [settings]);
 
-  /**
-   * Refresh manuel
-   */
   const handleManualRefresh = async () => {
     try {
       setRefreshing(true);
@@ -151,51 +135,65 @@ export default function DashboardScreen() {
 
   /**
    * Socket.IO (non bloquant)
-   * Step 1/2/3 : userId de test
+   * - userId = session.userId
    */
   useEffect(() => {
-    if (!settings) return;
+    let unsub: null | (() => void) = null;
+    let active = true;
 
-    setSocketError(null);
+    async function initSocket() {
+      if (!settings) return;
 
-    const userId = "test-user";
+      setSocketError(null);
 
-    try {
-      socketService.connect(SERVER_URL, userId);
-      setSocketConnected(true);
-    } catch {
-      setSocketConnected(false);
-      setSocketError("Socket : paramètres invalides (URL ou userId).");
-      return;
-    }
+      const session = await loadSession();
+      const userId = session?.userId;
 
-    const unsubscribeAlert = socketService.onAlert((alert) => {
-      alertStore.add(alert);
-      setLiveAlerts((prev) => [alert, ...prev].slice(0, 100));
-
-      if (settings.notificationsEnabled) {
-        void (async () => {
-          try {
-            await showAlertNotification(alert);
-          } catch (e) {
-            console.log("⚠️ Notification error:", e);
-          }
-        })();
+      if (!userId) {
+        setSocketConnected(false);
+        setSocketError("Socket désactivé : session absente.");
+        return;
       }
-    });
 
-    socketService.ping();
+      try {
+        socketService.connect(SERVER_URL, userId);
+        if (!active) return;
+        setSocketConnected(true);
+      } catch {
+        if (!active) return;
+        setSocketConnected(false);
+        setSocketError("Socket : paramètres invalides (URL ou userId).");
+        return;
+      }
+
+      unsub = socketService.onAlert((alert) => {
+        alertStore.add(alert);
+        setLiveAlerts((prev) => [alert, ...prev].slice(0, 100));
+
+        if (settings.notificationsEnabled) {
+          void (async () => {
+            try {
+              await showAlertNotification(alert);
+            } catch (e) {
+              console.log("⚠️ Notification error:", e);
+            }
+          })();
+        }
+      });
+
+      socketService.ping();
+    }
+
+    void initSocket();
 
     return () => {
-      unsubscribeAlert();
+      active = false;
+      if (unsub) unsub();
       socketService.disconnect();
       setSocketConnected(false);
     };
   }, [settings]);
 
-  /**
-   * Urgence : 1 seule alerte, la plus importante.
-   */
   const urgentAlert: Alert | null = useMemo(() => {
     if (liveAlerts.length === 0) return null;
 
@@ -208,10 +206,6 @@ export default function DashboardScreen() {
     return liveAlerts[0];
   }, [liveAlerts]);
 
-  /**
-   * Valeur globale du portefeuille (Step 3)
-   * total = somme(quantity * prix(mock))
-   */
   const portfolioTotalValue = useMemo(() => {
     if (!portfolio) return 0;
 
@@ -222,21 +216,15 @@ export default function DashboardScreen() {
     }, 0);
   }, [portfolio]);
 
-  /**
-   * Résumé : top assets à afficher sur le dashboard
-   * - on prend 3 assets max
-   */
   const topAssets: PortfolioAsset[] = useMemo(() => {
     if (!portfolio) return [];
 
-    // tri simple par valeur estimée (desc) si prix connu
     const withValue = portfolio.assets.map((a) => {
       const price = getMockPrice(a.symbol) ?? 0;
       return { ...a, _value: a.quantity * price };
     });
 
     withValue.sort((a, b) => (b as any)._value - (a as any)._value);
-
     return withValue.slice(0, 3).map(({ symbol, quantity }) => ({ symbol, quantity }));
   }, [portfolio]);
 
@@ -245,7 +233,6 @@ export default function DashboardScreen() {
     return Math.max(0, portfolio.assets.length - topAssets.length);
   }, [portfolio, topAssets]);
 
-  // Loading / erreurs bloquantes
   if (loading) {
     return (
       <View style={ui.centered}>
@@ -315,7 +302,7 @@ export default function DashboardScreen() {
           </TouchableOpacity>
         </View>
 
-        {/* 2) PORTEFEUILLE — cliquable => Wallet (Step 3) */}
+        {/* 2) PORTEFEUILLE */}
         <TouchableOpacity activeOpacity={0.85} onPress={() => navigation.navigate("Wallet" as never)}>
           <View style={[ui.card, compact && styles.cardCompact]}>
             <View style={styles.headerRow}>
@@ -330,7 +317,6 @@ export default function DashboardScreen() {
               </Text>
             </View>
 
-            {/* Résumé assets */}
             {portfolio.assets.length === 0 ? (
               <Text style={[ui.muted, { marginTop: 8 }]}>
                 Aucun asset (ajoute BTC/ETH/SOL dans Portefeuille)
@@ -352,14 +338,13 @@ export default function DashboardScreen() {
               </View>
             )}
 
-            {/* Ligne informative BTC @ prix (mock) */}
             <Text style={[ui.muted, { marginTop: 8 }]} numberOfLines={1}>
-              BTC @ {summary.price.toFixed(2)} {settings.currency} (source mock)
+              BTC @ {summary.price.toFixed(2)} {settings.currency} (mock)
             </Text>
           </View>
         </TouchableOpacity>
 
-        {/* 3) URGENCE — cliquable => Alertes */}
+        {/* 3) URGENCE */}
         <TouchableOpacity activeOpacity={0.85} onPress={() => navigation.navigate("Alerts" as never)}>
           <View style={[ui.card, compact && styles.cardCompact]}>
             <View style={styles.headerRow}>
@@ -387,7 +372,7 @@ export default function DashboardScreen() {
           </View>
         </TouchableOpacity>
 
-        {/* 4) PRIX BTC — cliquable => Historique */}
+        {/* 4) PRIX BTC */}
         <TouchableOpacity activeOpacity={0.85} onPress={() => navigation.navigate("History" as never)}>
           <View style={[ui.card, compact && styles.cardCompact]}>
             <View style={styles.priceHeaderRow}>
index 24c9e776caa7cf3c5794facd8a55ce05395fcf5d..4f753fb66eb8640676ba003bbe1cafad8b316cbc 100644 (file)
@@ -6,16 +6,26 @@ import { loadSettings, saveSettings } from "../utils/settingsStorage";
 import type { UserSettings } from "../models/UserSettings";
 
 import { requestNotificationPermission } from "../services/notificationService";
+import { clearSession, loadSession } from "../utils/sessionStorage";
+
 import { ui } from "../components/ui/uiStyles";
 
-export default function SettingsScreen() {
+/**
+ * SettingsScreen (Step 4)
+ * ----------------------
+ * Paramètres stockés par userId (via settingsStorage).
+ * Ajout : bouton Déconnexion.
+ */
+export default function SettingsScreen({ onLogout }: { onLogout?: () => void }) {
   const [settings, setSettings] = useState<UserSettings | null>(null);
   const [infoMessage, setInfoMessage] = useState<string | null>(null);
+  const [sessionLabel, setSessionLabel] = useState<string>("");
 
   useEffect(() => {
     async function init() {
-      const data = await loadSettings();
+      const [data, session] = await Promise.all([loadSettings(), loadSession()]);
       setSettings(data);
+      setSessionLabel(session?.email ?? "—");
     }
     init();
   }, []);
@@ -63,11 +73,26 @@ export default function SettingsScreen() {
     setInfoMessage("Paramètres sauvegardés ✅");
   };
 
+  const handleLogout = async () => {
+    await clearSession();
+    onLogout?.(); // App.tsx repasse sur Auth
+  };
+
   return (
     <SafeAreaView style={styles.safeArea}>
       <View style={ui.container}>
         <Text style={styles.screenTitle}>Paramètres</Text>
 
+        {/* Carte session */}
+        <View style={ui.card}>
+          <Text style={ui.title}>Compte</Text>
+          <Text style={ui.muted}>Connecté : <Text style={styles.boldInline}>{sessionLabel}</Text></Text>
+
+          <TouchableOpacity style={styles.secondaryButton} onPress={handleLogout}>
+            <Text style={styles.secondaryButtonText}>Se déconnecter</Text>
+          </TouchableOpacity>
+        </View>
+
         {/* Carte : Devise */}
         <View style={ui.card}>
           <Text style={ui.title}>Devise</Text>
@@ -91,9 +116,7 @@ export default function SettingsScreen() {
         {/* 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]} onPress={toggleNotifications}>
             <Text style={ui.buttonText}>
@@ -101,7 +124,7 @@ export default function SettingsScreen() {
             </Text>
           </TouchableOpacity>
 
-          {!!infoMessage && <Text style={styles.infoText}>{infoMessage}</Text>}
+          {!!infoMessage && <Text style={[ui.muted, { marginTop: 10 }]}>{infoMessage}</Text>}
         </View>
 
         {/* Bouton Save */}
@@ -124,25 +147,33 @@ const styles = StyleSheet.create({
     marginBottom: 12,
     color: "#0f172a",
   },
-  infoText: {
-    marginTop: 10,
-    opacity: 0.8,
+  boldInline: {
+    fontWeight: "900",
+    color: "#0f172a",
   },
 
-  /**
-   * fullButton
-   * ----------
-   * On neutralise les propriétés "grille" de ui.button (flexGrow/flexBasis),
-   * car elles sont utiles dans ActionsCard, mais pas dans Settings.
-   */
   fullButton: {
     flexGrow: 0,
     flexBasis: "auto",
     width: "100%",
     marginTop: 10,
   },
-
   saveButton: {
+    marginTop: 4,
     marginBottom: 10,
   },
+
+  secondaryButton: {
+    marginTop: 10,
+    paddingVertical: 10,
+    borderRadius: 10,
+    borderWidth: 1,
+    borderColor: "#e5e7eb",
+    alignItems: "center",
+    backgroundColor: "#fff",
+  },
+  secondaryButtonText: {
+    fontWeight: "900",
+    color: "#dc2626",
+  },
 });
\ No newline at end of file
index 887ed8c30585bf9b01c24bd2cecb7c0a5c6e9c33..8774d15440bb9af2e67d928e26ebdb6ff4d35a7e 100644 (file)
@@ -1,18 +1,24 @@
 import AsyncStorage from "@react-native-async-storage/async-storage";
 import type { PortfolioState } from "../models/Portfolio";
-
-const KEY = "portfolioStep3";
+import { loadSession } from "./sessionStorage";
 
 /**
- * Portfolio par défaut : vide
+ * KEY devient "portfolio:<userId>"
  */
+function keyFor(userId: string) {
+  return `portfolio:${userId}`;
+}
+
 const DEFAULT_PORTFOLIO: PortfolioState = {
   assets: [],
   updatedAtMs: Date.now(),
 };
 
 export async function loadPortfolio(): Promise<PortfolioState> {
-  const raw = await AsyncStorage.getItem(KEY);
+  const session = await loadSession();
+  if (!session) return DEFAULT_PORTFOLIO;
+
+  const raw = await AsyncStorage.getItem(keyFor(session.userId));
   if (!raw) return DEFAULT_PORTFOLIO;
 
   try {
@@ -28,13 +34,20 @@ export async function loadPortfolio(): Promise<PortfolioState> {
 }
 
 export async function savePortfolio(portfolio: PortfolioState): Promise<void> {
+  const session = await loadSession();
+  if (!session) return;
+
   const safe: PortfolioState = {
     assets: Array.isArray(portfolio.assets) ? portfolio.assets : [],
     updatedAtMs: Date.now(),
   };
-  await AsyncStorage.setItem(KEY, JSON.stringify(safe));
+
+  await AsyncStorage.setItem(keyFor(session.userId), JSON.stringify(safe));
 }
 
 export async function clearPortfolio(): Promise<void> {
-  await AsyncStorage.removeItem(KEY);
+  const session = await loadSession();
+  if (!session) return;
+
+  await AsyncStorage.removeItem(keyFor(session.userId));
 }
\ No newline at end of file
diff --git a/Wallette/mobile/src/utils/sessionStorage.ts b/Wallette/mobile/src/utils/sessionStorage.ts
new file mode 100644 (file)
index 0000000..0c756c8
--- /dev/null
@@ -0,0 +1,45 @@
+import AsyncStorage from "@react-native-async-storage/async-storage";
+
+export type Session = {
+  userId: string;
+  email: string;
+  createdAtMs: number;
+};
+
+const KEY = "session";
+
+/**
+ * Charge la session courante (si l'utilisateur est "connecté").
+ */
+export async function loadSession(): Promise<Session | null> {
+  const raw = await AsyncStorage.getItem(KEY);
+  if (!raw) return null;
+
+  try {
+    const parsed = JSON.parse(raw) as Partial<Session>;
+    if (!parsed.userId || !parsed.email) return null;
+
+    return {
+      userId: String(parsed.userId),
+      email: String(parsed.email),
+      createdAtMs: Number(parsed.createdAtMs ?? Date.now()),
+    };
+  } catch {
+    return null;
+  }
+}
+
+/**
+ * Sauvegarde une session.
+ * Ici c'est un login "local" (sans API).
+ */
+export async function saveSession(session: Session): Promise<void> {
+  await AsyncStorage.setItem(KEY, JSON.stringify(session));
+}
+
+/**
+ * Déconnexion.
+ */
+export async function clearSession(): Promise<void> {
+  await AsyncStorage.removeItem(KEY);
+}
\ No newline at end of file
index 07570d727a7eee2c342e08efb94dd3760f92a518..1fe6b39e951ce4d66c910b8048a25dac6452cd10 100644 (file)
@@ -1,26 +1,28 @@
 import AsyncStorage from "@react-native-async-storage/async-storage";
 import type { UserSettings } from "../models/UserSettings";
-
-const KEY = "userSettings";
+import { loadSession } from "./sessionStorage";
 
 /**
- * Settings par défaut
- * -------------------
- * On les fusionne avec ce qui est en storage.
+ * KEY devient "userSettings:<userId>"
  */
+function keyFor(userId: string) {
+  return `userSettings:${userId}`;
+}
+
 const DEFAULT_SETTINGS: UserSettings = {
   currency: "EUR",
   favoriteSymbol: "BTC",
   alertPreference: "critical",
   refreshMode: "manual",
   notificationsEnabled: false,
-
-  // ✅ stratégie par défaut
   selectedStrategyKey: "RSI_SIMPLE",
 };
 
 export async function loadSettings(): Promise<UserSettings> {
-  const raw = await AsyncStorage.getItem(KEY);
+  const session = await loadSession();
+  if (!session) return DEFAULT_SETTINGS; // sécurité (normalement, App bloque sans session)
+
+  const raw = await AsyncStorage.getItem(keyFor(session.userId));
   if (!raw) return DEFAULT_SETTINGS;
 
   try {
@@ -32,5 +34,8 @@ export async function loadSettings(): Promise<UserSettings> {
 }
 
 export async function saveSettings(settings: UserSettings): Promise<void> {
-  await AsyncStorage.setItem(KEY, JSON.stringify(settings));
+  const session = await loadSession();
+  if (!session) return;
+
+  await AsyncStorage.setItem(keyFor(session.userId), JSON.stringify(settings));
 }
\ No newline at end of file