258 lines
11 KiB
Dart
258 lines
11 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:lucide_icons/lucide_icons.dart';
|
|
import '../providers/auth_provider.dart';
|
|
import 'auth_screen.dart';
|
|
|
|
class ResetPasswordScreen extends ConsumerStatefulWidget {
|
|
final String email;
|
|
const ResetPasswordScreen({super.key, required this.email});
|
|
|
|
@override
|
|
ConsumerState<ResetPasswordScreen> createState() => _ResetPasswordScreenState();
|
|
}
|
|
|
|
class _ResetPasswordScreenState extends ConsumerState<ResetPasswordScreen> {
|
|
final otpController = TextEditingController();
|
|
final newPasswordController = TextEditingController();
|
|
final confirmPasswordController = TextEditingController();
|
|
bool _isLoading = false;
|
|
bool _obscurePassword = true;
|
|
bool _obscureConfirm = true;
|
|
String? _errorMessage;
|
|
|
|
@override
|
|
void dispose() {
|
|
otpController.dispose();
|
|
newPasswordController.dispose();
|
|
confirmPasswordController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _resetPassword() async {
|
|
final otp = otpController.text.trim();
|
|
final newPassword = newPasswordController.text;
|
|
final confirmPassword = confirmPasswordController.text;
|
|
|
|
if (otp.isEmpty || otp.length != 6) {
|
|
setState(() => _errorMessage = 'Please enter a valid 6-digit OTP');
|
|
return;
|
|
}
|
|
if (newPassword.isEmpty || newPassword.length < 6) {
|
|
setState(() => _errorMessage = 'Password must be at least 6 characters');
|
|
return;
|
|
}
|
|
if (newPassword != confirmPassword) {
|
|
setState(() => _errorMessage = 'Passwords do not match');
|
|
return;
|
|
}
|
|
|
|
setState(() {
|
|
_isLoading = true;
|
|
_errorMessage = null;
|
|
});
|
|
|
|
final success = await ref.read(authControllerProvider.notifier).resetPassword(widget.email, otp, newPassword);
|
|
|
|
if (mounted) {
|
|
setState(() => _isLoading = false);
|
|
if (success) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text('Password reset successfully! Please log in.'),
|
|
backgroundColor: Color(0xFF6C63FF),
|
|
),
|
|
);
|
|
Navigator.pushAndRemoveUntil(
|
|
context,
|
|
MaterialPageRoute(builder: (_) => const AuthScreen()),
|
|
(route) => false,
|
|
);
|
|
} else {
|
|
setState(() => _errorMessage = 'Invalid OTP or reset failed. Please try again.');
|
|
}
|
|
}
|
|
}
|
|
|
|
InputDecoration _buildInputDecoration(String hint, {IconData? prefixIcon, Widget? suffixIcon}) {
|
|
return InputDecoration(
|
|
hintText: hint,
|
|
prefixIcon: prefixIcon != null ? Icon(prefixIcon, color: Colors.grey.shade500) : null,
|
|
suffixIcon: suffixIcon,
|
|
filled: true,
|
|
fillColor: Colors.white,
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: BorderSide(color: Colors.grey.shade300),
|
|
),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: BorderSide(color: Colors.grey.shade300),
|
|
),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: BorderSide(color: Theme.of(context).primaryColor, width: 2),
|
|
),
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 18),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return GestureDetector(
|
|
onTap: () => FocusScope.of(context).unfocus(),
|
|
child: Scaffold(
|
|
backgroundColor: Colors.grey.shade50,
|
|
appBar: AppBar(
|
|
elevation: 0,
|
|
backgroundColor: Colors.transparent,
|
|
leading: Padding(
|
|
padding: const EdgeInsets.only(left: 16.0),
|
|
child: IconButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
icon: const Icon(LucideIcons.chevronLeft, size: 28),
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
),
|
|
body: SafeArea(
|
|
child: Center(
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(24.0),
|
|
child: ConstrainedBox(
|
|
constraints: const BoxConstraints(maxWidth: 400),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Container(
|
|
width: 80, height: 80,
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context).primaryColor.withOpacity(0.1),
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Center(child: Icon(LucideIcons.shieldCheck, size: 40, color: Theme.of(context).primaryColor)),
|
|
),
|
|
const SizedBox(height: 32),
|
|
Text(
|
|
'Reset Password',
|
|
style: Theme.of(context).textTheme.headlineMedium?.copyWith(fontWeight: FontWeight.bold, color: Colors.black87),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'We\'ve sent a 6-digit code to\n${widget.email}',
|
|
textAlign: TextAlign.center,
|
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: Colors.grey.shade600, height: 1.5),
|
|
),
|
|
const SizedBox(height: 40),
|
|
|
|
// OTP Field
|
|
TextField(
|
|
controller: otpController,
|
|
keyboardType: TextInputType.number,
|
|
textInputAction: TextInputAction.next,
|
|
maxLength: 6,
|
|
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 24, letterSpacing: 16),
|
|
textAlign: TextAlign.center,
|
|
decoration: InputDecoration(
|
|
hintText: '000000',
|
|
hintStyle: TextStyle(color: Colors.grey.shade300, letterSpacing: 16),
|
|
counterText: '',
|
|
filled: true,
|
|
fillColor: Colors.white,
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20),
|
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.grey.shade300)),
|
|
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.grey.shade300)),
|
|
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Theme.of(context).primaryColor, width: 2)),
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
// New Password
|
|
TextField(
|
|
controller: newPasswordController,
|
|
obscureText: _obscurePassword,
|
|
textInputAction: TextInputAction.next,
|
|
style: const TextStyle(fontWeight: FontWeight.w500),
|
|
decoration: _buildInputDecoration(
|
|
'New Password',
|
|
prefixIcon: LucideIcons.lock,
|
|
suffixIcon: IconButton(
|
|
icon: Icon(_obscurePassword ? LucideIcons.eyeOff : LucideIcons.eye, color: Colors.grey.shade500),
|
|
onPressed: () => setState(() => _obscurePassword = !_obscurePassword),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// Confirm Password
|
|
TextField(
|
|
controller: confirmPasswordController,
|
|
obscureText: _obscureConfirm,
|
|
textInputAction: TextInputAction.done,
|
|
onSubmitted: (_) => _resetPassword(),
|
|
style: const TextStyle(fontWeight: FontWeight.w500),
|
|
decoration: _buildInputDecoration(
|
|
'Confirm Password',
|
|
prefixIcon: LucideIcons.lock,
|
|
suffixIcon: IconButton(
|
|
icon: Icon(_obscureConfirm ? LucideIcons.eyeOff : LucideIcons.eye, color: Colors.grey.shade500),
|
|
onPressed: () => setState(() => _obscureConfirm = !_obscureConfirm),
|
|
),
|
|
),
|
|
),
|
|
|
|
AnimatedSize(
|
|
duration: const Duration(milliseconds: 300),
|
|
curve: Curves.easeInOut,
|
|
alignment: Alignment.topCenter,
|
|
child: _errorMessage != null
|
|
? Padding(
|
|
padding: const EdgeInsets.only(top: 16),
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
decoration: BoxDecoration(
|
|
color: Colors.red.shade50,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: Colors.red.shade200),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(LucideIcons.alertCircle, color: Colors.red.shade400, size: 20),
|
|
const SizedBox(width: 12),
|
|
Expanded(child: Text(_errorMessage!, style: TextStyle(color: Colors.red.shade700, fontSize: 14, fontWeight: FontWeight.w500))),
|
|
],
|
|
),
|
|
),
|
|
)
|
|
: const SizedBox.shrink(),
|
|
),
|
|
const SizedBox(height: 40),
|
|
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton(
|
|
onPressed: _isLoading ? null : _resetPassword,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Theme.of(context).primaryColor,
|
|
foregroundColor: Colors.white,
|
|
padding: const EdgeInsets.symmetric(vertical: 20),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
|
elevation: 2,
|
|
),
|
|
child: _isLoading
|
|
? const SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white))
|
|
: const Text('Reset Password', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, letterSpacing: 0.5)),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|