petitspas/frontend/lib/screens/auth/forgot_password_screen.dart
Julien Martin 644ba6c9c9 refactor(front): mutualiser les formulaires token password (#127)
Extrait un composant commun PasswordTokenFormScreen pour les pages create/reset password, conserve les comportements métier via wrappers (verify/create vs reset) et active la soumission sur Entrée dans mot de passe oublié.

Made-with: Cursor
2026-04-24 12:03:43 +02:00

161 lines
5.8 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 '../../utils/email_utils.dart';
import '../../widgets/image_button.dart';
/// Demande de lien de réinitialisation (ticket #127).
class ForgotPasswordScreen extends StatefulWidget {
const ForgotPasswordScreen({super.key});
@override
State<ForgotPasswordScreen> createState() => _ForgotPasswordScreenState();
}
class _ForgotPasswordScreenState extends State<ForgotPasswordScreen> {
final _formKey = GlobalKey<FormState>();
final _emailCtrl = TextEditingController();
bool _submitting = false;
String? _fieldError;
@override
void dispose() {
_emailCtrl.dispose();
super.dispose();
}
String? _validateEmail(String? value) {
return validateEmail(value, allowEmpty: false);
}
Future<void> _submit() async {
if (_submitting) return;
setState(() => _fieldError = null);
if (!(_formKey.currentState?.validate() ?? false)) return;
setState(() => _submitting = true);
try {
await AuthService.requestForgotPassword(_emailCtrl.text);
if (!mounted) return;
setState(() => _submitting = false);
await showDialog<void>(
context: context,
builder: (ctx) => AlertDialog(
title: Text('Demande enregistrée', style: GoogleFonts.merienda(fontWeight: FontWeight.w700)),
content: Text(
'Si un compte correspond à cette adresse, vous recevrez un e-mail '
'avec un lien pour réinitialiser votre mot de passe.',
style: GoogleFonts.merienda(),
),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(),
child: Text('OK', style: GoogleFonts.merienda(color: const Color(0xFF2D6A4F))),
),
],
),
);
if (mounted) context.go('/login');
} catch (e) {
if (!mounted) return;
setState(() {
_submitting = false;
_fieldError = e.toString().replaceAll('Exception: ', '');
});
}
}
@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: 520),
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: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'Mot de passe oublié',
style: GoogleFonts.merienda(fontSize: 28, fontWeight: FontWeight.w700),
textAlign: TextAlign.center,
),
const SizedBox(height: 12),
Text(
'Indiquez ladresse e-mail de votre compte. Nous vous enverrons un lien si elle est reconnue.',
style: GoogleFonts.merienda(fontSize: 14, color: Colors.black87),
textAlign: TextAlign.center,
),
const SizedBox(height: 20),
TextFormField(
controller: _emailCtrl,
keyboardType: TextInputType.emailAddress,
autocorrect: false,
textInputAction: TextInputAction.done,
onFieldSubmitted: (_) => _submit(),
validator: _validateEmail,
decoration: const InputDecoration(
labelText: 'E-mail',
border: OutlineInputBorder(),
),
),
if (_fieldError != null) ...[
const SizedBox(height: 12),
Text(
_fieldError!,
style: GoogleFonts.merienda(color: Colors.red.shade700, fontSize: 13),
),
],
const SizedBox(height: 20),
_submitting
? const Center(child: CircularProgressIndicator())
: ImageButton(
bg: 'assets/images/bg_green.png',
width: double.infinity,
height: 44,
text: 'Envoyer le lien',
textColor: const Color(0xFF2D6A4F),
onPressed: _submit,
),
const SizedBox(height: 12),
TextButton(
onPressed: () => context.go('/login'),
child: Text(
'Retour à la connexion',
style: GoogleFonts.merienda(
color: const Color(0xFF2D6A4F),
decoration: TextDecoration.underline,
),
),
),
],
),
),
),
),
),
),
),
);
}
}