diff --git a/lib/main.dart b/lib/main.dart index a65a396..72f6e90 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -90,59 +90,13 @@ class _AuthGateState extends State { /// Shows a dialog so the user can type a new password after clicking /// the "Reset Password" link from their email. Future _showResetPasswordDialog() async { - final passwordController = TextEditingController(); + if (!mounted) return; await showDialog( context: context, barrierDismissible: false, - builder: (ctx) => AlertDialog( - title: const Text('Set New Password'), - content: TextField( - controller: passwordController, - obscureText: true, - decoration: const InputDecoration( - labelText: 'New password', - border: OutlineInputBorder(), - ), - ), - actions: [ - TextButton( - onPressed: () => Navigator.of(ctx).pop(), - child: const Text('Cancel'), - ), - ElevatedButton( - onPressed: () async { - final newPassword = passwordController.text.trim(); - if (newPassword.length < 6) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Password must be at least 6 characters.')), - ); - return; - } - try { - await supabase.auth.updateUser( - UserAttributes(password: newPassword), - ); - if (!ctx.mounted) return; - Navigator.of(ctx).pop(); - if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Password updated successfully!')), - ); - } on AuthException catch (e) { - if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(e.message), backgroundColor: Colors.red), - ); - } - }, - child: const Text('Save'), - ), - ], - ), + builder: (_) => const _ResetPasswordDialog(), ); - - passwordController.dispose(); } @override @@ -161,6 +115,86 @@ class _AuthGateState extends State { } } +// ── Reset Password Dialog ───────────────────────────────────────────── +class _ResetPasswordDialog extends StatefulWidget { + const _ResetPasswordDialog(); + + @override + State<_ResetPasswordDialog> createState() => _ResetPasswordDialogState(); +} + +class _ResetPasswordDialogState extends State<_ResetPasswordDialog> { + final _passwordController = TextEditingController(); + bool _isSaving = false; + + @override + void dispose() { + _passwordController.dispose(); + super.dispose(); + } + + Future _save() async { + final newPassword = _passwordController.text.trim(); + if (newPassword.length < 6) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Password must be at least 6 characters.')), + ); + return; + } + + setState(() => _isSaving = true); + + try { + await supabase.auth.updateUser( + UserAttributes(password: newPassword), + ); + if (!mounted) return; + Navigator.of(context).pop(); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Password updated successfully!')), + ); + } on AuthException catch (e) { + if (!mounted) return; + setState(() => _isSaving = false); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(e.message), backgroundColor: Colors.red), + ); + } + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text('Set New Password'), + content: TextField( + controller: _passwordController, + obscureText: true, + decoration: const InputDecoration( + labelText: 'New password', + border: OutlineInputBorder(), + ), + onSubmitted: (_) => _save(), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Cancel'), + ), + ElevatedButton( + onPressed: _isSaving ? null : _save, + child: _isSaving + ? const SizedBox( + height: 18, + width: 18, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Text('Save'), + ), + ], + ); + } +} + // ── Login Screen ────────────────────────────────────────────────────── class LoginScreen extends StatefulWidget { const LoginScreen({super.key});