Change TPIn #3

This commit is contained in:
2025-11-08 16:56:54 +05:30
parent 87fd36b748
commit c26cc507a1
4 changed files with 178 additions and 25 deletions

View File

@@ -67,4 +67,21 @@ class ChangePasswordService {
} }
return response.toString(); 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();
}
} }

View File

@@ -237,7 +237,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
// Case 2: TPIN is set // Case 2: TPIN is set
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => const ChangeTpinScreen(), builder: (context) => ChangeTpinScreen(mobileNumber: widget.mobileNumber),
), ),
); );
} }

View File

@@ -1,10 +1,20 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart'; 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 { class ChangeTpinOtpScreen extends StatefulWidget {
final String newTpin; 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 @override
State<ChangeTpinOtpScreen> createState() => _ChangeTpinOtpScreenState(); State<ChangeTpinOtpScreen> createState() => _ChangeTpinOtpScreenState();
@@ -12,11 +22,62 @@
class _ChangeTpinOtpScreenState extends State<ChangeTpinOtpScreen> { class _ChangeTpinOtpScreenState extends State<ChangeTpinOtpScreen> {
final _otpController = TextEditingController(); final _otpController = TextEditingController();
final ChangePasswordService _changePasswordService = getIt<ChangePasswordService>();
bool _isLoading = false;
void _handleVerifyOtp() { void _handleVerifyOtp() async {
// TODO: Add API call to verify OTP and change TPIN if (_otpController.text.length != 6) {
print('Verifying OTP: ${_otpController.text} for new TPIN: ${widget.newTpin}'); ScaffoldMessenger.of(context).showSnackBar(
Navigator.of(context).popUntil((route) => route.isFirst); 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<dynamic> route) => false,
);
}
});
}
}
} }
@override @override
@@ -41,9 +102,21 @@
controller: _otpController, controller: _otpController,
), ),
const SizedBox(height: 32), const SizedBox(height: 32),
ElevatedButton( SizedBox(
onPressed: _handleVerifyOtp, width: double.infinity,
child: const Text('Verify & Change TPIN'), 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'),
),
), ),
], ],
), ),

View File

@@ -1,9 +1,13 @@
import 'package:flutter/material.dart'; 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/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 { class ChangeTpinScreen extends StatefulWidget {
const ChangeTpinScreen({super.key}); final String mobileNumber;
const ChangeTpinScreen({super.key, required this.mobileNumber});
@override @override
State<ChangeTpinScreen> createState() => _ChangeTpinScreenState(); State<ChangeTpinScreen> createState() => _ChangeTpinScreenState();
@@ -14,6 +18,8 @@
final _oldTpinController = TextEditingController(); final _oldTpinController = TextEditingController();
final _newTpinController = TextEditingController(); final _newTpinController = TextEditingController();
final _confirmTpinController = TextEditingController(); final _confirmTpinController = TextEditingController();
final ChangePasswordService _changePasswordService = getIt<ChangePasswordService>();
bool _isLoading = false;
@override @override
void dispose() { void dispose() {
@@ -23,16 +29,58 @@
super.dispose(); super.dispose();
} }
void _handleChangeTpin() { void _handleChangeTpin() async {
if (_formKey.currentState!.validate()) { if (_formKey.currentState!.validate()) {
// TODO: Add API call to request OTP for TPIN change setState(() {
Navigator.of(context).push( _isLoading = true;
MaterialPageRoute( });
builder: (context) => ChangeTpinOtpScreen( try {
newTpin: _newTpinController.text, // 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) { if (value == null || value.length != 6) {
return 'Please enter a 6-digit new TPIN'; return 'Please enter a 6-digit new TPIN';
} }
if (value == _oldTpinController.text) {
return 'New TPIN must be different from the old one.';
}
return null; return null;
}, },
), ),
@@ -88,9 +139,21 @@
}, },
), ),
const SizedBox(height: 32), const SizedBox(height: 32),
ElevatedButton( SizedBox(
onPressed: _handleChangeTpin, width: double.infinity,
child: const Text('Proceed'), 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'),
),
), ),
], ],
), ),