285 lines
12 KiB
TypeScript
285 lines
12 KiB
TypeScript
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 (
|
|
<View style={[styles.container, { justifyContent: "center", alignItems: "center" }]}>
|
|
<Text style={{ color: colors.textMuted }}>Chargement...</Text>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<KeyboardAvoidingView style={styles.container} behavior={Platform.OS === "ios" ? "padding" : undefined}>
|
|
<ScrollView contentContainerStyle={styles.content} keyboardShouldPersistTaps="handled">
|
|
|
|
{/* Header */}
|
|
<View style={styles.header}>
|
|
<View style={styles.avatar}>
|
|
<Ionicons name="person" size={28} color="#fff" />
|
|
</View>
|
|
<View>
|
|
<Text style={styles.username}>@{username || "..."}</Text>
|
|
<Text style={styles.usernameLabel}>Mon compte</Text>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Carte compte */}
|
|
<View style={styles.card}>
|
|
<View style={styles.cardTitle}>
|
|
<Ionicons name="person-outline" size={18} color={colors.accent} />
|
|
<Text style={styles.cardTitleText}>Mon compte</Text>
|
|
</View>
|
|
<View style={styles.row}>
|
|
<View style={styles.half}>
|
|
<TextInput
|
|
placeholder="Prénom"
|
|
value={prenom}
|
|
onChangeText={setPrenom}
|
|
icon={<Ionicons name="person-outline" size={18} color={colors.textMuted} />}
|
|
/>
|
|
</View>
|
|
<View style={styles.half}>
|
|
<TextInput
|
|
placeholder="Nom"
|
|
value={nom}
|
|
onChangeText={setNom}
|
|
icon={<Ionicons name="person-outline" size={18} color={colors.textMuted} />}
|
|
/>
|
|
</View>
|
|
</View>
|
|
<View style={styles.fieldSpacing}>
|
|
<TextInput
|
|
placeholder="Téléphone (compte)"
|
|
value={telephone}
|
|
onChangeText={setTelephone}
|
|
icon={<Ionicons name="call-outline" size={18} color={colors.textMuted} />}
|
|
keyboardType="phone-pad"
|
|
/>
|
|
</View>
|
|
<TouchableOpacity
|
|
style={styles.saveBtn}
|
|
onPress={saveContact}
|
|
disabled={savingContact}
|
|
>
|
|
<Ionicons name="save-outline" size={16} color="#fff" />
|
|
<Text style={styles.saveBtnText}>
|
|
{savingContact ? "Enregistrement..." : "Enregistrer le compte"}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
{/* Carte adresse par défaut */}
|
|
<View style={styles.card}>
|
|
<View style={styles.cardTitle}>
|
|
<Ionicons name="location-outline" size={18} color="#10b981" />
|
|
<Text style={styles.cardTitleText}>Adresse par défaut</Text>
|
|
</View>
|
|
<Text style={styles.hint}>
|
|
Sera pré-remplie à la commande. Modifiable si vous n'êtes pas à cette adresse.
|
|
</Text>
|
|
<TextInput
|
|
placeholder="Numéro, rue, ville, code postal"
|
|
value={defaultAddress}
|
|
onChangeText={setDefaultAddress}
|
|
icon={<Ionicons name="location-outline" size={18} color={colors.textMuted} />}
|
|
multiline
|
|
numberOfLines={2}
|
|
/>
|
|
</View>
|
|
|
|
{/* Carte contact livraison */}
|
|
<View style={styles.card}>
|
|
<View style={styles.cardTitle}>
|
|
<Ionicons name="call-outline" size={18} color="#3b82f6" />
|
|
<Text style={styles.cardTitleText}>Contact livraison</Text>
|
|
</View>
|
|
<Text style={styles.hint}>
|
|
Numéro et pseudo Signal utilisés lors de la livraison.
|
|
</Text>
|
|
<TextInput
|
|
placeholder="Téléphone par défaut"
|
|
value={defaultPhone}
|
|
onChangeText={setDefaultPhone}
|
|
icon={<Ionicons name="call-outline" size={18} color={colors.textMuted} />}
|
|
keyboardType="phone-pad"
|
|
/>
|
|
<View style={styles.fieldSpacing}>
|
|
<TextInput
|
|
placeholder="Pseudo Signal (optionnel)"
|
|
value={signalPseudo}
|
|
onChangeText={setSignalPseudo}
|
|
icon={<Ionicons name="chatbubble-outline" size={18} color={colors.textMuted} />}
|
|
/>
|
|
</View>
|
|
<TouchableOpacity
|
|
style={[styles.saveBtn, styles.saveBtnSecondary]}
|
|
onPress={saveLocal}
|
|
>
|
|
<Ionicons name="save-outline" size={16} color={colors.accent} />
|
|
<Text style={[styles.saveBtnText, styles.saveBtnTextSecondary]}>
|
|
Enregistrer les infos par défaut
|
|
</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
</ScrollView>
|
|
</KeyboardAvoidingView>
|
|
);
|
|
}
|