From 0ae152935390ca59430f6be6084c83b410f133d3 Mon Sep 17 00:00:00 2001 From: Xor290 Date: Sat, 14 Mar 2026 17:44:00 +0100 Subject: [PATCH] chore: update --- mobile/src/api/api.ts | 25 ++ mobile/src/navigation/ClientNavigator.tsx | 15 + mobile/src/navigation/types.ts | 1 + mobile/src/screens/client/CheckoutScreen.tsx | 26 ++ .../src/screens/client/OrderHistoryScreen.tsx | 16 +- mobile/src/screens/client/ProfileScreen.tsx | 284 ++++++++++++++++++ 6 files changed, 360 insertions(+), 7 deletions(-) create mode 100644 mobile/src/screens/client/ProfileScreen.tsx diff --git a/mobile/src/api/api.ts b/mobile/src/api/api.ts index be1983c1..ca718f39 100644 --- a/mobile/src/api/api.ts +++ b/mobile/src/api/api.ts @@ -591,6 +591,31 @@ export const getMyPenalties = async (): Promise => { } }; +// ============================================ +// 👤 PROFIL CLIENT +// ============================================ + +export const getMyProfile = async (): Promise<{ success: boolean; client?: any; message?: string }> => { + try { + const { data } = await apiClient.get(`${V1}/profile`); + return { success: true, client: data.client }; + } catch { + return { success: false, message: "Erreur récupération profil" }; + } +}; + +export const updateMyProfile = async (fields: { nom?: string; prenom?: string; telephone?: string }): Promise<{ success: boolean; message?: string; client?: any }> => { + try { + const { data } = await apiClient.put(`${V1}/profile/update`, fields); + return { success: true, client: data.client, message: data.message }; + } catch (error: any) { + return { + success: false, + message: error.response?.data?.error || error.response?.data?.message || "Erreur mise à jour profil", + }; + } +}; + // ============================================ // ORDER DETAILS // ============================================ diff --git a/mobile/src/navigation/ClientNavigator.tsx b/mobile/src/navigation/ClientNavigator.tsx index cc96ddfd..9bc307c7 100644 --- a/mobile/src/navigation/ClientNavigator.tsx +++ b/mobile/src/navigation/ClientNavigator.tsx @@ -30,6 +30,7 @@ import ProductDetailScreen from "../screens/client/ProductDetailScreen"; import CheckoutScreen from "../screens/client/CheckoutScreen"; import OrderDetailsScreen from "../screens/client/OrderDetailsScreen"; import ParrainageScreen from "../screens/client/ParrainageScreen"; +import ProfileScreen from "../screens/client/ProfileScreen"; const Tab = createBottomTabNavigator(); const Stack = createNativeStackNavigator(); @@ -238,6 +239,20 @@ function ClientTabs() { ), }} /> + ( + + ), + }} + /> { if (res.success && res.balance > 0) setReferralBalance(res.balance); }); + AsyncStorage.multiGet([ + "profile_default_address", + "profile_default_phone", + ]).then((pairs) => { + const savedAddress = pairs[0][1]; + const savedPhone = pairs[1][1]; + if (savedAddress) setAddress(savedAddress); + if (savedPhone) setTelephone(savedPhone); + }); }, []); const handleCheckout = async () => { @@ -199,6 +209,12 @@ export default function CheckoutScreen() { marginTop: spacing.s, lineHeight: 16, }, + prefillHint: { + color: "#6ee7b7", + fontSize: fontSize.xs, + marginTop: 4, + opacity: 0.85, + }, invalidAddrContent: { gap: spacing.m }, invalidAddrIconContainer: { alignItems: "center" }, invalidAddrIconCircle: { @@ -378,6 +394,11 @@ export default function CheckoutScreen() { : undefined } /> + {!!telephone && ( + + Pre-rempli depuis votre profil + + )} @@ -411,6 +432,11 @@ export default function CheckoutScreen() { multiline numberOfLines={3} /> + {!!address && ( + + Pre-rempli depuis votre profil — modifiez si vous etes ailleurs + + )} {/* Section parrainage — visible uniquement si solde > 0 */} diff --git a/mobile/src/screens/client/OrderHistoryScreen.tsx b/mobile/src/screens/client/OrderHistoryScreen.tsx index 012d97bd..14edd486 100644 --- a/mobile/src/screens/client/OrderHistoryScreen.tsx +++ b/mobile/src/screens/client/OrderHistoryScreen.tsx @@ -257,17 +257,19 @@ export default function OrderHistoryScreen() { ) : ( <> - {poolNames.map((name, i) => ( + {poolNames.map((name, i) => { + const poolIconNames = ["leaf-outline", "medical-outline", "flask-outline", "color-fill-outline", "star-outline"] as const; + const poolIconColors = [colors.categoryWeedHash, "#e879f9", "#fb923c", "#38bdf8", colors.accent]; + const icon = poolIconNames[i] ?? "star-outline"; + const color = poolIconColors[i] ?? colors.accent; + return ( - + {poolPoints[i] || 0} Pts {name} - ))} + ); + })} {totalPoints > 0 && ( diff --git a/mobile/src/screens/client/ProfileScreen.tsx b/mobile/src/screens/client/ProfileScreen.tsx new file mode 100644 index 00000000..386ade1b --- /dev/null +++ b/mobile/src/screens/client/ProfileScreen.tsx @@ -0,0 +1,284 @@ +import React, { useState, useCallback, useMemo } from "react"; +import { + View, + Text, + ScrollView, + StyleSheet, + TouchableOpacity, + Alert, + KeyboardAvoidingView, + Platform, +} from "react-native"; +import { useFocusEffect } from "@react-navigation/native"; +import AsyncStorage from "@react-native-async-storage/async-storage"; +import { Ionicons } from "@expo/vector-icons"; +import { getMyProfile, updateMyProfile } from "../../api/api"; +import TextInput from "../../components/ui/TextInput"; +import Button from "../../components/ui/Button"; +import { useTheme } from "../../context/ThemeContext"; +import { spacing, borderRadius, fontSize, fontWeight } from "../../theme"; + +const STORAGE_ADDRESS = "profile_default_address"; +const STORAGE_PHONE = "profile_default_phone"; +const STORAGE_SIGNAL = "profile_signal_pseudo"; + +export default function ProfileScreen() { + const { colors } = useTheme(); + + // Données compte (backend) + const [nom, setNom] = useState(""); + const [prenom, setPrenom] = useState(""); + const [telephone, setTelephone] = useState(""); + const [username, setUsername] = useState(""); + + // Données locales (AsyncStorage) + const [defaultAddress, setDefaultAddress] = useState(""); + const [defaultPhone, setDefaultPhone] = useState(""); + const [signalPseudo, setSignalPseudo] = useState(""); + + const [loadingProfile, setLoadingProfile] = useState(true); + const [savingContact, setSavingContact] = useState(false); + + const loadData = useCallback(async () => { + setLoadingProfile(true); + const [savedAddress, savedPhone, savedSignal, profileRes] = await Promise.all([ + AsyncStorage.getItem(STORAGE_ADDRESS), + AsyncStorage.getItem(STORAGE_PHONE), + AsyncStorage.getItem(STORAGE_SIGNAL), + getMyProfile(), + ]); + + if (savedAddress !== null) setDefaultAddress(savedAddress); + if (savedPhone !== null) setDefaultPhone(savedPhone); + if (savedSignal !== null) setSignalPseudo(savedSignal); + + if (profileRes.success && profileRes.client) { + const c = profileRes.client; + setNom(c.nom ?? ""); + setPrenom(c.prenom ?? ""); + setTelephone(c.telephone ?? ""); + setUsername(c.username ?? ""); + // Init téléphone par défaut si pas encore sauvegardé + if (savedPhone === null && c.telephone) { + setDefaultPhone(c.telephone); + } + } + setLoadingProfile(false); + }, []); + + useFocusEffect(useCallback(() => { loadData(); }, [loadData])); + + const saveLocal = async () => { + await Promise.all([ + AsyncStorage.setItem(STORAGE_ADDRESS, defaultAddress.trim()), + AsyncStorage.setItem(STORAGE_PHONE, defaultPhone.trim()), + AsyncStorage.setItem(STORAGE_SIGNAL, signalPseudo.trim()), + ]); + Alert.alert("Enregistré", "Informations par défaut sauvegardées"); + }; + + const saveContact = async () => { + setSavingContact(true); + const res = await updateMyProfile({ nom: nom.trim(), prenom: prenom.trim(), telephone: telephone.trim() }); + setSavingContact(false); + if (res.success) { + Alert.alert("Succès", res.message ?? "Profil mis à jour"); + } else { + Alert.alert("Erreur", res.message ?? "Erreur lors de la mise à jour"); + } + }; + + const styles = useMemo(() => StyleSheet.create({ + container: { flex: 1, backgroundColor: colors.bgPrimary }, + content: { padding: spacing.l, paddingBottom: spacing.xxxl }, + header: { + flexDirection: "row", + alignItems: "center", + gap: spacing.m, + marginBottom: spacing.xl, + paddingTop: spacing.m, + }, + avatar: { + width: 56, + height: 56, + borderRadius: 28, + backgroundColor: colors.accent, + justifyContent: "center", + alignItems: "center", + }, + username: { color: colors.textPrimary, fontSize: fontSize.lg, fontWeight: fontWeight.bold }, + usernameLabel: { color: colors.textMuted, fontSize: fontSize.sm }, + card: { + backgroundColor: colors.bgCard, + borderRadius: borderRadius.md, + padding: spacing.l, + marginBottom: spacing.m, + borderWidth: 1, + borderColor: colors.borderLight, + }, + cardTitle: { + flexDirection: "row", + alignItems: "center", + gap: spacing.s, + marginBottom: spacing.m, + }, + cardTitleText: { + color: colors.textPrimary, + fontSize: fontSize.md, + fontWeight: fontWeight.semibold, + }, + hint: { + color: colors.textMuted, + fontSize: fontSize.xs, + marginBottom: spacing.m, + lineHeight: 18, + }, + row: { flexDirection: "row", gap: spacing.m }, + half: { flex: 1 }, + fieldSpacing: { marginTop: spacing.m }, + saveBtn: { + marginTop: spacing.m, + backgroundColor: colors.accent, + borderRadius: borderRadius.sm, + paddingVertical: spacing.m, + paddingHorizontal: spacing.l, + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + gap: spacing.s, + }, + saveBtnSecondary: { + backgroundColor: "transparent", + borderWidth: 1, + borderColor: colors.accent + "66", + }, + saveBtnText: { color: "#fff", fontSize: fontSize.sm, fontWeight: fontWeight.semibold }, + saveBtnTextSecondary: { color: colors.accent }, + }), [colors]); + + if (loadingProfile) { + return ( + + Chargement... + + ); + } + + return ( + + + + {/* Header */} + + + + + + @{username || "..."} + Mon compte + + + + {/* Carte compte */} + + + + Mon compte + + + + } + /> + + + } + /> + + + + } + keyboardType="phone-pad" + /> + + + + + {savingContact ? "Enregistrement..." : "Enregistrer le compte"} + + + + + {/* Carte adresse par défaut */} + + + + Adresse par défaut + + + Sera pré-remplie à la commande. Modifiable si vous n'êtes pas à cette adresse. + + } + multiline + numberOfLines={2} + /> + + + {/* Carte contact livraison */} + + + + Contact livraison + + + Numéro et pseudo Signal utilisés lors de la livraison. + + } + keyboardType="phone-pad" + /> + + } + /> + + + + + Enregistrer les infos par défaut + + + + + + + ); +}