feat(#47): Ajout de la modale de changement de mot de passe obligatoire

Implémentation complète du ticket #47 :
- Mise à jour de l'URL API vers app.ptits-pas.fr
- Ajout du champ changement_mdp_obligatoire au modèle AppUser
- Ajout des endpoints /auth/me et /auth/change-password-required
- Implémentation de la vraie logique de connexion dans AuthService
- Création de la modale ChangePasswordDialog non-dismissible
- Connexion du bouton de connexion avec gestion de la modale
- Ajout des routes admin-dashboard et parent-dashboard

La modale s'affiche automatiquement après connexion si
changement_mdp_obligatoire = true et bloque l'utilisateur jusqu'au
changement de mot de passe.
This commit is contained in:
2026-01-27 16:30:15 +01:00
parent b3ec1b94ea
commit fe71fdf28e
7 changed files with 564 additions and 40 deletions
+114 -16
View File
@@ -4,9 +4,10 @@ import 'package:google_fonts/google_fonts.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:url_launcher/url_launcher.dart';
import 'package:p_tits_pas/services/bug_report_service.dart';
import 'package:go_router/go_router.dart';
import '../../widgets/image_button.dart';
import '../../widgets/custom_app_text_field.dart';
import '../../services/auth_service.dart';
import '../../widgets/auth/change_password_dialog.dart';
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@@ -19,6 +20,9 @@ class _LoginPageState extends State<LoginPage> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
bool _isLoading = false;
String? _errorMessage;
@override
void dispose() {
@@ -41,12 +45,80 @@ class _LoginPageState extends State<LoginPage> {
if (value == null || value.isEmpty) {
return 'Veuillez entrer votre mot de passe';
}
if (value.length < 6) {
return 'Le mot de passe doit contenir au moins 6 caractères';
}
return null;
}
/// Gère la connexion de l'utilisateur
Future<void> _handleLogin() async {
// Réinitialiser le message d'erreur
setState(() {
_errorMessage = null;
});
if (!(_formKey.currentState?.validate() ?? false)) {
return;
}
setState(() {
_isLoading = true;
});
try {
// Appeler le service d'authentification
final user = await AuthService.login(
_emailController.text.trim(),
_passwordController.text,
);
if (!mounted) return;
// Vérifier si l'utilisateur doit changer son mot de passe
if (user.changementMdpObligatoire) {
if (!mounted) return;
// Afficher la modale de changement de mot de passe (non-dismissible)
final result = await showDialog<bool>(
context: context,
barrierDismissible: false,
builder: (context) => const ChangePasswordDialog(),
);
// Si le changement de mot de passe a réussi, rafraîchir l'utilisateur
if (result == true && mounted) {
await AuthService.refreshCurrentUser();
}
}
if (!mounted) return;
// Rediriger selon le rôle de l'utilisateur
_redirectUserByRole(user.role);
} catch (e) {
setState(() {
_isLoading = false;
_errorMessage = e.toString().replaceAll('Exception: ', '');
});
}
}
/// Redirige l'utilisateur selon son rôle
void _redirectUserByRole(String role) {
switch (role.toLowerCase()) {
case 'super_admin':
case 'gestionnaire':
Navigator.pushReplacementNamed(context, '/admin-dashboard');
break;
case 'parent':
Navigator.pushReplacementNamed(context, '/parent-dashboard');
break;
case 'assistante_maternelle':
Navigator.pushReplacementNamed(context, '/am-dashboard');
break;
default:
Navigator.pushReplacementNamed(context, '/home');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -136,20 +208,46 @@ class _LoginPageState extends State<LoginPage> {
],
),
const SizedBox(height: 20),
// Message d'erreur
if (_errorMessage != null)
Container(
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.only(bottom: 15),
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],
),
),
),
],
),
),
// Bouton centré
Center(
child: ImageButton(
bg: 'assets/images/btn_green.png',
width: 300,
height: 40,
text: 'Se connecter',
textColor: const Color(0xFF2D6A4F),
onPressed: () {
if (_formKey.currentState?.validate() ?? false) {
// TODO: Implémenter la logique de connexion
}
},
),
child: _isLoading
? const CircularProgressIndicator()
: ImageButton(
bg: 'assets/images/btn_green.png',
width: 300,
height: 40,
text: 'Se connecter',
textColor: const Color(0xFF2D6A4F),
onPressed: _handleLogin,
),
),
const SizedBox(height: 10),
// Lien mot de passe oublié