chore: add change password

This commit is contained in:
2026-03-01 16:32:43 +01:00
parent 7b23f82355
commit e4020c6388
8 changed files with 261 additions and 7 deletions
@@ -0,0 +1,191 @@
import React, { useState } from "react";
import {
View,
Text,
TextInput,
TouchableOpacity,
StyleSheet,
Alert,
ActivityIndicator,
} from "react-native";
import { Feather } from "@expo/vector-icons";
import { useAuth } from "../../auth/AuthContext";
import { useTheme } from "../../context/ThemeContext";
import { changePassword } from "../../api/api";
export default function ChangePasswordScreen() {
const { setMustChangePassword } = useAuth();
const { colors } = useTheme();
const [currentPassword, setCurrentPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [showCurrent, setShowCurrent] = useState(false);
const [showNew, setShowNew] = useState(false);
const [showConfirm, setShowConfirm] = useState(false);
const [loading, setLoading] = useState(false);
const handleChangePassword = async () => {
if (!currentPassword || !newPassword || !confirmPassword) {
Alert.alert("Erreur", "Veuillez remplir tous les champs");
return;
}
if (newPassword.length < 8) {
Alert.alert(
"Erreur",
"Le nouveau mot de passe doit contenir au moins 8 caractères",
);
return;
}
if (newPassword !== confirmPassword) {
Alert.alert("Erreur", "Les nouveaux mots de passe ne correspondent pas");
return;
}
setLoading(true);
try {
const result = await changePassword(currentPassword, newPassword);
if (result.success) {
setMustChangePassword(false);
} else {
Alert.alert("Erreur", result.message || "Erreur inattendue");
}
} finally {
setLoading(false);
}
};
return (
<View style={[styles.container, { backgroundColor: colors.bgPrimary }]}>
<View style={styles.iconWrapper}>
<Feather name="lock" size={48} color="#7c3aed" />
</View>
<Text style={[styles.title, { color: colors.textPrimary }]}>
Changez votre mot de passe
</Text>
<Text style={[styles.subtitle, { color: colors.textSecondary }]}>
Pour accéder à l'application, vous devez définir un nouveau mot de
passe personnel.
</Text>
<View style={[styles.inputWrapper, { backgroundColor: colors.bgSecondary, borderColor: colors.border }]}>
<Feather name="lock" size={18} color={colors.textMuted} style={styles.inputIcon} />
<TextInput
style={[styles.input, { color: colors.textPrimary }]}
placeholder="Mot de passe actuel"
placeholderTextColor={colors.textMuted}
secureTextEntry={!showCurrent}
value={currentPassword}
onChangeText={setCurrentPassword}
autoCapitalize="none"
autoCorrect={false}
/>
<TouchableOpacity onPress={() => setShowCurrent(!showCurrent)}>
<Feather name={showCurrent ? "eye-off" : "eye"} size={18} color={colors.textMuted} />
</TouchableOpacity>
</View>
<View style={[styles.inputWrapper, { backgroundColor: colors.bgSecondary, borderColor: colors.border }]}>
<Feather name="key" size={18} color={colors.textMuted} style={styles.inputIcon} />
<TextInput
style={[styles.input, { color: colors.textPrimary }]}
placeholder="Nouveau mot de passe (min. 8 car.)"
placeholderTextColor={colors.textMuted}
secureTextEntry={!showNew}
value={newPassword}
onChangeText={setNewPassword}
autoCapitalize="none"
autoCorrect={false}
/>
<TouchableOpacity onPress={() => setShowNew(!showNew)}>
<Feather name={showNew ? "eye-off" : "eye"} size={18} color={colors.textMuted} />
</TouchableOpacity>
</View>
<View style={[styles.inputWrapper, { backgroundColor: colors.bgSecondary, borderColor: colors.border }]}>
<Feather name="check-circle" size={18} color={colors.textMuted} style={styles.inputIcon} />
<TextInput
style={[styles.input, { color: colors.textPrimary }]}
placeholder="Confirmer le nouveau mot de passe"
placeholderTextColor={colors.textMuted}
secureTextEntry={!showConfirm}
value={confirmPassword}
onChangeText={setConfirmPassword}
autoCapitalize="none"
autoCorrect={false}
/>
<TouchableOpacity onPress={() => setShowConfirm(!showConfirm)}>
<Feather name={showConfirm ? "eye-off" : "eye"} size={18} color={colors.textMuted} />
</TouchableOpacity>
</View>
<TouchableOpacity
style={[styles.button, loading && styles.buttonDisabled]}
onPress={handleChangePassword}
disabled={loading}
>
{loading ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={styles.buttonText}>Confirmer</Text>
)}
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 24,
justifyContent: "center",
},
iconWrapper: {
alignItems: "center",
marginBottom: 16,
},
title: {
fontSize: 22,
fontWeight: "bold",
marginBottom: 10,
textAlign: "center",
},
subtitle: {
fontSize: 14,
marginBottom: 32,
textAlign: "center",
lineHeight: 20,
},
inputWrapper: {
flexDirection: "row",
alignItems: "center",
borderWidth: 1,
borderRadius: 8,
paddingHorizontal: 12,
height: 50,
marginBottom: 14,
},
inputIcon: {
marginRight: 8,
},
input: {
flex: 1,
fontSize: 15,
},
button: {
backgroundColor: "#7c3aed",
height: 50,
borderRadius: 8,
justifyContent: "center",
alignItems: "center",
marginTop: 8,
},
buttonDisabled: {
opacity: 0.7,
},
buttonText: {
color: "#fff",
fontSize: 16,
fontWeight: "600",
},
});
@@ -60,7 +60,7 @@ const LoginClient = () => {
formData.password,
);
if (result.success && result.access_token) {
await loginClient(result.access_token);
await loginClient(result.access_token, result.user?.must_change_password ?? false);
} else {
const errorMessage =
result.message || "Identifiants incorrects";