]> git.digitality.be Git - pdw25-26/commitdiff
Mobile : Portfolio created
authorThibaud Moustier <thibaudmoustier0@gmail.com>
Wed, 25 Feb 2026 17:00:38 +0000 (18:00 +0100)
committerThibaud Moustier <thibaudmoustier0@gmail.com>
Wed, 25 Feb 2026 17:00:38 +0000 (18:00 +0100)
Wallette/mobile/src/mocks/prices.mock.ts [new file with mode: 0644]
Wallette/mobile/src/models/Portfolio.ts [new file with mode: 0644]
Wallette/mobile/src/screens/DashboardScreen.tsx
Wallette/mobile/src/screens/WalletScreen.tsx
Wallette/mobile/src/utils/portfolioStorage.ts [new file with mode: 0644]

diff --git a/Wallette/mobile/src/mocks/prices.mock.ts b/Wallette/mobile/src/mocks/prices.mock.ts
new file mode 100644 (file)
index 0000000..d93941c
--- /dev/null
@@ -0,0 +1,16 @@
+/**
+ * Mock prices (Step 3)
+ * --------------------
+ * En attendant l'API REST /api/price/current.
+ */
+export const mockPrices: Record<string, number> = {
+  BTC: 42150.23,
+  ETH: 2300.55,
+  SOL: 105.12,
+  ADA: 0.48,
+};
+
+export function getMockPrice(symbol: string): number | null {
+  const key = symbol.toUpperCase().trim();
+  return typeof mockPrices[key] === "number" ? mockPrices[key] : null;
+}
\ No newline at end of file
diff --git a/Wallette/mobile/src/models/Portfolio.ts b/Wallette/mobile/src/models/Portfolio.ts
new file mode 100644 (file)
index 0000000..52f0f34
--- /dev/null
@@ -0,0 +1,15 @@
+/**
+ * Portfolio (Step 3)
+ * ------------------
+ * Mono-user / multi-cryptos.
+ * Une ligne = un asset (BTC, ETH, etc.) + quantité.
+ */
+export interface PortfolioAsset {
+  symbol: string;   // ex: "BTC"
+  quantity: number; // ex: 0.25
+}
+
+export interface PortfolioState {
+  assets: PortfolioAsset[];
+  updatedAtMs: number;
+}
\ No newline at end of file
index 8bd123a3a4b64874a67011bbff7e0e3a5ad77192..f1655133bb0b6228a9df07f42a4b48a663fcfd3d 100644 (file)
@@ -24,12 +24,16 @@ import type { Alert } from "../types/Alert";
 import { alertStore } from "../services/alertStore";
 import { showAlertNotification } from "../services/notificationService";
 
-import { loadWallet } from "../utils/walletStorage";
-import type { WalletState } from "../models/Wallet";
+// ✅ Step 3 portfolio
+import { loadPortfolio } from "../utils/portfolioStorage";
+import type { PortfolioState, PortfolioAsset } from "../models/Portfolio";
+import { getMockPrice } from "../mocks/prices.mock";
 
 /**
- * DashboardScreen (WF-01) — Responsive + No-scroll goal
- * ----------------------------------------------------
+ * DashboardScreen (WF-01) — Step 3
+ * --------------------------------
+ * - Portefeuille multi-cryptos (portfolioStep3, AsyncStorage)
+ * - Valeur globale = somme(quantity * prix mock)
  * - Cartes cliquables (chevron subtil) :
  *   * Portefeuille -> Wallet
  *   * Urgence -> Alertes
@@ -43,7 +47,9 @@ export default function DashboardScreen() {
 
   const [summary, setSummary] = useState<DashboardSummary | null>(null);
   const [settings, setSettings] = useState<UserSettings | null>(null);
-  const [wallet, setWallet] = useState<WalletState | null>(null);
+
+  // ✅ Step 3 portfolio
+  const [portfolio, setPortfolio] = useState<PortfolioState | null>(null);
 
   const [loading, setLoading] = useState<boolean>(true);
   const [error, setError] = useState<string | null>(null);
@@ -58,6 +64,10 @@ 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;
@@ -67,17 +77,18 @@ export default function DashboardScreen() {
           setError(null);
           setLoading(true);
 
-          const [dashboardData, userSettings, walletData] = await Promise.all([
+          const [dashboardData, userSettings, portfolioData] = await Promise.all([
             fetchDashboardSummary(),
             loadSettings(),
-            loadWallet(),
+            loadPortfolio(),
           ]);
 
           if (!isActive) return;
 
           setSummary(dashboardData);
           setSettings(userSettings);
-          setWallet(walletData);
+          setPortfolio(portfolioData);
+
           setLastRefreshMs(Date.now());
         } catch {
           if (isActive) setError("Impossible de charger le dashboard.");
@@ -94,6 +105,9 @@ export default function DashboardScreen() {
     }, [])
   );
 
+  /**
+   * Refresh auto (si activé)
+   */
   useEffect(() => {
     if (!settings) return;
     if (settings.refreshMode !== "auto") return;
@@ -118,6 +132,9 @@ export default function DashboardScreen() {
     };
   }, [settings]);
 
