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,
) 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);
}

View File

@ -20,7 +20,7 @@ class AuthService {
Future<AuthToken> 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');
}

View File

@ -35,7 +35,7 @@ Future<void> 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: {

View File

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

View File

@ -33,13 +33,20 @@ class _BlockCardScreen extends State<BlockCardScreen>{
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,8 +173,12 @@ class _BlockCardScreen extends State<BlockCardScreen>{
validator: (value) =>
value != null && value.length >= 10 ? null : 'Enter valid phone number',
),
const SizedBox(height: 35),
ElevatedButton(
const SizedBox(height: 45),
Align(
alignment: Alignment.center,
child: SizedBox(
width: 250,
child: ElevatedButton(
onPressed: _blockCard,
style: ElevatedButton.styleFrom(
shape: const StadiumBorder(),
@ -177,6 +188,8 @@ class _BlockCardScreen extends State<BlockCardScreen>{
),
child: const Text('Block'),
),
),
),
],
),
),

View File

@ -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<CardManagementScreen>{
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<CardManagementScreen>{
icon: Symbols.password_2,
label: 'Change Card PIN',
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'),
),
),
),
],
),
),
),
);
}
}