card-management

This commit is contained in:
Trina Bakshi 2025-05-19 16:30:27 +05:30
parent 2b2605cecc
commit 98f2270614
8 changed files with 375 additions and 20 deletions

View File

@ -13,7 +13,7 @@ class AuthInterceptor extends Interceptor {
RequestInterceptorHandler handler, RequestInterceptorHandler handler,
) async { ) async {
// Skip auth header for login and refresh endpoints // Skip auth header for login and refresh endpoints
if (options.path.contains('/auth/login') || if (options.path.contains('/login') ||
options.path.contains('/auth/refresh')) { options.path.contains('/auth/refresh')) {
return handler.next(options); return handler.next(options);
} }

View File

@ -20,7 +20,7 @@ class AuthService {
Future<AuthToken> login(AuthCredentials credentials) async { Future<AuthToken> login(AuthCredentials credentials) async {
try { try {
final response = await _dio.post( final response = await _dio.post(
'/auth/login', '/login',
data: credentials.toJson(), data: credentials.toJson(),
); );
@ -30,6 +30,9 @@ class AuthService {
throw AuthException('Login failed'); throw AuthException('Login failed');
} }
} on DioException catch (e) { } on DioException catch (e) {
if (kDebugMode) {
print(e.toString());
}
if (e.response?.statusCode == 401) { if (e.response?.statusCode == 401) {
throw AuthException('Invalid credentials'); throw AuthException('Invalid credentials');
} }

View File

@ -35,7 +35,7 @@ Future<void> setupDependencies() async {
Dio _createDioClient() { Dio _createDioClient() {
final dio = Dio( final dio = Dio(
BaseOptions( BaseOptions(
baseUrl: 'https://api.yourbank.com/v1', baseUrl: 'http://localhost:3000',
connectTimeout: const Duration(seconds: 5), connectTimeout: const Duration(seconds: 5),
receiveTimeout: const Duration(seconds: 3), receiveTimeout: const Duration(seconds: 3),
headers: { headers: {

View File

@ -5,7 +5,7 @@ class AuthCredentials {
AuthCredentials({required this.username, required this.password}); AuthCredentials({required this.username, required this.password});
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
'username': username, 'customer_no': username,
'password': password, 'password': password,
}; };
} }

View File

@ -33,13 +33,20 @@ class _BlockCardScreen extends State<BlockCardScreen>{
if (_formKey.currentState?.validate() ?? false) { if (_formKey.currentState?.validate() ?? false) {
// Call your backend logic here // Call your backend logic here
ScaffoldMessenger.of(context).showSnackBar( final snackBar = SnackBar(
const SnackBar( content: const Text('Card has been blocked'),
content: Text('Card has been blocked'), action: SnackBarAction(
duration: Duration(seconds: 3), label: 'X',
behavior: SnackBarBehavior.floating, 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<BlockCardScreen>{
validator: (value) => validator: (value) =>
value != null && value.length >= 10 ? null : 'Enter valid phone number', value != null && value.length >= 10 ? null : 'Enter valid phone number',
), ),
const SizedBox(height: 35), const SizedBox(height: 45),
ElevatedButton( Align(
onPressed: _blockCard, alignment: Alignment.center,
style: ElevatedButton.styleFrom( child: SizedBox(
shape: const StadiumBorder(), width: 250,
padding: const EdgeInsets.symmetric(vertical: 16), child: ElevatedButton(
backgroundColor: Colors.blue[900], onPressed: _blockCard,
foregroundColor: Colors.white, 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'),
), ),
], ],
), ),

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:kmobile/features/card/screens/block_card_screen.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'; import 'package:material_symbols_icons/material_symbols_icons.dart';
class CardManagementScreen extends StatefulWidget { class CardManagementScreen extends StatefulWidget {
@ -46,7 +47,7 @@ class _CardManagementScreen extends State<CardManagementScreen>{
label: 'Block / Unblock Card', label: 'Block / Unblock Card',
onTap: () { onTap: () {
Navigator.push(context, MaterialPageRoute( Navigator.push(context, MaterialPageRoute(
builder: (context) => const BlockCardScreen()));; builder: (context) => const BlockCardScreen()));
}, },
), ),
@ -56,7 +57,8 @@ class _CardManagementScreen extends State<CardManagementScreen>{
icon: Symbols.password_2, icon: Symbols.password_2,
label: 'Change Card PIN', label: 'Change Card PIN',
onTap: () { onTap: () {
Navigator.push(context, MaterialPageRoute(
builder: (context) => const CardPinChangeDetailsScreen()));
}, },
), ),

View File

@ -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<CardPinChangeDetailsScreen> createState() => _CardPinChangeDetailsScreen();
}
class _CardPinChangeDetailsScreen extends State<CardPinChangeDetailsScreen>{
final _formKey = GlobalKey<FormState>();
final _cardController = TextEditingController();
final _cvvController = TextEditingController();
final _expiryController = TextEditingController();
final _phoneController = TextEditingController();
Future<void> _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'),
),
),
),
],
),
),
),
);
}
}

View File

@ -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<CardPinSetScreen> createState() => _CardPinSetScreen();
}
class _CardPinSetScreen extends State<CardPinSetScreen>{
final _formKey = GlobalKey<FormState>();
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'),
),
),
),
],
),
),
),
);
}
}