239 lines
10 KiB
Dart
239 lines
10 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.');
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return GestureDetector(
|
|
onTap: () => FocusScope.of(context).unfocus(),
|
|
child: Scaffold(
|
|
body: SafeArea(
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const SizedBox(height: 12),
|
|
IconButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
icon: const Icon(Icons.arrow_back_ios_new, size: 20),
|
|
style: IconButton.styleFrom(
|
|
backgroundColor: Colors.grey.shade100,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
),
|
|
),
|
|
const SizedBox(height: 32),
|
|
Center(
|
|
child: Container(
|
|
width: 80,
|
|
height: 80,
|
|
decoration: BoxDecoration(
|
|
gradient: const LinearGradient(
|
|
colors: [Color(0xFF6C63FF), Color(0xFF48C6EF)],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
borderRadius: BorderRadius.circular(24),
|
|
),
|
|
child: const Icon(LucideIcons.shieldCheck, color: Colors.white, size: 36),
|
|
),
|
|
),
|
|
const SizedBox(height: 28),
|
|
const Center(
|
|
child: Text(
|
|
'Reset Password',
|
|
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Center(
|
|
child: Text(
|
|
'We\'ve sent a 6-digit code to\n${widget.email}',
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(fontSize: 15, 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: 20, letterSpacing: 8),
|
|
textAlign: TextAlign.center,
|
|
decoration: InputDecoration(
|
|
hintText: '000000',
|
|
hintStyle: TextStyle(color: Colors.grey.shade300, letterSpacing: 8),
|
|
counterText: '',
|
|
filled: true,
|
|
fillColor: Colors.grey.shade100,
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 18),
|
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none),
|
|
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: const BorderSide(color: Color(0xFF6C63FF), width: 2)),
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
|
|
// New Password
|
|
TextField(
|
|
controller: newPasswordController,
|
|
obscureText: _obscurePassword,
|
|
textInputAction: TextInputAction.next,
|
|
style: const TextStyle(fontWeight: FontWeight.w500),
|
|
decoration: InputDecoration(
|
|
hintText: 'New Password',
|
|
prefixIcon: Icon(LucideIcons.lock, color: Colors.grey.shade500),
|
|
suffixIcon: IconButton(
|
|
icon: Icon(_obscurePassword ? LucideIcons.eyeOff : LucideIcons.eye, color: Colors.grey.shade500),
|
|
onPressed: () => setState(() => _obscurePassword = !_obscurePassword),
|
|
),
|
|
filled: true,
|
|
fillColor: Colors.grey.shade100,
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none),
|
|
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: const BorderSide(color: Color(0xFF6C63FF), width: 2)),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// Confirm Password
|
|
TextField(
|
|
controller: confirmPasswordController,
|
|
obscureText: _obscureConfirm,
|
|
textInputAction: TextInputAction.done,
|
|
onSubmitted: (_) => _resetPassword(),
|
|
style: const TextStyle(fontWeight: FontWeight.w500),
|
|
decoration: InputDecoration(
|
|
hintText: 'Confirm Password',
|
|
prefixIcon: Icon(LucideIcons.lock, color: Colors.grey.shade500),
|
|
suffixIcon: IconButton(
|
|
icon: Icon(_obscureConfirm ? LucideIcons.eyeOff : LucideIcons.eye, color: Colors.grey.shade500),
|
|
onPressed: () => setState(() => _obscureConfirm = !_obscureConfirm),
|
|
),
|
|
filled: true,
|
|
fillColor: Colors.grey.shade100,
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none),
|
|
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: const BorderSide(color: Color(0xFF6C63FF), width: 2)),
|
|
),
|
|
),
|
|
|
|
if (_errorMessage != null) ...[
|
|
const SizedBox(height: 20),
|
|
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: 18),
|
|
const SizedBox(width: 10),
|
|
Expanded(child: Text(_errorMessage!, style: TextStyle(color: Colors.red.shade700, fontSize: 13))),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
const SizedBox(height: 32),
|
|
|
|
SizedBox(
|
|
width: double.infinity,
|
|
height: 56,
|
|
child: ElevatedButton(
|
|
onPressed: _isLoading ? null : _resetPassword,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFF6C63FF),
|
|
foregroundColor: Colors.white,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
|
elevation: 0,
|
|
),
|
|
child: _isLoading
|
|
? const SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white))
|
|
: const Text('Reset Password', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|