madari-oss/lib/features/auth/pages/forget_password_page.dart

205 lines
6.7 KiB
Dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../pocketbase/service/pocketbase.service.dart';
class ForgotPasswordPage extends StatefulWidget {
const ForgotPasswordPage({super.key});
@override
State<ForgotPasswordPage> createState() => _ForgotPasswordPageState();
}
class _ForgotPasswordPageState extends State<ForgotPasswordPage> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _emailFocusNode = FocusNode();
bool _isLoading = false;
final pocketbase = AppPocketBaseService.instance.pb;
Widget _buildEmailField() {
return TextFormField(
controller: _emailController,
focusNode: _emailFocusNode,
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.done,
autofillHints: const [AutofillHints.email],
style: Theme.of(context).textTheme.bodyLarge,
decoration: InputDecoration(
labelText: 'Email',
prefixIcon: const Icon(Icons.email_outlined),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: Theme.of(context).colorScheme.outline,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: Theme.of(context).colorScheme.outline.withValues(alpha: 0.5),
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: Theme.of(context).colorScheme.primary,
width: 2,
),
),
filled: true,
fillColor: Theme.of(context).colorScheme.surface,
),
validator: (value) {
if (value?.isEmpty ?? true) {
return 'Please enter your email';
}
if (!RegExp(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$').hasMatch(value!)) {
return 'Please enter a valid email';
}
return null;
},
onFieldSubmitted: (_) => _requestPasswordReset(),
);
}
Future<void> _requestPasswordReset() async {
if (!_formKey.currentState!.validate()) return;
setState(() => _isLoading = true);
try {
await pocketbase
.collection('users')
.requestPasswordReset(_emailController.text.trim());
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Password reset instructions have been sent to your email'),
backgroundColor: Colors.green,
),
);
// Navigate back to sign in page
context.go('/signin');
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to send reset instructions: ${e.toString()}'),
backgroundColor: Colors.red,
),
);
}
} finally {
if (mounted) {
setState(() => _isLoading = false);
}
}
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return Scaffold(
backgroundColor: colorScheme.surface,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
leading: IconButton(
icon: Icon(Icons.arrow_back, color: colorScheme.onSurface),
onPressed: () => context.go('/signin'),
),
),
body: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 400),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Hero(
tag: 'app_logo',
child: Image.asset(
'assets/icon/icon_mini.png',
height: 80,
width: 80,
),
),
const SizedBox(height: 24),
Text(
'Forgot Password',
style: theme.textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
color: colorScheme.onSurface,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
'Enter your email address to receive password reset instructions',
style: theme.textTheme.bodyLarge?.copyWith(
color: colorScheme.onSurface.withValues(alpha: 0.7),
),
textAlign: TextAlign.center,
),
const SizedBox(height: 32),
_buildEmailField(),
const SizedBox(height: 24),
AnimatedContainer(
duration: const Duration(milliseconds: 300),
height: 50,
child: ElevatedButton(
onPressed: _isLoading ? null : _requestPasswordReset,
style: ElevatedButton.styleFrom(
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: _isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
Colors.white,
),
),
)
: const Text(
'Reset Password',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
),
],
),
),
),
),
),
);
}
@override
void dispose() {
_emailController.dispose();
_emailFocusNode.dispose();
super.dispose();
}
}