petitspas/frontend/lib/widgets/auth/change_password_dialog.dart
Julien Martin b18d5c8a9e feat(frontend): Refonte infrastructure formulaires multi-modes
- Support des modes Desktop/Mobile et Édition/Lecture seule
- Refactoring des widgets de formulaire (PersonalInfo, ProfessionalInfo, Presentation, ChildCard)
- Mise à jour des écrans de récapitulatif (ParentStep5, AmStep4)
- Ajout de navigation (Précédent/Soumettre) sur mobile

Closes #78

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-07 14:51:33 +01:00

303 lines
10 KiB
Dart

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../services/auth_service.dart';
import '../../widgets/custom_app_text_field.dart';
import '../../widgets/image_button.dart';
/// Dialogue modal bloquant pour le changement de mot de passe obligatoire
/// Utilisé après la première connexion quand changement_mdp_obligatoire = true
class ChangePasswordDialog extends StatefulWidget {
const ChangePasswordDialog({super.key});
@override
State<ChangePasswordDialog> createState() => _ChangePasswordDialogState();
}
class _ChangePasswordDialogState extends State<ChangePasswordDialog> {
final _formKey = GlobalKey<FormState>();
final _currentPasswordController = TextEditingController();
final _newPasswordController = TextEditingController();
final _confirmPasswordController = TextEditingController();
bool _isLoading = false;
String? _errorMessage;
@override
void dispose() {
_currentPasswordController.dispose();
_newPasswordController.dispose();
_confirmPasswordController.dispose();
super.dispose();
}
String? _validateCurrentPassword(String? value) {
if (value == null || value.isEmpty) {
return 'Veuillez entrer votre mot de passe actuel';
}
return null;
}
String? _validateNewPassword(String? value) {
if (value == null || value.isEmpty) {
return 'Veuillez entrer un nouveau mot de passe';
}
if (value.length < 8) {
return 'Le mot de passe doit contenir au moins 8 caractères';
}
if (!RegExp(r'[A-Z]').hasMatch(value)) {
return 'Le mot de passe doit contenir au moins une majuscule';
}
if (!RegExp(r'[a-z]').hasMatch(value)) {
return 'Le mot de passe doit contenir au moins une minuscule';
}
if (!RegExp(r'[0-9]').hasMatch(value)) {
return 'Le mot de passe doit contenir au moins un chiffre';
}
return null;
}
String? _validateConfirmPassword(String? value) {
if (value == null || value.isEmpty) {
return 'Veuillez confirmer votre nouveau mot de passe';
}
if (value != _newPasswordController.text) {
return 'Les mots de passe ne correspondent pas';
}
return null;
}
Future<void> _handleSubmit() async {
// Réinitialiser le message d'erreur
setState(() {
_errorMessage = null;
});
if (!(_formKey.currentState?.validate() ?? false)) {
return;
}
setState(() {
_isLoading = true;
});
try {
await AuthService.changePasswordRequired(
currentPassword: _currentPasswordController.text,
newPassword: _newPasswordController.text,
);
if (mounted) {
// Fermer le dialogue avec succès
Navigator.of(context).pop(true);
// Afficher un message de succès
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Mot de passe modifié avec succès',
style: GoogleFonts.merienda(),
),
backgroundColor: Colors.green,
),
);
}
} catch (e) {
setState(() {
_isLoading = false;
_errorMessage = e.toString().replaceAll('Exception: ', '');
});
}
}
@override
Widget build(BuildContext context) {
return PopScope(
// Empêcher la fermeture du dialogue avec le bouton retour
canPop: false,
child: Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
child: Container(
constraints: const BoxConstraints(maxWidth: 500),
padding: const EdgeInsets.all(30),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Titre
Text(
'Changement de mot de passe obligatoire',
style: GoogleFonts.merienda(
fontSize: 20,
fontWeight: FontWeight.bold,
color: const Color(0xFF2D6A4F),
),
textAlign: TextAlign.center,
),
const SizedBox(height: 10),
// Message d'explication
Text(
'Pour des raisons de sécurité, vous devez changer votre mot de passe avant de continuer.',
style: GoogleFonts.merienda(
fontSize: 14,
color: Colors.black87,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 25),
// Champ mot de passe actuel
CustomAppTextField(
controller: _currentPasswordController,
labelText: 'Mot de passe actuel',
hintText: 'Votre mot de passe actuel',
obscureText: true,
validator: _validateCurrentPassword,
style: CustomAppTextFieldStyle.lavande,
fieldHeight: 53,
fieldWidth: double.infinity,
enabled: !_isLoading,
),
const SizedBox(height: 15),
// Champ nouveau mot de passe
CustomAppTextField(
controller: _newPasswordController,
labelText: 'Nouveau mot de passe',
hintText: 'Minimum 8 caractères',
obscureText: true,
validator: _validateNewPassword,
style: CustomAppTextFieldStyle.jaune,
fieldHeight: 53,
fieldWidth: double.infinity,
enabled: !_isLoading,
),
const SizedBox(height: 15),
// Champ confirmation mot de passe
CustomAppTextField(
controller: _confirmPasswordController,
labelText: 'Confirmer le mot de passe',
hintText: 'Retapez le nouveau mot de passe',
obscureText: true,
validator: _validateConfirmPassword,
style: CustomAppTextFieldStyle.lavande,
fieldHeight: 53,
fieldWidth: double.infinity,
enabled: !_isLoading,
),
const SizedBox(height: 10),
// Critères du mot de passe
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(10),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Le mot de passe doit contenir :',
style: GoogleFonts.merienda(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
const SizedBox(height: 5),
_buildPasswordCriteria('Au moins 8 caractères'),
_buildPasswordCriteria('Au moins une majuscule'),
_buildPasswordCriteria('Au moins une minuscule'),
_buildPasswordCriteria('Au moins un chiffre'),
],
),
),
// Message d'erreur
if (_errorMessage != null) ...[
const SizedBox(height: 15),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.red[50],
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.red[300]!),
),
child: Row(
children: [
Icon(Icons.error_outline, color: Colors.red[700], size: 20),
const SizedBox(width: 10),
Expanded(
child: Text(
_errorMessage!,
style: GoogleFonts.merienda(
fontSize: 12,
color: Colors.red[700],
),
),
),
],
),
),
],
const SizedBox(height: 25),
// Bouton de validation
Center(
child: _isLoading
? const CircularProgressIndicator()
: ImageButton(
bg: 'assets/images/bg_green.png',
width: 250,
height: 40,
text: 'Changer le mot de passe',
textColor: const Color(0xFF2D6A4F),
onPressed: _handleSubmit,
),
),
],
),
),
),
),
);
}
Widget _buildPasswordCriteria(String text) {
return Padding(
padding: const EdgeInsets.only(top: 3, left: 5),
child: Row(
children: [
const Icon(Icons.check_circle_outline, size: 14, color: Colors.green),
const SizedBox(width: 8),
Text(
text,
style: GoogleFonts.merienda(
fontSize: 11,
color: Colors.black87,
),
),
],
),
);
}
}