diff --git a/lib/features/auth/screens/login_screen.dart b/lib/features/auth/screens/login_screen.dart index a3eb883..ccad012 100644 --- a/lib/features/auth/screens/login_screen.dart +++ b/lib/features/auth/screens/login_screen.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:kmobile/di/injection.dart'; import 'package:kmobile/features/auth/screens/mpin_screen.dart'; +import 'package:kmobile/features/auth/screens/set_password_screen.dart'; import 'package:kmobile/security/secure_storage.dart'; import '../../../app.dart'; import '../controllers/auth_cubit.dart'; @@ -75,8 +76,10 @@ class LoginScreenState extends State } } else if (state is AuthError) { if (state.message == 'MIGRATED_USER_HAS_NO_PASSWORD') { - Navigator.of(context) - .push(MaterialPageRoute(builder: (_) => const Placeholder())); + Navigator.of(context).push(MaterialPageRoute( + builder: (_) => SetPasswordScreen( + customerNo: _customerNumberController.text.trim(), + ))); } else { ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text(state.message))); diff --git a/lib/features/auth/screens/set_password_screen.dart b/lib/features/auth/screens/set_password_screen.dart new file mode 100644 index 0000000..92be727 --- /dev/null +++ b/lib/features/auth/screens/set_password_screen.dart @@ -0,0 +1,291 @@ +import 'package:flutter/material.dart'; +import 'package:kmobile/api/services/auth_service.dart'; +import 'package:kmobile/di/injection.dart'; +import 'package:kmobile/features/auth/screens/login_screen.dart'; + +class SetPasswordScreen extends StatefulWidget { + final String customerNo; + const SetPasswordScreen({super.key, required this.customerNo}); + + @override + State createState() => _SetPasswordScreenState(); +} + +enum SetPasswordStep { initial, otp, setPassword, success } + +class _SetPasswordScreenState extends State { + final _authService = getIt(); + SetPasswordStep _currentStep = SetPasswordStep.initial; + bool _isLoading = false; + String? _error; + String? _otpToken; + + final _otpController = TextEditingController(); + final _passwordController = TextEditingController(); + final _confirmPasswordController = TextEditingController(); + + bool _obscurePassword = true; + bool _obscureConfirmPassword = true; + + @override + void dispose() { + _otpController.dispose(); + _passwordController.dispose(); + _confirmPasswordController.dispose(); + super.dispose(); + } + + Future _sendOtp() async { + setState(() { + _isLoading = true; + _error = null; + }); + try { + await _authService.sendOtpForSettingPassword(widget.customerNo); + setState(() { + _currentStep = SetPasswordStep.otp; + _isLoading = false; + }); + } catch (e) { + setState(() { + _error = e.toString(); + _isLoading = false; + }); + } + } + + Future _verifyOtp() async { + if (_otpController.text.isEmpty) { + setState(() { + _error = 'Please enter OTP'; + }); + return; + } + setState(() { + _isLoading = true; + _error = null; + }); + try { + final token = await _authService.verifyOtpForSettingPassword( + widget.customerNo, _otpController.text); + setState(() { + _otpToken = token; + _currentStep = SetPasswordStep.setPassword; + _isLoading = false; + }); + } catch (e) { + setState(() { + _error = e.toString(); + _isLoading = false; + }); + } + } + + Future _setPassword() async { + if (_passwordController.text.isEmpty || + _confirmPasswordController.text.isEmpty) { + setState(() { + _error = 'Please fill both password fields'; + }); + return; + } + if (_passwordController.text != _confirmPasswordController.text) { + setState(() { + _error = 'Passwords do not match'; + }); + return; + } + if (_otpToken == null) { + setState(() { + _error = 'OTP token is missing. Please restart the process.'; + _currentStep = SetPasswordStep.initial; + }); + return; + } + + setState(() { + _isLoading = true; + _error = null; + }); + + try { + await _authService.changePassword(_passwordController.text, _otpToken!); + setState(() { + _currentStep = SetPasswordStep.success; + _isLoading = false; + }); + } catch (e) { + setState(() { + _error = e.toString(); + _isLoading = false; + }); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Set New Password'), + ), + body: Padding( + padding: const EdgeInsets.all(24.0), + child: Center( + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (_error != null) ...[ + Text( + _error!, + style: const TextStyle(color: Colors.red), + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + ], + if (_isLoading) + const CircularProgressIndicator() + else + _buildStepContent(), + ], + ), + ), + ), + ), + ); + } + + Widget _buildStepContent() { + switch (_currentStep) { + case SetPasswordStep.initial: + return _buildInitialStep(); + case SetPasswordStep.otp: + return _buildOtpStep(); + case SetPasswordStep.setPassword: + return _buildSetPasswordStep(); + case SetPasswordStep.success: + return _buildSuccessStep(); + } + } + + Widget _buildInitialStep() { + return Column( + children: [ + const Text( + 'You need to set a new password to continue. We will send an OTP to your registered mobile number.', + textAlign: TextAlign.center, + style: TextStyle(fontSize: 16), + ), + const SizedBox(height: 24), + ElevatedButton( + onPressed: _sendOtp, + child: const Text('Proceed'), + ), + ], + ); + } + + Widget _buildOtpStep() { + return Column( + children: [ + const Text( + 'An OTP has been sent to your registered mobile number. Please enter it below.', + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + TextField( + controller: _otpController, + decoration: const InputDecoration( + labelText: 'Enter OTP', + border: OutlineInputBorder(), + ), + keyboardType: TextInputType.number, + ), + const SizedBox(height: 24), + ElevatedButton( + onPressed: _verifyOtp, + child: const Text('Verify OTP'), + ), + ], + ); + } + + Widget _buildSetPasswordStep() { + return Column( + children: [ + TextFormField( + controller: _passwordController, + obscureText: _obscurePassword, + decoration: InputDecoration( + labelText: 'New Password', + border: const OutlineInputBorder(), + suffixIcon: IconButton( + icon: Icon( + _obscurePassword ? Icons.visibility : Icons.visibility_off, + ), + onPressed: () { + setState(() { + _obscurePassword = !_obscurePassword; + }); + }, + ), + ), + ), + const SizedBox(height: 24), + TextFormField( + controller: _confirmPasswordController, + obscureText: _obscureConfirmPassword, + decoration: InputDecoration( + labelText: 'Confirm New Password', + border: const OutlineInputBorder(), + suffixIcon: IconButton( + icon: Icon( + _obscureConfirmPassword + ? Icons.visibility + : Icons.visibility_off, + ), + onPressed: () { + setState(() { + _obscureConfirmPassword = !_obscureConfirmPassword; + }); + }, + ), + ), + ), + const SizedBox(height: 24), + ElevatedButton( + onPressed: _setPassword, + child: const Text('Set Password'), + ), + ], + ); + } + + Widget _buildSuccessStep() { + return Column( + children: [ + const Icon(Icons.check_circle, color: Colors.green, size: 80), + const SizedBox(height: 24), + const Text( + 'Password set successfully!', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + const Text( + 'You can now log in with your new password.', + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + ElevatedButton( + onPressed: () { + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute(builder: (_) => const LoginScreen()), + (route) => false, + ); + }, + child: const Text('Back to Login'), + ), + ], + ); + } +}