From 98f227061479e51cc83123ae8ce088253a5327b9 Mon Sep 17 00:00:00 2001 From: Trina Bakshi Date: Mon, 19 May 2025 16:30:27 +0530 Subject: [PATCH] card-management --- lib/api/interceptors/auth_interceptor.dart | 2 +- lib/api/services/auth_service.dart | 5 +- lib/di/injection.dart | 2 +- .../auth/models/auth_credentials.dart | 2 +- .../card/screens/block_card_screen.dart | 41 ++-- .../card/screens/card_management_screen.dart | 6 +- .../card_pin_change_details_screen.dart | 189 ++++++++++++++++++ .../card/screens/card_pin_set_screen.dart | 148 ++++++++++++++ 8 files changed, 375 insertions(+), 20 deletions(-) create mode 100644 lib/features/card/screens/card_pin_change_details_screen.dart create mode 100644 lib/features/card/screens/card_pin_set_screen.dart diff --git a/lib/api/interceptors/auth_interceptor.dart b/lib/api/interceptors/auth_interceptor.dart index 56ee5be..53c4824 100644 --- a/lib/api/interceptors/auth_interceptor.dart +++ b/lib/api/interceptors/auth_interceptor.dart @@ -13,7 +13,7 @@ class AuthInterceptor extends Interceptor { RequestInterceptorHandler handler, ) async { // Skip auth header for login and refresh endpoints - if (options.path.contains('/auth/login') || + if (options.path.contains('/login') || options.path.contains('/auth/refresh')) { return handler.next(options); } diff --git a/lib/api/services/auth_service.dart b/lib/api/services/auth_service.dart index 5169306..d2fb6d1 100644 --- a/lib/api/services/auth_service.dart +++ b/lib/api/services/auth_service.dart @@ -20,7 +20,7 @@ class AuthService { Future login(AuthCredentials credentials) async { try { final response = await _dio.post( - '/auth/login', + '/login', data: credentials.toJson(), ); @@ -30,6 +30,9 @@ class AuthService { throw AuthException('Login failed'); } } on DioException catch (e) { + if (kDebugMode) { + print(e.toString()); + } if (e.response?.statusCode == 401) { throw AuthException('Invalid credentials'); } diff --git a/lib/di/injection.dart b/lib/di/injection.dart index bcca63d..c981af3 100644 --- a/lib/di/injection.dart +++ b/lib/di/injection.dart @@ -35,7 +35,7 @@ Future setupDependencies() async { Dio _createDioClient() { final dio = Dio( BaseOptions( - baseUrl: 'https://api.yourbank.com/v1', + baseUrl: 'http://localhost:3000', connectTimeout: const Duration(seconds: 5), receiveTimeout: const Duration(seconds: 3), headers: { diff --git a/lib/features/auth/models/auth_credentials.dart b/lib/features/auth/models/auth_credentials.dart index 1a13bb8..7e63b6a 100644 --- a/lib/features/auth/models/auth_credentials.dart +++ b/lib/features/auth/models/auth_credentials.dart @@ -5,7 +5,7 @@ class AuthCredentials { AuthCredentials({required this.username, required this.password}); Map toJson() => { - 'username': username, + 'customer_no': username, 'password': password, }; } diff --git a/lib/features/card/screens/block_card_screen.dart b/lib/features/card/screens/block_card_screen.dart index 4917151..47b27dc 100644 --- a/lib/features/card/screens/block_card_screen.dart +++ b/lib/features/card/screens/block_card_screen.dart @@ -33,13 +33,20 @@ class _BlockCardScreen extends State{ if (_formKey.currentState?.validate() ?? false) { // Call your backend logic here - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Card has been blocked'), - duration: Duration(seconds: 3), - behavior: SnackBarBehavior.floating, + final snackBar = SnackBar( + content: const Text('Card has been blocked'), + action: SnackBarAction( + label: 'X', + onPressed: () { + // Just close the SnackBar + }, + textColor: Colors.white, ), + backgroundColor: Colors.black, + behavior: SnackBarBehavior.floating, ); + + ScaffoldMessenger.of(context).showSnackBar(snackBar); } } @@ -166,16 +173,22 @@ class _BlockCardScreen extends State{ validator: (value) => value != null && value.length >= 10 ? null : 'Enter valid phone number', ), - const SizedBox(height: 35), - ElevatedButton( - onPressed: _blockCard, - style: ElevatedButton.styleFrom( - shape: const StadiumBorder(), - padding: const EdgeInsets.symmetric(vertical: 16), - backgroundColor: Colors.blue[900], - foregroundColor: Colors.white, + const SizedBox(height: 45), + Align( + alignment: Alignment.center, + child: SizedBox( + width: 250, + child: ElevatedButton( + onPressed: _blockCard, + style: ElevatedButton.styleFrom( + shape: const StadiumBorder(), + padding: const EdgeInsets.symmetric(vertical: 16), + backgroundColor: Colors.blue[900], + foregroundColor: Colors.white, + ), + child: const Text('Block'), + ), ), - child: const Text('Block'), ), ], ), diff --git a/lib/features/card/screens/card_management_screen.dart b/lib/features/card/screens/card_management_screen.dart index 307812c..0687115 100644 --- a/lib/features/card/screens/card_management_screen.dart +++ b/lib/features/card/screens/card_management_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:kmobile/features/card/screens/block_card_screen.dart'; +import 'package:kmobile/features/card/screens/card_pin_change_details_screen.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; class CardManagementScreen extends StatefulWidget { @@ -46,7 +47,7 @@ class _CardManagementScreen extends State{ label: 'Block / Unblock Card', onTap: () { Navigator.push(context, MaterialPageRoute( - builder: (context) => const BlockCardScreen()));; + builder: (context) => const BlockCardScreen())); }, ), @@ -56,7 +57,8 @@ class _CardManagementScreen extends State{ icon: Symbols.password_2, label: 'Change Card PIN', onTap: () { - + Navigator.push(context, MaterialPageRoute( + builder: (context) => const CardPinChangeDetailsScreen())); }, ), diff --git a/lib/features/card/screens/card_pin_change_details_screen.dart b/lib/features/card/screens/card_pin_change_details_screen.dart new file mode 100644 index 0000000..1eb4335 --- /dev/null +++ b/lib/features/card/screens/card_pin_change_details_screen.dart @@ -0,0 +1,189 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:kmobile/features/card/screens/card_pin_set_screen.dart'; +import 'package:material_symbols_icons/material_symbols_icons.dart'; + +class CardPinChangeDetailsScreen extends StatefulWidget { + const CardPinChangeDetailsScreen({super.key}); + + @override + State createState() => _CardPinChangeDetailsScreen(); +} + +class _CardPinChangeDetailsScreen extends State{ + final _formKey = GlobalKey(); + final _cardController = TextEditingController(); + final _cvvController = TextEditingController(); + final _expiryController = TextEditingController(); + final _phoneController = TextEditingController(); + + Future _pickExpiryDate() async { + final now = DateTime.now(); + final selectedDate = await showDatePicker( + context: context, + initialDate: now, + firstDate: now, + lastDate: DateTime(now.year + 10), + ); + if (selectedDate != null) { + _expiryController.text = DateFormat('dd/MM/yyyy').format(selectedDate); + } + } + + void _nextButton() { + if (_formKey.currentState?.validate() ?? false) { + // Call your backend logic here + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (context) => const CardPinSetScreen()), + ); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + leading: IconButton(icon: const Icon(Symbols.arrow_back_ios_new), + onPressed: () { + Navigator.pop(context); + },), + title: const Text('Card Details', style: TextStyle(color: Colors.black, + fontWeight: FontWeight.w500),), + centerTitle: false, + actions: const [ + Padding( + padding: EdgeInsets.only(right: 10.0), + child: CircleAvatar( + backgroundImage: AssetImage('assets/images/avatar.jpg'), // Replace with your image + radius: 20, + ), + ), + ], + ), + + body: Padding( + padding: const EdgeInsets.all(10.0), + child: Form( + key: _formKey, + child: ListView( + children: [ + const SizedBox(height: 10), + TextFormField( + controller: _cardController, + decoration: const InputDecoration( + labelText: 'Card Number', + border: OutlineInputBorder(), + isDense: true, + filled: true, + fillColor: Colors.white, + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.black), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.black, width: 2), + ), + ), + keyboardType: TextInputType.number, + textInputAction: TextInputAction.next, + validator: (value) => + value != null && value.length == 16 ? null : 'Enter valid card number', + ), + const SizedBox(height: 24), + Row( + children: [ + Expanded( + child: TextFormField( + controller: _cvvController, + decoration: const InputDecoration( + labelText: 'CVV', + border: OutlineInputBorder(), + isDense: true, + filled: true, + fillColor: Colors.white, + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.black), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.black, width: 2), + ), + ), + keyboardType: TextInputType.number, + textInputAction: TextInputAction.next, + obscureText: true, + validator: (value) => + value != null && value.length == 3 ? null : 'CVV must be 3 digits', + ), + ), + const SizedBox(width: 16), + Expanded( + child: TextFormField( + controller: _expiryController, + readOnly: true, + onTap: _pickExpiryDate, + decoration: const InputDecoration( + labelText: 'Expiry Date', + suffixIcon: Icon(Icons.calendar_today), + border: OutlineInputBorder(), + isDense: true, + filled: true, + fillColor: Colors.white, + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.black), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.black, width: 2), + ), + ), + validator: (value) => + value != null && value.isNotEmpty ? null : 'Select expiry date', + ), + ), + ], + ), + const SizedBox(height: 24), + TextFormField( + controller: _phoneController, + decoration: const InputDecoration( + labelText: 'Phone', + prefixIcon: Icon(Icons.phone), + border: OutlineInputBorder(), + isDense: true, + filled: true, + fillColor: Colors.white, + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.black), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.black, width: 2), + ), + ), + textInputAction: TextInputAction.done, + keyboardType: TextInputType.phone, + validator: (value) => + value != null && value.length >= 10 ? null : 'Enter valid phone number', + ), + const SizedBox(height: 45), + Align( + alignment: Alignment.center, + child: SizedBox( + width: 250, + child: ElevatedButton( + onPressed: _nextButton, + style: ElevatedButton.styleFrom( + shape: const StadiumBorder(), + padding: const EdgeInsets.symmetric(vertical: 16), + backgroundColor: Colors.blue[900], + foregroundColor: Colors.white, + ), + child: const Text('Next'), + ), + ), + ), + ], + ), + ), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/features/card/screens/card_pin_set_screen.dart b/lib/features/card/screens/card_pin_set_screen.dart new file mode 100644 index 0000000..f31cf5d --- /dev/null +++ b/lib/features/card/screens/card_pin_set_screen.dart @@ -0,0 +1,148 @@ +import 'package:flutter/material.dart'; +import 'package:material_symbols_icons/material_symbols_icons.dart'; + +class CardPinSetScreen extends StatefulWidget { + const CardPinSetScreen({super.key}); + + @override + State createState() => _CardPinSetScreen(); +} + +class _CardPinSetScreen extends State{ + final _formKey = GlobalKey(); + final _pinController = TextEditingController(); + final _confirmPinController = TextEditingController(); + + void _submit() { + if (_formKey.currentState!.validate()) { + // Handle PIN submission logic here + final snackBar = SnackBar( + content: const Text('PIN set successfully'), + action: SnackBarAction( + label: 'X', + onPressed: () { + // Just close the SnackBar + }, + textColor: Colors.white, + ), + backgroundColor: Colors.black, + behavior: SnackBarBehavior.floating, + ); + + ScaffoldMessenger.of(context).showSnackBar(snackBar); + } + } + + @override + void dispose() { + _pinController.dispose(); + _confirmPinController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + leading: IconButton(icon: const Icon(Symbols.arrow_back_ios_new), + onPressed: () { + Navigator.pop(context); + },), + title: const Text('Card PIN', style: TextStyle(color: Colors.black, + fontWeight: FontWeight.w500),), + centerTitle: false, + actions: const [ + Padding( + padding: EdgeInsets.only(right: 10.0), + child: CircleAvatar( + backgroundImage: AssetImage('assets/images/avatar.jpg'), // Replace with your image + radius: 20, + ), + ), + ], + ), + + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Form( + key: _formKey, + child: Column( + children: [ + TextFormField( + controller: _pinController, + obscureText: true, + decoration: const InputDecoration( + labelText: 'Enter new PIN', + border: OutlineInputBorder(), + isDense: true, + filled: true, + fillColor: Colors.white, + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.black), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.black, width: 2), + ), + ), + keyboardType: TextInputType.number, + textInputAction: TextInputAction.next, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter new PIN'; + } + if (value.length < 4) { + return 'PIN must be at least 4 digits'; + } + return null; + }, + ), + const SizedBox(height: 24), + TextFormField( + controller: _confirmPinController, + obscureText: true, + decoration: const InputDecoration( + labelText: 'Enter Again', + border: OutlineInputBorder(), + isDense: true, + filled: true, + fillColor: Colors.white, + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.black), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.black, width: 2), + ), + ), + keyboardType: TextInputType.number, + textInputAction: TextInputAction.done, + validator: (value) { + if (value != _pinController.text) { + return 'PINs do not match'; + } + return null; + }, + ), + const SizedBox(height: 45), + Align( + alignment: Alignment.center, + child: SizedBox( + width: 250, + child: ElevatedButton( + onPressed: _submit, + style: ElevatedButton.styleFrom( + shape: const StadiumBorder(), + padding: const EdgeInsets.symmetric(vertical: 16), + backgroundColor: Colors.blue[900], + foregroundColor: Colors.white, + ), + child: const Text('Submit'), + ), + ), + ), + ], + ), + ), + ), + ); + } +} \ No newline at end of file