card-management
This commit is contained in:
parent
2b2605cecc
commit
98f2270614
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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');
|
||||||
}
|
}
|
||||||
|
@ -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: {
|
||||||
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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'),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -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()));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
|
189
lib/features/card/screens/card_pin_change_details_screen.dart
Normal file
189
lib/features/card/screens/card_pin_change_details_screen.dart
Normal 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'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
148
lib/features/card/screens/card_pin_set_screen.dart
Normal file
148
lib/features/card/screens/card_pin_set_screen.dart
Normal 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'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user