+  /**
+   * Refresh manuel
+   */
   const handleManualRefresh = async () => {
     try {
       setRefreshing(true);
@@ -132,6 +149,10 @@ export default function DashboardScreen() {
     }
   };
 
+  /**
+   * Socket.IO (non bloquant)
+   * Step 1/2/3 : userId de test
+   */
   useEffect(() => {
     if (!settings) return;
 
@@ -172,6 +193,9 @@ export default function DashboardScreen() {
     };
   }, [settings]);
 
+  /**
+   * Urgence : 1 seule alerte, la plus importante.
+   */
   const urgentAlert: Alert | null = useMemo(() => {
     if (liveAlerts.length === 0) return null;
 
@@ -184,11 +208,44 @@ export default function DashboardScreen() {
     return liveAlerts[0];
   }, [liveAlerts]);
 
-  const walletTotalValue = useMemo(() => {
-    if (!wallet || !summary) return null;
-    return wallet.quantity * summary.price;
-  }, [wallet, summary]);
+  /**
+   * Valeur globale du portefeuille (Step 3)
+   * total = somme(quantity * prix(mock))
+   */
+  const portfolioTotalValue = useMemo(() => {
+    if (!portfolio) return 0;
+
+    return portfolio.assets.reduce((sum, a) => {
+      const price = getMockPrice(a.symbol);
+      if (price === null) return sum;
+      return sum + a.quantity * price;
+    }, 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]);
+
+  const remainingCount = useMemo(() => {
+    if (!portfolio) return 0;
+    return Math.max(0, portfolio.assets.length - topAssets.length);
+  }, [portfolio, topAssets]);
+
+  // Loading / erreurs bloquantes
   if (loading) {
     return (
       <View style={ui.centered}>
@@ -205,7 +262,7 @@ export default function DashboardScreen() {
     );
   }
 
-  if (!summary || !settings || !wallet) {
+  if (!summary || !settings || !portfolio) {
     return (
       <View style={ui.centered}>
         <Text>Initialisation…</Text>
@@ -214,7 +271,12 @@ export default function DashboardScreen() {
   }
 
   const Chevron = () => (
-    <Ionicons name="chevron-forward-outline" size={18} color="#0f172a" style={{ opacity: 0.35 }} />
+    <Ionicons
+      name="chevron-forward-outline"
+      size={18}
+      color="#0f172a"
+      style={{ opacity: 0.35 }}
+    />
   );
 
   return (
@@ -253,7 +315,7 @@ export default function DashboardScreen() {
           </TouchableOpacity>
         </View>
 
-        {/* 2) PORTEFEUILLE — cliquable => Wallet */}
+        {/* 2) PORTEFEUILLE — cliquable => Wallet (Step 3) */}
         <TouchableOpacity activeOpacity={0.85} onPress={() => navigation.navigate("Wallet" as never)}>
           <View style={[ui.card, compact && styles.cardCompact]}>
             <View style={styles.headerRow}>
@@ -262,19 +324,37 @@ export default function DashboardScreen() {
             </View>
 
             <View style={ui.rowBetween}>
-              <Text style={ui.value}>Quantité BTC :</Text>
-              <Text style={ui.valueBold}>{wallet.quantity.toFixed(6)} BTC</Text>
-            </View>
-
-            <View style={[ui.rowBetween, { marginTop: 6 }]}>
-              <Text style={ui.value}>Valeur Totale :</Text>
+              <Text style={ui.value}>Valeur globale :</Text>
               <Text style={ui.valueBold}>
-                {walletTotalValue !== null ? `${walletTotalValue.toFixed(2)} ${settings.currency}` : "—"}
+                {portfolioTotalValue.toFixed(2)} {settings.currency}
               </Text>
             </View>
 
-            <Text style={[ui.muted, { marginTop: 6 }]} numberOfLines={1}>
-              BTC @ {summary.price.toFixed(2)} {settings.currency}
+            {/* Résumé assets */}
+            {portfolio.assets.length === 0 ? (
+              <Text style={[ui.muted, { marginTop: 8 }]}>
+                Aucun asset (ajoute BTC/ETH/SOL dans Portefeuille)
+              </Text>
+            ) : (
+              <View style={{ marginTop: 8 }}>
+                {topAssets.map((a) => (
+                  <View key={a.symbol} style={[ui.rowBetween, { marginTop: 4 }]}>
+                    <Text style={ui.muted}>{a.symbol}</Text>
+                    <Text style={ui.valueBold}>{a.quantity.toFixed(6)}</Text>
+                  </View>
+                ))}
+
+                {remainingCount > 0 && (
+                  <Text style={[ui.muted, { marginTop: 6 }]} numberOfLines={1}>
+                    +{remainingCount} autre(s) asset(s)
+                  </Text>
+                )}
+              </View>
+            )}
+
+            {/* Ligne informative BTC @ prix (mock) */}
+            <Text style={[ui.muted, { marginTop: 8 }]} numberOfLines={1}>
+              BTC @ {summary.price.toFixed(2)} {settings.currency} (source mock)
             </Text>
           </View>
         </TouchableOpacity>
@@ -317,7 +397,11 @@ export default function DashboardScreen() {
               </View>
 
               <TouchableOpacity
-                style={[styles.refreshBtn, refreshing && styles.refreshBtnDisabled, compact && styles.refreshBtnCompact]}
+                style={[
+                  styles.refreshBtn,
+                  refreshing && styles.refreshBtnDisabled,
+                  compact && styles.refreshBtnCompact,
+                ]}
                 onPress={handleManualRefresh}
                 disabled={refreshing}
               >
@@ -404,7 +488,6 @@ const styles = StyleSheet.create({
     marginTop: 10,
   },
 
-  // ✅ header row + chevron icon
   headerRow: {
     flexDirection: "row",
     justifyContent: "space-between",
index 07683790020acd95d17a4ee75b76f767eeb04217..0368f014c47e9255afed9e6304c68acc0d030e28 100644 (file)
@@ -1,57 +1,55 @@
-import { View, Text, StyleSheet, TouchableOpacity, TextInput, Alert as RNAlert } from "react-native";
-import { useMemo, useState } from "react";
+import {
+  View,
+  Text,
+  StyleSheet,
+  TouchableOpacity,
+  TextInput,
+  Alert as RNAlert,
+  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 { useCallback } from "react";
 
 import { ui } from "../components/ui/uiStyles";
-import { loadWallet, saveWallet, clearWallet } from "../utils/walletStorage";
-import type { WalletState } from "../models/Wallet";
-import { fetchDashboardSummary } from "../services/dashboardService";
+import type { PortfolioAsset, PortfolioState } from "../models/Portfolio";
+import { loadPortfolio, savePortfolio, clearPortfolio } from "../utils/portfolioStorage";
+import { getMockPrice } from "../mocks/prices.mock";
 import { loadSettings } from "../utils/settingsStorage";
 import type { UserSettings } from "../models/UserSettings";
 
 /**
- * WalletScreen (WF-03 Step 1)
- * ---------------------------
- * Mono-utilisateur / mono-crypto : BTC uniquement.
- * - L'utilisateur encode la quantité de BTC qu'il possède
- * - On calcule la valeur estimée via le prix BTC du dashboard
- * - Stockage local (AsyncStorage) pour ne pas dépendre de l'API
+ * WalletScreen (Step 3)
+ * ---------------------
+ * Mono-user / multi-cryptos
+ * - liste d'assets (BTC, ETH, SOL...)
+ * - quantité par asset
+ * - valeur globale du portefeuille
+ *
+ * Aujourd'hui : prix mock
+ * Demain : prix via GET /api/price/current?pair=XXX/EUR
  */
 export default function WalletScreen() {
-  const [wallet, setWallet] = useState<WalletState | null>(null);
+  const [portfolio, setPortfolio] = useState<PortfolioState | null>(null);
   const [settings, setSettings] = useState<UserSettings | null>(null);
 
-  // Prix BTC actuel (mock aujourd'hui, API demain)
-  const [btcPrice, setBtcPrice] = useState<number | null>(null);
-
-  // input texte (évite les bugs de virgule/points)
+  // Ajout asset
+  const [symbolInput, setSymbolInput] = useState<string>("BTC");
   const [qtyInput, setQtyInput] = useState<string>("0");
 
   const [info, setInfo] = useState<string | null>(null);
 
-  // Recharge quand l’écran reprend le focus (retour depuis autre page)
   useFocusEffect(
     useCallback(() => {
       let active = true;
 
       async function init() {
         setInfo(null);
-
-        const [w, s, dash] = await Promise.all([
-          loadWallet(),
-          loadSettings(),
-          fetchDashboardSummary(),
-        ]);
-
+        const [p, s] = await Promise.all([loadPortfolio(), loadSettings()]);
         if (!active) return;
 
-        setWallet(w);
+        setPortfolio(p);
         setSettings(s);
-        setBtcPrice(dash.price);
-
-        setQtyInput(String(w.quantity));
       }
 
       init();
@@ -62,6 +60,11 @@ export default function WalletScreen() {
     }, [])
   );
 
+  const lastUpdatedLabel = useMemo(() => {
+    if (!portfolio) return "—";
+    return new Date(portfolio.updatedAtMs).toLocaleString();
+  }, [portfolio]);
+
   const parsedQty = useMemo(() => {
     const normalized = qtyInput.replace(",", ".").trim();
     const val = Number(normalized);
@@ -70,49 +73,101 @@ export default function WalletScreen() {
     return val;
   }, [qtyInput]);
 
+  const normalizedSymbol = useMemo(() => symbolInput.toUpperCase().trim(), [symbolInput]);
+
   const totalValue = useMemo(() => {
-    if (parsedQty === null || btcPrice === null) return null;
-    return parsedQty * btcPrice;
-  }, [parsedQty, btcPrice]);
+    if (!portfolio) return 0;
 
-  const lastUpdatedLabel = useMemo(() => {
-    if (!wallet) return "—";
-    return new Date(wallet.updatedAtMs).toLocaleString();
-  }, [wallet]);
+    return portfolio.assets.reduce((sum, a) => {
+      const price = getMockPrice(a.symbol);
+      if (price === null) return sum;
+      return sum + a.quantity * price;
+    }, 0);
+  }, [portfolio]);
 
-  const handleSave = async () => {
-    if (!wallet) return;
+  const handleAddOrUpdate = async () => {
+    if (!portfolio) return;
+
+    setInfo(null);
+
+    if (!normalizedSymbol || normalizedSymbol.length < 2) {
+      setInfo("Symbole invalide (ex: BTC).");
+      return;
+    }
 
     if (parsedQty === null) {
       setInfo("Quantité invalide. Exemple : 0.25");
       return;
     }
 
-    const updated: WalletState = {
-      ...wallet,
-      quantity: parsedQty,
+    const price = getMockPrice(normalizedSymbol);
+    if (price === null) {
+      setInfo("Prix inconnu (mock). Essayez: BTC, ETH, SOL, ADA.");
+      return;
+    }
+
+    const existingIndex = portfolio.assets.findIndex((a) => a.symbol === normalizedSymbol);
+
+    let updatedAssets: PortfolioAsset[];
+    if (existingIndex >= 0) {
+      // update
+      updatedAssets = portfolio.assets.map((a) =>
+        a.symbol === normalizedSymbol ? { ...a, quantity: parsedQty } : a
+      );
+    } else {
+      // add
+      updatedAssets = [...portfolio.assets, { symbol: normalizedSymbol, quantity: parsedQty }];
+    }
+
+    const updated: PortfolioState = {
+      assets: updatedAssets,
       updatedAtMs: Date.now(),
     };
 
-    await saveWallet(updated);
-    setWallet(updated);
-    setInfo("Portefeuille sauvegardé ✅");
+    await savePortfolio(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 local.",
+      [
+        { 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 savePortfolio(updated);
+            setPortfolio(updated);
+            setInfo(`${symbol} supprimé ✅`);
+          },
+        },
+      ]
+    );
   };
 
   const handleClear = () => {
     RNAlert.alert(
       "Réinitialiser le portefeuille ?",
-      "Cela remet la quantité BTC à 0 (stockage local).",
+      "Cela supprime tous les assets du stockage local.",
       [
         { text: "Annuler", style: "cancel" },
         {
           text: "Réinitialiser",
           style: "destructive",
           onPress: async () => {
-            await clearWallet();
-            const fresh = await loadWallet();
-            setWallet(fresh);
-            setQtyInput("0");
+            await clearPortfolio();
+            const fresh = await loadPortfolio();
+            setPortfolio(fresh);
             setInfo("Portefeuille réinitialisé ✅");
           },
         },
@@ -120,7 +175,7 @@ export default function WalletScreen() {
     );
   };
 
-  if (!wallet || !settings) {
+  if (!portfolio || !settings) {
     return (
       <View style={ui.centered}>
         <Text>Chargement du portefeuille…</Text>
@@ -130,62 +185,113 @@ export default function WalletScreen() {
 
   return (
     <SafeAreaView style={styles.safeArea}>
-      <View style={ui.container}>
-        <Text style={styles.screenTitle}>Portefeuille</Text>
-
-        {/* Carte BTC */}
-        <View style={ui.card}>
-          <Text style={ui.title}>BTC</Text>
-
-          <Text style={ui.muted}>Quantité détenue</Text>
-
-          <TextInput
-            value={qtyInput}
-            onChangeText={setQtyInput}
-            keyboardType="decimal-pad"
-            placeholder="ex: 0.25"
-            style={styles.input}
-          />
-
-          <Text style={[ui.muted, { marginTop: 10 }]}>
-            Prix BTC actuel :{" "}
-            <Text style={styles.boldInline}>
-              {btcPrice !== null ? `${btcPrice.toFixed(2)} ${settings.currency}` : "—"}
+      <FlatList
+        contentContainerStyle={ui.container}
+        data={portfolio.assets}
+        keyExtractor={(it) => it.symbol}
+        ListHeaderComponent={
+          <View>
+            <Text style={styles.screenTitle}>Portefeuille</Text>
+
+            {/* Résumé global */}
+            <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.valueBold}>
+                  {totalValue.toFixed(2)} {settings.currency}
+                </Text>
+              </View>
+
+              <Text style={[ui.muted, { marginTop: 6 }]}>
+                Dernière mise à jour :{" "}
+                <Text style={styles.boldInline}>{lastUpdatedLabel}</Text>
+              </Text>
+
+              <TouchableOpacity style={styles.secondaryButton} onPress={handleClear}>
+                <Text style={styles.secondaryButtonText}>Réinitialiser</Text>
+              </TouchableOpacity>
+            </View>
+
+            {/* Ajouter / modifier */}
+            <View style={ui.card}>
+              <Text style={ui.title}>Ajouter / Modifier un asset</Text>
+
+              <Text style={ui.muted}>Symbole (ex: BTC, ETH, SOL, ADA)</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.muted, { marginTop: 10 }]}>
+                Prix utilisés = mock pour Step 3 (API à brancher plus tard).
+              </Text>
+            </View>
+
+            <Text style={[ui.muted, { marginBottom: 10 }]}>
+              Liste des assets :
             </Text>
-          </Text>
-
-          <Text style={[ui.muted, { marginTop: 6 }]}>
-            Valeur estimée :{" "}
-            <Text style={styles.boldInline}>
-              {totalValue !== null ? `${totalValue.toFixed(2)} ${settings.currency}` : "—"}
-            </Text>
-          </Text>
-
-          {/* ✅ Dernière mise à jour */}
-          <Text style={[ui.muted, { marginTop: 6 }]}>
-            Dernière mise à jour :{" "}
-            <Text style={styles.boldInline}>{lastUpdatedLabel}</Text>
-          </Text>
-
-          <TouchableOpacity style={[ui.button, styles.fullButton]} onPress={handleSave}>
-            <Text style={ui.buttonText}>Enregistrer</Text>
-          </TouchableOpacity>
-
-          <TouchableOpacity style={styles.secondaryButton} onPress={handleClear}>
-            <Text style={styles.secondaryButtonText}>Réinitialiser</Text>
-          </TouchableOpacity>
-
-          {!!info && <Text style={[ui.muted, { marginTop: 10 }]}>{info}</Text>}
-        </View>
-
-        {/* Carte info Step */}
-        <View style={ui.card}>
-          <Text style={ui.title}>Step 1</Text>
-          <Text style={ui.muted}>
-            Mono-utilisateur / mono-crypto (BTC). Step 3 : portefeuille multi-cryptos + valeur globale.
-          </Text>
-        </View>
-      </View>
+          </View>
+        }
+        ListEmptyComponent={
+          <View style={ui.card}>
+            <Text style={ui.title}>Aucun asset</Text>
+            <Text style={ui.muted}>Ajoutez BTC/ETH/SOL… pour commencer.</Text>
+          </View>
+        }
+        renderItem={({ item }) => {
+          const price = getMockPrice(item.symbol);
+          const value = price !== null ? item.quantity * price : null;
+
+          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>
+              </View>
+
+              <View style={[ui.rowBetween, { marginTop: 8 }]}>
+                <Text style={ui.value}>Quantité</Text>
+                <Text style={ui.valueBold}>{item.quantity.toFixed(6)}</Text>
+              </View>
+
+              <View style={[ui.rowBetween, { marginTop: 6 }]}>
+                <Text style={ui.value}>Prix (mock)</Text>
+                <Text style={ui.valueBold}>
+                  {price !== null ? `${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}` : "—"}
+                </Text>
+              </View>
+            </View>
+          );
+        }}
+      />
     </SafeAreaView>
   );
 }
@@ -237,4 +343,9 @@ const styles = StyleSheet.create({
     fontWeight: "900",
     color: "#dc2626",
   },
+
+  deleteText: {
+    fontWeight: "900",
+    color: "#dc2626",
+  },
 });
\ No newline at end of file
diff --git a/Wallette/mobile/src/utils/portfolioStorage.ts b/Wallette/mobile/src/utils/portfolioStorage.ts
new file mode 100644 (file)
index 0000000..887ed8c
--- /dev/null
@@ -0,0 +1,40 @@
+import AsyncStorage from "@react-native-async-storage/async-storage";
+import type { PortfolioState } from "../models/Portfolio";
+
+const KEY = "portfolioStep3";
+
+/**
+ * Portfolio par défaut : vide
+ */
+const DEFAULT_PORTFOLIO: PortfolioState = {
+  assets: [],
+  updatedAtMs: Date.now(),
+};
+
+export async function loadPortfolio(): Promise<PortfolioState> {
+  const raw = await AsyncStorage.getItem(KEY);
+  if (!raw) return DEFAULT_PORTFOLIO;
+
+  try {
+    const parsed = JSON.parse(raw) as Partial<PortfolioState>;
+    return {
+      ...DEFAULT_PORTFOLIO,
+      ...parsed,
+      assets: Array.isArray(parsed.assets) ? parsed.assets : [],
+    };
+  } catch {
+    return DEFAULT_PORTFOLIO;
+  }
+}
+
+export async function savePortfolio(portfolio: PortfolioState): Promise<void> {
+  const safe: PortfolioState = {
+    assets: Array.isArray(portfolio.assets) ? portfolio.assets : [],
+    updatedAtMs: Date.now(),
+  };
+  await AsyncStorage.setItem(KEY, JSON.stringify(safe));
+}
+
+export async function clearPortfolio(): Promise<void> {
+  await AsyncStorage.removeItem(KEY);
+}
\ No newline at end of file