diff --git a/lib/api/services/change_password_service.dart b/lib/api/services/change_password_service.dart index f438b34..9573400 100644 --- a/lib/api/services/change_password_service.dart +++ b/lib/api/services/change_password_service.dart @@ -67,4 +67,21 @@ class ChangePasswordService { } return response.toString(); } + + Future validateChangeTpin({ + required String oldTpin, + required String newTpin, + }) async { + final response = await _dio.post( + '/api/auth/change/tpin', + data: { + 'oldTpin': oldTpin, + 'newTpin': newTpin, + }, + ); + if (response.statusCode != 200) { + throw Exception("Wrong OTP"); + } + return response.toString(); + } } diff --git a/lib/features/profile/profile_screen.dart b/lib/features/profile/profile_screen.dart index f9b6fea..82ee5eb 100644 --- a/lib/features/profile/profile_screen.dart +++ b/lib/features/profile/profile_screen.dart @@ -237,7 +237,7 @@ class _ProfileScreenState extends State { // Case 2: TPIN is set Navigator.of(context).push( MaterialPageRoute( - builder: (context) => const ChangeTpinScreen(), + builder: (context) => ChangeTpinScreen(mobileNumber: widget.mobileNumber), ), ); } diff --git a/lib/features/profile/tpin/change_tpin_otp_screen.dart b/lib/features/profile/tpin/change_tpin_otp_screen.dart index 96fa682..6e65b03 100644 --- a/lib/features/profile/tpin/change_tpin_otp_screen.dart +++ b/lib/features/profile/tpin/change_tpin_otp_screen.dart @@ -1,10 +1,20 @@ - + import 'dart:async'; + import 'dart:convert'; import 'package:flutter/material.dart'; - import 'package:kmobile/widgets/pin_input_field.dart'; // Use the new widget + import 'package:kmobile/di/injection.dart'; + import 'package:kmobile/features/dashboard/screens/dashboard_screen.dart'; + import 'package:kmobile/widgets/pin_input_field.dart'; + import '../../../api/services/change_password_service.dart'; class ChangeTpinOtpScreen extends StatefulWidget { final String newTpin; - const ChangeTpinOtpScreen({super.key, required this.newTpin}); + final String mobileNumber; // Receive mobile number + + const ChangeTpinOtpScreen({ + super.key, + required this.newTpin, + required this.mobileNumber, + }); @override State createState() => _ChangeTpinOtpScreenState(); @@ -12,11 +22,62 @@ class _ChangeTpinOtpScreenState extends State { final _otpController = TextEditingController(); + final ChangePasswordService _changePasswordService = getIt(); + bool _isLoading = false; - void _handleVerifyOtp() { - // TODO: Add API call to verify OTP and change TPIN - print('Verifying OTP: ${_otpController.text} for new TPIN: ${widget.newTpin}'); - Navigator.of(context).popUntil((route) => route.isFirst); + void _handleVerifyOtp() async { + if (_otpController.text.length != 6) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Please enter a valid 6-digit OTP')), + ); + return; + } + + setState(() { + _isLoading = true; + }); + + String message = 'An unknown error occurred.'; + bool success = false; + + try { + // 1. Validate the OTP + final responseString = await _changePasswordService.validateOtp( + otp: _otpController.text, + mobileNumber: widget.mobileNumber, + ); + final response = jsonDecode(responseString); + + // 2. Check status and set message + if (response['statusCode'] == 200 || response['statusCode'] == 2000) { + message = 'TPIN changed successfully!'; + success = true; + } else { + message = response['message'] ?? 'Invalid OTP. Please try again.'; + } + } catch (e) { + message = 'Failed to verify OTP: $e'; + } finally { + // 3. Show feedback + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + backgroundColor: success ? Colors.green : Colors.red, + ), + ); + + // 4. Navigate to dashboard after 5 seconds + Timer(const Duration(seconds: 5), () { + if (mounted) { + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute(builder: (context) => const DashboardScreen()), + (Route route) => false, + ); + } + }); + } + } } @override @@ -41,9 +102,21 @@ controller: _otpController, ), const SizedBox(height: 32), - ElevatedButton( - onPressed: _handleVerifyOtp, - child: const Text('Verify & Change TPIN'), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: _isLoading ? null : _handleVerifyOtp, + child: _isLoading + ? const SizedBox( + height: 24, + width: 24, + child: CircularProgressIndicator( + color: Colors.white, + strokeWidth: 2.5, + ), + ) + : const Text('Verify & Change TPIN'), + ), ), ], ), diff --git a/lib/features/profile/tpin/change_tpin_screen.dart b/lib/features/profile/tpin/change_tpin_screen.dart index 912b5e2..4ecf038 100644 --- a/lib/features/profile/tpin/change_tpin_screen.dart +++ b/lib/features/profile/tpin/change_tpin_screen.dart @@ -1,9 +1,13 @@ import 'package:flutter/material.dart'; + import 'package:kmobile/di/injection.dart'; import 'package:kmobile/features/profile/tpin/change_tpin_otp_screen.dart'; - import 'package:kmobile/widgets/pin_input_field.dart'; // Use the new widget + import 'package:kmobile/widgets/pin_input_field.dart'; + import '../../../api/services/change_password_service.dart'; + import 'dart:convert'; class ChangeTpinScreen extends StatefulWidget { - const ChangeTpinScreen({super.key}); + final String mobileNumber; + const ChangeTpinScreen({super.key, required this.mobileNumber}); @override State createState() => _ChangeTpinScreenState(); @@ -14,6 +18,8 @@ final _oldTpinController = TextEditingController(); final _newTpinController = TextEditingController(); final _confirmTpinController = TextEditingController(); + final ChangePasswordService _changePasswordService = getIt(); + bool _isLoading = false; @override void dispose() { @@ -23,16 +29,58 @@ super.dispose(); } - void _handleChangeTpin() { + void _handleChangeTpin() async { if (_formKey.currentState!.validate()) { - // TODO: Add API call to request OTP for TPIN change - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => ChangeTpinOtpScreen( - newTpin: _newTpinController.text, - ), - ), - ); + setState(() { + _isLoading = true; + }); + try { + // 1. Validate the current TPIN and new TPIN + final responseString = await _changePasswordService.validateChangeTpin( + oldTpin: _oldTpinController.text, + newTpin: _newTpinController.text, + ); + + // The service throws an exception for non-200 HTTP status, + // so we assume HTTP 200 here and check the body. + final response = jsonDecode(responseString); + + // 2. Check the status code from the response body + if (response.statusCode == 200) { + // 3. Get OTP for TPIN change + await _changePasswordService.getOtpTpin(mobileNumber: '8981274001'); + + // 4. Navigate to OTP screen + if (mounted) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => ChangeTpinOtpScreen( + newTpin: _newTpinController.text, + mobileNumber: '8981274001', // Pass mobile number + ), + ), + ); + } + } else { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(response['message'] ?? 'Invalid TPIN details') + )); + } + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('An error occurred: $e')), + ); + } + } finally { + if (mounted) { + setState(() { + _isLoading = false; + }); + } + } } } @@ -69,6 +117,9 @@ if (value == null || value.length != 6) { return 'Please enter a 6-digit new TPIN'; } + if (value == _oldTpinController.text) { + return 'New TPIN must be different from the old one.'; + } return null; }, ), @@ -88,9 +139,21 @@ }, ), const SizedBox(height: 32), - ElevatedButton( - onPressed: _handleChangeTpin, - child: const Text('Proceed'), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: _isLoading ? null : _handleChangeTpin, + child: _isLoading + ? const SizedBox( + height: 24, + width: 24, + child: CircularProgressIndicator( + color: Colors.white, + strokeWidth: 2.5, + ), + ) + : const Text('Proceed'), + ), ), ], ),