import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import '../../services/auth_service.dart'; import '../../widgets/image_button.dart'; /// Réinitialisation du mot de passe via lien e-mail (ticket #127, distinct de [CreatePasswordScreen]). class ResetPasswordScreen extends StatefulWidget { final String? token; const ResetPasswordScreen({super.key, this.token}); @override State createState() => _ResetPasswordScreenState(); } class _ResetPasswordScreenState extends State { final _formKey = GlobalKey(); final _passwordCtrl = TextEditingController(); final _confirmCtrl = TextEditingController(); bool _submitting = false; String? _message; String get _token => (widget.token ?? '').trim(); @override void dispose() { _passwordCtrl.dispose(); _confirmCtrl.dispose(); super.dispose(); } bool _isStrongPassword(String v) { if (v.length < 8) return false; final hasUpper = RegExp(r'[A-Z]').hasMatch(v); final hasDigit = RegExp(r'\d').hasMatch(v); return hasUpper && hasDigit; } String? _validatePassword(String? value) { final v = value ?? ''; if (v.isEmpty) return 'Veuillez saisir un mot de passe.'; if (!_isStrongPassword(v)) { return 'Minimum 8 caractères, avec au moins 1 majuscule et 1 chiffre.'; } return null; } String? _validateConfirmation(String? value) { final v = value ?? ''; if (v.isEmpty) return 'Veuillez confirmer le mot de passe.'; if (v != _passwordCtrl.text) return 'Les mots de passe ne correspondent pas.'; return null; } bool get _tokenPresent => _token.isNotEmpty; Future _submit() async { if (_submitting || !_tokenPresent) return; if (!(_formKey.currentState?.validate() ?? false)) return; setState(() { _submitting = true; _message = null; }); try { await AuthService.resetPasswordWithToken( token: _token, password: _passwordCtrl.text, passwordConfirmation: _confirmCtrl.text, ); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( 'Mot de passe mis à jour. Vous pouvez vous connecter.', style: GoogleFonts.merienda(), ), ), ); context.go('/login'); } catch (e) { if (!mounted) return; final raw = e.toString().replaceAll('Exception: ', ''); setState(() { _submitting = false; _message = _neutralResetFeedback(raw); }); } } /// Erreurs liées au lien : libellé unique neutre (#127). String _neutralResetFeedback(String raw) { final l = raw.toLowerCase(); if (l.contains('token') || l.contains('invalide') || l.contains('expir')) { return 'Lien invalide ou expiré.'; } return raw; } @override Widget build(BuildContext context) { return Scaffold( body: Container( decoration: const BoxDecoration( image: DecorationImage( image: AssetImage('assets/images/paper2.png'), fit: BoxFit.cover, repeat: ImageRepeat.repeat, ), ), child: Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 560), child: Card( color: const Color(0xF4FFFFFF), margin: const EdgeInsets.all(24), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), child: Padding( padding: const EdgeInsets.all(24), child: _tokenPresent ? _buildForm() : _buildInvalidToken(), ), ), ), ), ), ); } Widget _buildForm() { return Form( key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( 'Nouveau mot de passe', style: GoogleFonts.merienda(fontSize: 28, fontWeight: FontWeight.w700), textAlign: TextAlign.center, ), const SizedBox(height: 10), Text( 'Choisissez un mot de passe pour votre compte.', style: GoogleFonts.merienda(fontSize: 14, color: Colors.black87), textAlign: TextAlign.center, ), const SizedBox(height: 18), TextFormField( controller: _passwordCtrl, obscureText: true, validator: _validatePassword, decoration: const InputDecoration( labelText: 'Mot de passe', border: OutlineInputBorder(), ), ), const SizedBox(height: 14), TextFormField( controller: _confirmCtrl, obscureText: true, validator: _validateConfirmation, decoration: const InputDecoration( labelText: 'Confirmer le mot de passe', border: OutlineInputBorder(), ), ), const SizedBox(height: 10), Text( 'Au moins 8 caractères, dont 1 majuscule et 1 chiffre.', style: GoogleFonts.merienda(fontSize: 12, color: Colors.black54), ), if (_message != null) ...[ const SizedBox(height: 12), Text( _message!, style: GoogleFonts.merienda( color: Colors.red.shade700, fontSize: 13, ), ), ], const SizedBox(height: 16), _submitting ? const Center(child: CircularProgressIndicator()) : ImageButton( bg: 'assets/images/bg_green.png', width: double.infinity, height: 44, text: 'Enregistrer', textColor: const Color(0xFF2D6A4F), onPressed: _submit, ), ], ), ); } Widget _buildInvalidToken() { return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( 'Réinitialisation', style: GoogleFonts.merienda(fontSize: 28, fontWeight: FontWeight.w700), textAlign: TextAlign.center, ), const SizedBox(height: 20), Text( 'Lien invalide ou expiré.', style: GoogleFonts.merienda(fontSize: 16), textAlign: TextAlign.center, ), const SizedBox(height: 20), ImageButton( bg: 'assets/images/bg_green.png', width: double.infinity, height: 44, text: 'Retour à la connexion', textColor: const Color(0xFF2D6A4F), onPressed: () => context.go('/login'), ), ], ); } }