This commit is contained in:
Surendiran 2026-03-16 15:00:41 +05:30
commit f1f3bcd1b6
18 changed files with 1733 additions and 1000 deletions

View File

@ -90,7 +90,7 @@ class _SplashScreenState extends State<SplashScreen> {
Future<String?> tokenRedirectLogic(
BuildContext context, GoRouterState state) async {
print('ABCDEFGH');
const allowedWithoutToken = [
const guestRoutes = [
'/login',
'/verify',
'/mailVerify',
@ -98,17 +98,17 @@ Future<String?> tokenRedirectLogic(
'/splash',
];
final location = state.uri.toString();
// Allow login, verify, splash without token
if (allowedWithoutToken.contains(location)) return null;
// Check if token exists
// final hasToken = await TokenService.hasValidToken();
// if (!hasToken) return '/login';
final hasToken = await TokenService.hasValidToken();
print('hasToken : $hasToken');
if (!hasToken) {
final location = state.matchedLocation;
// If user is already logged in, never allow returning to OTP/login screens.
if (hasToken && guestRoutes.contains(location)) {
return '/home';
}
// If not logged in and trying to access a protected route, force login.
if (!hasToken && !guestRoutes.contains(location)) {
print('!!!!hasToken : $hasToken');
await SessionManager().clear();
// Show toast once

View File

@ -28,72 +28,84 @@ class changesPassword extends StatefulWidget {
}
class _changesPasswordState extends State<changesPassword> {
final TextEditingController oldPasswordController = TextEditingController();
final TextEditingController newPasswordController = TextEditingController();
final TextEditingController confirmPasswordController = TextEditingController();
final _formKey = GlobalKey<FormState>();
dynamic _preToken;
dynamic _postToken;
dynamic clientName;
dynamic clientLogo;
final TextEditingController oldPasswordController = TextEditingController();
final TextEditingController newPasswordController = TextEditingController();
final TextEditingController confirmPasswordController = TextEditingController();
final _formKey = GlobalKey<FormState>();
dynamic _preToken;
dynamic _postToken;
dynamic clientName;
dynamic clientLogo;
bool _isLoading = false;
bool _obscureOldPassword = true;
bool _obscureNewPassword = true;
bool _obscureConfirmPassword = true;
late SessionManager session;
bool hasMinLength = false;
bool hasUpperLower = false;
bool hasNumber = false;
bool hasSpecialChar = false;
bool get isPasswordValid => hasMinLength && hasUpperLower && hasNumber && hasSpecialChar;
bool hasMinLength = false;
bool hasUpperLower = false;
bool hasNumber = false;
bool hasSpecialChar = false;
bool get isPasswordValid => hasMinLength && hasUpperLower && hasNumber && hasSpecialChar;
@override
void initState() {
super.initState();
}
void validatePassword(String password) {
setState(() {
hasMinLength = password.length >= 8;
hasUpperLower = RegExp(r'(?=.*[A-Za-z])').hasMatch(password);
hasNumber = RegExp(r'(?=.*\d)').hasMatch(password);
hasSpecialChar = RegExp(r'(?=.*[@$!%*#?&])').hasMatch(password);
});
}
void validatePassword(String password) {
setState(() {
hasMinLength = password.length >= 8;
hasUpperLower = RegExp(r'(?=.*[A-Za-z])').hasMatch(password);
hasNumber = RegExp(r'(?=.*\d)').hasMatch(password);
hasSpecialChar = RegExp(r'(?=.*[@$!%*#?&])').hasMatch(password);
});
}
Future<void> resetYourPassword() async {
final oldpassword = oldPasswordController.text.trim();
final newPassword = newPasswordController.text.trim();
final confirmPassword = confirmPasswordController.text.trim();
try {
if (_formKey.currentState!.validate()) {
if (confirmPassword != newPassword) {
ToastHelper.showErrorToast(context, 'Passwords do not match');
return;
}
setState(() {
_isLoading = true;
});
final oldpassword = oldPasswordController.text.trim();
final newPassword = newPasswordController.text.trim();
final confirmPassword = confirmPasswordController.text.trim();
try {
if (_formKey.currentState!.validate()) {
// Extra safety checks before calling the API
if (oldpassword == newPassword) {
ToastHelper.showErrorToast(
context, 'New password cannot be same as old password');
return;
}
if (!isPasswordValid) {
ToastHelper.showErrorToast(
context,
'Password must be 8+ characters with 1 letter, 1 number, and 1 special character');
return;
}
if (confirmPassword != newPassword) {
ToastHelper.showErrorToast(context, 'Passwords do not match');
return;
}
setState(() {
_isLoading = true;
});
// Determine the API and the payload based on the visible field
String apiEndpoint = Environment.apiUrlEnrollment + 'changePassword';
Map<String, dynamic> payload = {
'email_id': widget.email,
'client_id': widget.client_id,
'old_password': oldpassword,
'new_password': newPassword,
'confirm_password': confirmPassword
};
// Determine the API and the payload based on the visible field
String apiEndpoint = Environment.apiUrlEnrollment + 'changePassword';
Map<String, dynamic> payload = {
'email_id': widget.email,
'client_id': widget.client_id,
'old_password': oldpassword,
'new_password': newPassword,
'confirm_password': confirmPassword
};
// var enteredMobileNumber = mobileController.text;
final response = await http.post(
Uri.parse(apiEndpoint),
body: json.encode(payload),
headers: {
HttpHeaders.contentTypeHeader: 'application/json',
'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y',
},
);
// var enteredMobileNumber = mobileController.text;
final response = await http.post(
Uri.parse(apiEndpoint),
body: json.encode(payload),
headers: {
HttpHeaders.contentTypeHeader: 'application/json',
'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y',
},
);
if (response.statusCode == 200) {
Map<String, dynamic> data = json.decode(response.body);
@ -163,26 +175,26 @@ class _changesPasswordState extends State<changesPassword> {
print('Error: $e');
}
// 🔹 Password validation
// if (password.isEmpty) {
// ToastHelper.showErrorToast(context, 'Please enter your password');
// return;
// }
// if (password.length < 6) {
// ToastHelper.showErrorToast(context, 'Password must be at least 6 characters');
// return;
// }
// if (!RegExp(r'^(?=.*[A-Z])(?=.*[0-9]).{6,}$').hasMatch(password)) {
// ToastHelper.showErrorToast(context, 'Include at least 1 uppercase letter and 1 number');
// return;
// }
//
// // 🔹 Confirm password validation
// if (confirmPassword.isEmpty) {
// ToastHelper.showErrorToast(context, 'Please confirm your password');
// return;
// }
}
// 🔹 Password validation
// if (password.isEmpty) {
// ToastHelper.showErrorToast(context, 'Please enter your password');
// return;
// }
// if (password.length < 6) {
// ToastHelper.showErrorToast(context, 'Password must be at least 6 characters');
// return;
// }
// if (!RegExp(r'^(?=.*[A-Z])(?=.*[0-9]).{6,}$').hasMatch(password)) {
// ToastHelper.showErrorToast(context, 'Include at least 1 uppercase letter and 1 number');
// return;
// }
//
// // 🔹 Confirm password validation
// if (confirmPassword.isEmpty) {
// ToastHelper.showErrorToast(context, 'Please confirm your password');
// return;
// }
}
//Ends Login with UserName and Password
@ -227,7 +239,7 @@ class _changesPasswordState extends State<changesPassword> {
body: SingleChildScrollView(
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
child: Container(
height: _size.height,
constraints: BoxConstraints(minHeight: _size.height),
color: Colors.white,
child: Stack(
children: [
@ -314,7 +326,7 @@ class _changesPasswordState extends State<changesPassword> {
// ),
Container(
margin: marginInsets,
alignment: Alignment.bottomCenter,
alignment: Alignment.topCenter,
child: SingleChildScrollView(
child: Form(
key: _formKey,
@ -332,62 +344,37 @@ class _changesPasswordState extends State<changesPassword> {
mainAxisAlignment:
MainAxisAlignment.center,
children: [
if (!Responsive.isMobile(context) &&
!Responsive.isTablet(context))
// Desktop-only: center "<" and logo in one row
if (Responsive.isDesktop(context))
Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
InkWell(
onTap: () {
context.go('/home');
},
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
// if (!Responsive.isDesktop(context))
Icon(
Icons.chevron_left,
color: Color(0xFF000000),
size: 30,
),
SizedBox(
width: Responsive.isDesktop(context)
? 0
: 5),
],
),
),
Expanded(
flex: 12,
child: Align(
alignment: Alignment
.topLeft, // Always top-left
child: _size.width <= 1100
? Image.asset(
'assets/nhance_app_logo.png',
width: 150,
height: 150,
)
: _size.width > 1100
? Image.asset(
'assets/nhance_app_logo.png',
width: 150,
height: 150,
)
: Image.asset(
'assets/nhance_app_logo.png',
width: 150,
height: 150,
),
onTap: () {
context.go('/home');
},
child: const Icon(
Icons.chevron_left,
color: Color(0xFF000000),
size: 30,
),
),
const SizedBox(width: 8),
Image.asset(
'assets/nhance_app_logo.png',
width: 150,
height: 150,
// color: Colors.green,
),
],
),
SizedBox(
height: Responsive.isDesktop(context)
? _size.height * 0.1
? null
: 10,
),
SizedBox(height: 10),
// SizedBox(height: 10),
Container(
margin: Responsive.isDesktop(context)
? EdgeInsets.symmetric(
@ -437,259 +424,278 @@ class _changesPasswordState extends State<changesPassword> {
height: 20,
),
Column(
children: [
// 🔹 Password Field
Container(
height: 55,
margin: Responsive.isDesktop(
context)
? const EdgeInsets
Column(
children: [
// 🔹 Password Field
Container(
height: 55,
margin: Responsive.isDesktop(
context)
? const EdgeInsets
.symmetric(
horizontal: 150)
: const EdgeInsets
.symmetric(
horizontal: 0),
decoration: BoxDecoration(
border: Border.all(
width: 1,
color: Colors.grey),
borderRadius:
BorderRadius.circular(
10),
),
child: TextFormField(
controller:
oldPasswordController,
obscureText:
_obscureOldPassword,
textAlignVertical:
TextAlignVertical
.center,
decoration: InputDecoration(
border: InputBorder.none,
hintText:
"Old Password",
contentPadding:
const EdgeInsets
.symmetric(
horizontal: 150)
: const EdgeInsets
.symmetric(
horizontal: 0),
decoration: BoxDecoration(
border: Border.all(
width: 1,
color: Colors.grey),
borderRadius:
BorderRadius.circular(
10),
),
child: TextFormField(
controller:
oldPasswordController,
obscureText:
_obscureOldPassword,
textAlignVertical:
TextAlignVertical
.center,
decoration: InputDecoration(
border: InputBorder.none,
hintText:
"Old Password",
contentPadding:
const EdgeInsets
.symmetric(
horizontal: 10),
suffixIcon: IconButton(
icon: Icon(
_obscureOldPassword
? Icons
.visibility_off
: Icons
.visibility,
color: Colors.grey,
),
onPressed: () {
setState(() {
_obscureOldPassword =
!_obscureOldPassword;
});
},
),
horizontal: 10),
suffixIcon: IconButton(
icon: Icon(
_obscureOldPassword
? Icons
.visibility_off
: Icons
.visibility,
color: Colors.grey,
),
validator: (value) {
if (value == null ||
value.isEmpty) {
return 'Please enter your old password';
}
if (value.length < 6) {
return 'Password must be at least 6 characters';
}
if (!RegExp(
r'^(?=.*[A-Z])(?=.*[0-9]).{6,}$')
.hasMatch(value)) {
return 'Include at least 1 uppercase letter and 1 number';
}
return null;
onPressed: () {
setState(() {
_obscureOldPassword =
!_obscureOldPassword;
});
},
),
),
const SizedBox(height: 10),
Container(
height: 55,
margin: Responsive.isDesktop(context)
? const EdgeInsets.symmetric(horizontal: 150)
: const EdgeInsets.symmetric(horizontal: 0),
decoration: BoxDecoration(
border: Border.all(width: 1, color: Colors.grey),
borderRadius: BorderRadius.circular(10),
),
child: TextFormField(
controller: newPasswordController,
obscureText: _obscureNewPassword,
onChanged: validatePassword,
textAlignVertical: TextAlignVertical.center,
decoration: InputDecoration(
border: InputBorder.none,
hintText: "New Password",
contentPadding: const EdgeInsets.symmetric(horizontal: 10),
suffixIcon: IconButton(
icon: Icon(
_obscureNewPassword ? Icons.visibility_off : Icons.visibility,
color: Colors.grey,
),
onPressed: () {
setState(() {
_obscureNewPassword = !_obscureNewPassword;
});
},
),
validator: (value) {
if (value == null ||
value.isEmpty) {
return 'Please enter your old password';
}
if (value.length < 8) {
return 'Password must be at least 8 characters';
}
if (!RegExp(
r'^(?=.*[A-Z])(?=.*[0-9]).{6,}$')
.hasMatch(value)) {
return 'Include at least 1 uppercase letter and 1 number';
}
return null;
},
),
),
const SizedBox(height: 10),
Container(
height: 55,
margin: Responsive.isDesktop(context)
? const EdgeInsets.symmetric(horizontal: 150)
: const EdgeInsets.symmetric(horizontal: 0),
decoration: BoxDecoration(
border: Border.all(width: 1, color: Colors.grey),
borderRadius: BorderRadius.circular(10),
),
child: TextFormField(
controller: newPasswordController,
obscureText: _obscureNewPassword,
onChanged: validatePassword,
textAlignVertical: TextAlignVertical.center,
decoration: InputDecoration(
border: InputBorder.none,
hintText: "New Password",
contentPadding: const EdgeInsets.symmetric(horizontal: 10),
suffixIcon: IconButton(
icon: Icon(
_obscureNewPassword ? Icons.visibility_off : Icons.visibility,
color: Colors.grey,
),
onPressed: () {
setState(() {
_obscureNewPassword = !_obscureNewPassword;
});
},
),
),
const SizedBox(height: 8),
// 🔹 VALIDATION LIST
Padding(
padding: Responsive.isDesktop(context)
? const EdgeInsets.symmetric(horizontal: 150)
: const EdgeInsets.symmetric(horizontal: 5),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
validator: (value) {
final password = value ?? '';
if (password.isEmpty) {
return 'Please enter your new password';
}
if (password.length < 8) {
return 'Password must be at least 8 characters';
}
if (!RegExp(r'[A-Za-z]').hasMatch(password)) {
return 'Password must include at least 1 letter';
}
if (!RegExp(r'\d').hasMatch(password)) {
return 'Password must include at least 1 number';
}
if (!RegExp(r'[@$!%*#?&]').hasMatch(password)) {
return 'Password must include at least 1 special character';
}
return null;
},
),
),
const SizedBox(height: 8),
// 🔹 VALIDATION LIST
Padding(
padding: Responsive.isDesktop(context)
? const EdgeInsets.symmetric(horizontal: 150)
: const EdgeInsets.symmetric(horizontal: 5),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Row(
children: [
Expanded(child: _buildCheckItem(hasMinLength, "Minimum 8 characters")),
Expanded(child: _buildCheckItem(hasSpecialChar, "1 special character")),
],
),
Row(
children: [
Expanded(child: _buildCheckItem(hasUpperLower, "1 UPPER or lower case")),
Expanded(child: _buildCheckItem(hasNumber, "1 numerical")),
],
),
Expanded(child: _buildCheckItem(hasMinLength, "Minimum 8 characters")),
Expanded(child: _buildCheckItem(hasSpecialChar, "1 special character")),
],
),
),
const SizedBox(height: 10),
Container(
height: 55,
margin: Responsive.isDesktop(
context)
? const EdgeInsets
.symmetric(
horizontal: 150)
: const EdgeInsets
.symmetric(
horizontal: 0),
decoration: BoxDecoration(
border: Border.all(
width: 1,
color: Colors.grey),
borderRadius:
BorderRadius.circular(
10),
Row(
children: [
Expanded(child: _buildCheckItem(hasUpperLower, "1 UPPER or lower case")),
Expanded(child: _buildCheckItem(hasNumber, "1 numerical")),
],
),
child: TextFormField(
controller:
confirmPasswordController,
obscureText:
_obscureConfirmPassword,
textAlignVertical:
TextAlignVertical
.center,
decoration: InputDecoration(
border: InputBorder.none,
hintText:
"Confirm Password",
contentPadding:
const EdgeInsets
.symmetric(
horizontal: 10),
suffixIcon: IconButton(
icon: Icon(
_obscureConfirmPassword
? Icons
.visibility_off
: Icons
.visibility,
color: Colors.grey,
),
onPressed: () {
setState(() {
_obscureConfirmPassword =
!_obscureConfirmPassword;
});
},
),
],
),
),
const SizedBox(height: 10),
Container(
height: 55,
margin: Responsive.isDesktop(
context)
? const EdgeInsets
.symmetric(
horizontal: 150)
: const EdgeInsets
.symmetric(
horizontal: 0),
decoration: BoxDecoration(
border: Border.all(
width: 1,
color: Colors.grey),
borderRadius:
BorderRadius.circular(
10),
),
child: TextFormField(
controller:
confirmPasswordController,
obscureText:
_obscureConfirmPassword,
textAlignVertical:
TextAlignVertical
.center,
decoration: InputDecoration(
border: InputBorder.none,
hintText:
"Confirm Password",
contentPadding:
const EdgeInsets
.symmetric(
horizontal: 10),
suffixIcon: IconButton(
icon: Icon(
_obscureConfirmPassword
? Icons
.visibility_off
: Icons
.visibility,
color: Colors.grey,
),
validator: (value) {
if (value == null ||
value.isEmpty) {
return 'Please confirm your password';
}
if (value !=
newPasswordController
.text) {
return 'Passwords do not match';
}
return null;
onPressed: () {
setState(() {
_obscureConfirmPassword =
!_obscureConfirmPassword;
});
},
),
),
],
validator: (value) {
if (value == null ||
value.isEmpty) {
return 'Please confirm your password';
}
if (value !=
newPasswordController
.text) {
return 'Passwords do not match';
}
return null;
},
),
),
SizedBox(height: 15),
Container(
margin:
Responsive.isDesktop(context)
? EdgeInsets.symmetric(
horizontal: 150)
: EdgeInsets.symmetric(
horizontal: 0),
child: SizedBox(
width: double.infinity,
height: 40,
child: ElevatedButton(
style:
ElevatedButton.styleFrom(
backgroundColor:
Color(0xFF00989E),
shape:
RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(
10),
),
),
onPressed: _isLoading
? null
: resetYourPassword,
],
),
SizedBox(height: 15),
Container(
margin:
Responsive.isDesktop(context)
? EdgeInsets.symmetric(
horizontal: 150)
: EdgeInsets.symmetric(
horizontal: 0),
child: SizedBox(
width: double.infinity,
height: 40,
child: ElevatedButton(
style:
ElevatedButton.styleFrom(
backgroundColor:
Color(0xFF00989E),
shape:
RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(
10),
),
),
onPressed: _isLoading
? null
: resetYourPassword,
child: _isLoading
? CircularProgressIndicator(
valueColor:
AlwaysStoppedAnimation<
Color>(
Color(0xFF00989E),
),
)
: Text(
"Reset Password",
style: GoogleFonts
.poppins(
color: Color(
0xFFFFFFFF),
),
),
child: _isLoading
? CircularProgressIndicator(
valueColor:
AlwaysStoppedAnimation<
Color>(
Color(0xFF00989E),
),
)
: Text(
"Reset Password",
style: GoogleFonts
.poppins(
color: Color(
0xFFFFFFFF),
),
),
),
),
),
SizedBox(
height: Responsive.isDesktop(context)
? _size.height * 0.3
? null
: _size.height * 0.2,
),
// SizedBox(
// height: _size.height * 0.1,
// ),
Container(
alignment: Alignment.bottomCenter,
alignment: Alignment.center,
padding:
EdgeInsets.symmetric(vertical: 8),
child: RichText(
@ -801,27 +807,27 @@ class _changesPasswordState extends State<changesPassword> {
)));
}
Widget _buildCheckItem(bool status, String text) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(
children: [
Icon(
status ? Icons.check : Icons.close,
color: status ? Colors.green : Colors.red,
size: 18,
),
const SizedBox(width: 6),
Text(
text,
style: TextStyle(
color: status ? Colors.green : Colors.red,
fontSize: 14,
),
),
],
),
);
}
Widget _buildCheckItem(bool status, String text) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(
children: [
Icon(
status ? Icons.check : Icons.close,
color: status ? Colors.green : Colors.red,
size: 18,
),
const SizedBox(width: 6),
Text(
text,
style: TextStyle(
color: status ? Colors.green : Colors.red,
fontSize: 14,
),
),
],
),
);
}
}

View File

@ -32,171 +32,216 @@
// import '../session/settingUpPinAndBiometric.dart';
// import '../verify.dart';
//
// class AppRouter {
// // static final SessionNotifier sessionNotifier = SessionNotifier();
// static GoRouter createRouter() {
// return GoRouter(
// initialLocation: kIsWeb ? '/login' : '/splash',
// debugLogDiagnostics: true,
// // refreshListenable: sessionNotifier,
// redirect: (BuildContext context, GoRouterState state) {
// const allowedWithoutToken = [
// '/login',
// '/verify',
// '/mailVerify',
// '/pinPage',
// '/splash',
// ];
//
// final location = state.uri.toString();
// if (allowedWithoutToken.contains(location)) return null;
//
// // final hasToken = TokenService.hasValidTokenSync();
// // if (!hasToken) return '/login';
//
// // if (!kIsWeb &&
// // SessionManager().prefs?.getString('is_mpin_skipped') == '0' &&
// // location != '/pinPage') {
// // return '/pinPage';
// // }
//
// return null;
// },
//
//
//
// routes: [
// // if (!kIsWeb)
// // GoRoute(
// // path: '/splash',
// // builder: (context, state) => SplashScreen(),
// // ),
// GoRoute(
// path: '/login',
// builder: (context, state) => login(),
// ),
// GoRoute(
// path: '/mailVerify',
// builder: (context, state) {
// final email = state.extra as String;
// return MyEmailVerify(email: email);
// },
// ),
// GoRoute(
// path: '/verify',
// builder: (context, state) {
// final args = state.extra as Map<String, dynamic>;
// return MyVerify(
// verificationId: args['verificationId'] as String,
// mobileNumber: args['mobileNumber'] as String,
// resendToken: args['resendToken'],
// onResendCode: args['onResendCode'] as Function(String, int?),
// );
// },
// ),
// GoRoute(
// path: '/mailVerify',
// builder: (context, state) => MyVerify(
// verificationId: '',
// mobileNumber: '',
// resendToken: null,
// onResendCode: (String, int) {},
// ),
// ),
// GoRoute(
// path: '/home',
// builder: (context, state) => Home(),
// ),
// GoRoute(
// path: '/pinSettingPage',
// builder: (context, state) => pinSettingPage(),
// ),
// GoRoute(
// path: '/pinPage',
// builder: (context, state) => pinPage(),
// ),
// GoRoute(
// path: '/changePin',
// builder: (context, state) => changePin(),
// ),
// GoRoute(
// path: '/claimprocess',
// builder: (context, state) => claimprocess(),
// ),
// GoRoute(
// path: '/policies',
// builder: (context, state) => policies(),
// ),
// GoRoute(
// path: '/claims',
// builder: (context, state) => claims(),
// ),
// GoRoute(
// path: '/profile',
// builder: (context, state) => profile(),
// ),
// GoRoute(
// path: '/help',
// builder: (context, state) => help(),
// ),
// GoRoute(
// path: '/wellness',
// builder: (context, state) => wellness(),
// ),
// GoRoute(
// path: '/planclaimsform',
// builder: (context, state) => planclaimsform(),
// ),
// GoRoute(
// path: '/privacypolicy',
// builder: (context, state) => privacypolicy(),
// ),
// GoRoute(
// path: '/termsofuse',
// builder: (context, state) => termsofuse(),
// ),
// GoRoute(
// path: '/generalExclusionsDeductibles',
// builder: (context, state) => generalExclusionsDeductibles(),
// ),
// GoRoute(
// path: '/chatbot()',
// builder: (context, state) => chatbot(),
// ),
// GoRoute(
// path: '/chatbot()',
// builder: (context, state) => chatbot(),
// ),
// GoRoute(
// path: '/tickettracklist',
// builder: (context, state) => tickettracklist(
// ticketID: "",
// ),
// ),
// GoRoute(
// path: '/empDetails()',
// builder: (context, state) => empDetails(),
// ),
// GoRoute(
// path: '/addOnsDetails()',
// builder: (context, state) => addOnsDetails(),
// ),
// GoRoute(
// path: '/empReviewDetails()',
// builder: (context, state) => empReviewDetails(),
// ),
// GoRoute(
// path: '/tickets()',
// builder: (context, state) => tickets(),
// ),
// GoRoute(
// path: '/raisedTicketHistory()',
// builder: (context, state) => raisedTicketHistory(),
// ),
// ],
// );
// }
// }
// import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../main.dart';
import '../email_verify.dart';
import '../enrollment/addons.dart';
import '../enrollment/empDetails.dart';
import '../enrollment/empReview.dart';
import '../login.dart';
import '../postEnrollment/chatbot.dart';
import '../postEnrollment/claimprocess.dart';
import '../postEnrollment/claims.dart';
import '../postEnrollment/faqs.dart';
import '../postEnrollment/generalexclusionsdeductibles.dart';
import '../postEnrollment/help.dart';
import '../postEnrollment/home.dart';
import '../postEnrollment/planclaimsform.dart';
import '../postEnrollment/policies.dart';
import '../postEnrollment/privacypolicy.dart';
import '../postEnrollment/profile.dart';
import '../postEnrollment/raisedTicketList.dart';
import '../postEnrollment/retailClaimForm.dart';
import '../postEnrollment/termsofuse.dart';
import '../postEnrollment/tickets.dart';
import '../postEnrollment/tickettracklist.dart';
import '../postEnrollment/wellness.dart';
import '../postEnrollment/wellness_web_view.dart';
import '../service/SessionManager.dart';
import '../service/TokenService.dart';
import '../session/SetPinBiometric.dart';
import '../session/changePin.dart';
import '../session/settingUpPinAndBiometric.dart';
import '../verify.dart';
class AppRouter {
// static final SessionNotifier sessionNotifier = SessionNotifier();
static GoRouter createRouter() {
return GoRouter(
// On web, respect the browser URL if present; otherwise fall back to login.
initialLocation: kIsWeb ? '/login' : '/splash',
debugLogDiagnostics: true,
redirect: (BuildContext context, GoRouterState state) async {
const allowedWithoutToken = [
'/login',
'/verify',
'/mailVerify',
'/pinPage',
'/splash',
];
// Always work with the matched path (ignores query params).
final location = state.matchedLocation;
final hasToken = await TokenService.hasValidToken();
final isGuestRoute = allowedWithoutToken.contains(location);
if (hasToken && isGuestRoute) {
// If a logged-in user tries to access a guest route, redirect to home
return '/home';
}
if (!hasToken && !isGuestRoute) {
// If a guest user tries to access a protected route, redirect to login
return '/login';
}
return null; // No redirect needed
},
routes: [
if (!kIsWeb)
GoRoute(
path: '/splash',
builder: (context, state) => const SplashScreen(),
),
GoRoute(
path: '/login',
builder: (context, state) => const login(),
),
GoRoute(
path: '/mailVerify',
builder: (context, state) {
final data = state.extra as Map<String, dynamic>;
final type = data['type'] as String;
final value = data['value'] as String;
return MyEmailVerify(type: type, value: value);
},
),
// Legacy Firebase-phone OTP route is commented out in verify.dart,
// so we do not expose /verify from GoRouter anymore.
GoRoute(
path: '/home',
builder: (context, state) => Home(),
),
GoRoute(
path: '/pinSettingPage',
builder: (context, state) => pinSettingPage(),
),
GoRoute(
path: '/pinPage',
builder: (context, state) => pinPage(),
),
GoRoute(
path: '/changePin',
builder: (context, state) => changePin(),
),
GoRoute(
path: '/claimprocess',
builder: (context, state) => claimprocess(),
),
GoRoute(
path: '/policies',
builder: (context, state) {
final arguments =
state.extra as Map<String, dynamic>?; // optional args
return policies(arguments: arguments);
},
),
GoRoute(
path: '/claims',
builder: (context, state) {
final int tabIndex = state.extra as int? ?? 0;
return claims(initialTab: tabIndex);
},
),
GoRoute(
path: '/profile',
builder: (context, state) => profile(),
),
GoRoute(
path: '/help',
builder: (context, state) => help(),
),
GoRoute(
path: '/wellness',
builder: (context, state) => wellness(),
),
GoRoute(
path: '/privacypolicy',
builder: (context, state) => privacypolicy(),
),
GoRoute(
path: '/termsofuse',
builder: (context, state) => termsofuse(),
),
GoRoute(
path: '/generalExclusionsDeductibles',
builder: (context, state) => generalExclusionsDeductibles(),
),
GoRoute(
path: '/planclaimsform',
builder: (context, state) {
final details = state.extra as Map<String, dynamic>?;
return planclaimsform(details: details);
},
),
GoRoute(
path: '/retailClaimForm',
builder: (context, state) {
final details = state.extra as Map<String, dynamic>?;
return retailClaimForm(details: details);
},
),
GoRoute(
path: '/raisedTicketHistory',
builder: (context, state) => raisedTicketHistory(),
),
GoRoute(
path: '/tickettracklist/:ticketID',
builder: (context, state) {
final ticketID = state.pathParameters['ticketID']!;
return tickettracklist(ticketID: ticketID);
},
),
GoRoute(
path: '/empDetails',
builder: (context, state) => empDetails(),
),
GoRoute(
path: '/faqs',
builder: (context, state) => faqs(),
),
GoRoute(
path: '/addOnsDetails',
builder: (context, state) => addOnsDetails(),
),
GoRoute(
path: '/empReviewDetails',
builder: (context, state) => empReviewDetails(),
),
GoRoute(
path: '/tickets',
builder: (context, state) => tickets(),
),
GoRoute(
path: '/chatbot',
builder: (context, state) => chatbot(),
),
GoRoute(
path: '/wellnessWebView',
builder: (context, state) {
final url = state.extra as String;
return WellnessWebView(url: url);
},
),
],
);
}
}
//
// //
// // class AppRouter {

View File

@ -511,7 +511,8 @@ class _MyEmailVerifyState extends State<MyEmailVerify> {
session.empClientBranchId == null || session.empClientBranchId!.isEmpty) {
prefs.setBool('isRetailLoggedIn', true);
}
context.go('/home');
// context.go('/home');
context.replace('/home');
}
// if (emp_status == 'enrolled' || emp_status == 'active') {
// context.go('/home');
@ -686,10 +687,12 @@ class _MyEmailVerifyState extends State<MyEmailVerify> {
if (_postToken != null && _postToken.isNotEmpty) {
ToastHelper.showSuccessToast(context, 'Successfully Logged In');
context.go('/home');
context.replace('/home');
// context.go('/home');
// Navigator.pushReplacementNamed(context, 'home');
} else {
context.go('/empDetails');
context.replace('/empDetails');
// context.go('/empDetails');
// Navigator.pushReplacementNamed(context, 'empDetails');
}
@ -740,10 +743,12 @@ class _MyEmailVerifyState extends State<MyEmailVerify> {
if (response.statusCode == 200) {
Map<String, dynamic> data = json.decode(response.body);
if(data['status'] == 'success') {
context.go('/${route}');
context.replace('/${route}');
// context.go('/${route}');
} else if(data['status'] == 'failed'){
print('setPassword');
context.goNamed(
// goNamed
context.replaceNamed(
'setPassword',
queryParameters: {
'email': email_id,
@ -930,7 +935,7 @@ class _MyEmailVerifyState extends State<MyEmailVerify> {
),
Container(
margin: marginInsets,
alignment: Alignment.bottomCenter,
alignment: Alignment.center,
child: SingleChildScrollView(
child: Form(
key: _formKey,
@ -1272,14 +1277,14 @@ class _MyEmailVerifyState extends State<MyEmailVerify> {
// ),
SizedBox(
height: Responsive.isDesktop(context)
? _size.height * 0.3
: _size.height * 0.2,
? _size.height * 0.2
: _size.height * 0.15,
),
// SizedBox(
// height: _size.height * 0.1,
// ),
Container(
alignment: Alignment.bottomCenter,
alignment: Alignment.center,
padding:
EdgeInsets.symmetric(vertical: 8),
child: RichText(

View File

@ -1428,15 +1428,14 @@ class _addOnsDetailsState extends State<addOnsDetails> {
Future<void> backFunction() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
if (prefs.containsKey('hrtoken')) {
String? mobileNo = prefs.getString('fromHrLoginMobileNo');
Navigator.pushNamed(context, 'empDetails',
arguments: {'mobile': mobileNo});
} else {
context.go('/empDetails');
// Navigator.pushNamed(context, 'empDetails');
}
}
if (prefs.containsKey('hrtoken')) {
String? mobileNo = prefs.getString('fromHrLoginMobileNo');
Navigator.pushNamed(context, 'empDetails',
arguments: {'mobile': mobileNo});
} else {
context.pop();
// Navigator.pushNamed(context, 'empDetails');
} }
void checkSiTopUp() async {
List<Map<String, dynamic>> siData = [
@ -1936,6 +1935,14 @@ class _addOnsDetailsState extends State<addOnsDetails> {
Image.network(
clientLogo ?? '', // Nhance logo
height: 40,
errorBuilder: (BuildContext context, Object error,
StackTrace? stackTrace) {
return Image.asset(
'assets/Solid_gray.png',
height: 40,
fit: BoxFit.contain,
);
},
),
// Image.network(
// 'https://i.imgur.com/qOihOvk.png', // Prodapt logo
@ -4520,8 +4527,7 @@ class _addOnsDetailsState extends State<addOnsDetails> {
checkDependentTopUp();
sendAddonsGmcDependentToAPI();
}
context.go('/empReviewDetails');
// Navigator.pushNamed(context,
context.push('/empReviewDetails'); // Navigator.pushNamed(context,
// 'empReviewDetails');
}
: null,

View File

@ -760,15 +760,15 @@ class _empDetailsState extends State<empDetails> {
),
);
},
// errorBuilder: (BuildContext context,
// Object error, StackTrace? stackTrace) {
// return Image.asset(
// 'assets/Solid_gray.png', // Replace 'default_image.png' with your default image asset path
// width: 80,
// height: 80,
// fit: BoxFit.cover,
// );
// },
errorBuilder: (BuildContext context,
Object error, StackTrace? stackTrace) {
return Image.asset(
'assets/Solid_gray.png',
width: 80,
height: 80,
fit: BoxFit.cover,
);
},
),
),
),
@ -976,7 +976,7 @@ class _empDetailsState extends State<empDetails> {
alignment: Alignment.centerRight,
child: ElevatedButton(
onPressed: () {
context.go('/addOnsDetails');
context.push('/addOnsDetails');
// Navigator.pushNamed(
// context, 'addOnsDetails');
},
@ -2240,6 +2240,8 @@ class _empDetailsState extends State<empDetails> {
print('gmcFloaterTextDescription $gmcFloaterTextDescription');
String gmcNotes = item['notes'];
print('gmcNotes $gmcNotes');
String cleanedNotes = gmcNotes?.toString().toLowerCase().replaceAll(' ', '') ?? '';
print('gmcNotes cleaned: $cleanedNotes');
dynamic gmcECardDownload = item['eCardDownload'];
print('gmcECardDownload $gmcECardDownload');
bool gmcCopyDependenceDataEnable = item['copy_dependence_data_enable'];
@ -2635,46 +2637,134 @@ class _empDetailsState extends State<empDetails> {
Column(
children: familyFloaterContainers,
),
// Add Family Member Button HERE
if (gmcOpenForEnrollment != 0 && gmcECardDownload == null)...[
GestureDetector(
onTap: () {
if (getFalseObjects.length == 0) {
ToastHelper.showWarningToast(context, "No family member to add");
return;
}
openAddFamilyMemberPopup(
"Add",
null,
gmcClientPolicyId,
gmcRelationShip,
gmcSumInsured,
);
},
child: Container(
margin: EdgeInsets.only(top: 10),
padding: EdgeInsets.all(15),
decoration: BoxDecoration(
border: Border.all(color: Colors.black, width: 1),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.person_add, color: Colors.black),
SizedBox(width: 10),
Text(
"Add Family Member",
style: GoogleFonts.poppins(
fontSize: Responsive.isDesktop(context) ? 20 : 16,
fontWeight: FontWeight.w500,
// Add Family Member Button:
// Show only when:
// - enrollment is open
// - e-card not generated
// - there are still dependants left to add
// - and the policy allows relationships beyond just "Self"
// if (gmcOpenForEnrollment != 0 &&
// gmcECardDownload == null &&
// getFalseObjects.isNotEmpty &&
// gmcRelationShip.any((rel) =>
// (rel?.toString().toLowerCase() ?? '') != 'self')) ...[
// Builder(builder: (context) {
// print("*** Condition is TRUE");
// GestureDetector(
// onTap: () {
// if (getFalseObjects.length == 0) {
// ToastHelper.showWarningToast(context, "No family member to add");
// return;
// }
// openAddFamilyMemberPopup(
// "Add",
// null,
// gmcClientPolicyId,
// gmcRelationShip,
// gmcSumInsured,
// );
// },
// child: Container(
// margin: EdgeInsets.only(top: 10),
// padding: EdgeInsets.all(15),
// decoration: BoxDecoration(
// border: Border.all(color: Colors.black, width: 1),
// borderRadius: BorderRadius.circular(8),
// ),
// child: Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// Icon(Icons.person_add, color: Colors.black),
// SizedBox(width: 10),
// Text(
// "Add Family Member",
// style: GoogleFonts.poppins(
// fontSize: Responsive.isDesktop(context) ? 20 : 16,
// fontWeight: FontWeight.w500,
// ),
// ),
// ],
// ),
// ),
// ),
// ],
if (cleanedNotes != 'allowedmembersself') ...<Widget>[
if (gmcOpenForEnrollment != 0 &&
gmcECardDownload == null &&
getFalseObjects.isNotEmpty &&
gmcRelationShip.any((rel) =>
(rel is Map
? rel['relationship']?.toString().toLowerCase()
: rel?.toString().toLowerCase() ?? '') != 'self')) ...<Widget>[
Builder(builder: (context) {
print("*** IF - Condition is TRUE");
print("*** gmcOpenForEnrollment: $gmcOpenForEnrollment");
print("*** gmcECardDownload: $gmcECardDownload");
print("*** getFalseObjects length: ${getFalseObjects.length}");
print("*** gmcRelationShip full: $gmcRelationShip");
print("*** gmcNotes original: $gmcNotes");
print("*** gmcNotes cleaned: $cleanedNotes");
return GestureDetector(
onTap: () {
if (getFalseObjects.length == 0) {
ToastHelper.showWarningToast(context, "No family member to add");
return;
}
openAddFamilyMemberPopup(
"Add",
null,
gmcClientPolicyId,
gmcRelationShip,
gmcSumInsured,
);
},
child: Container(
margin: EdgeInsets.only(top: 10),
padding: EdgeInsets.all(15),
decoration: BoxDecoration(
border: Border.all(color: Colors.black, width: 1),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.person_add, color: Colors.black),
SizedBox(width: 10),
Text(
"Add Family Member",
style: GoogleFonts.poppins(
fontSize: Responsive.isDesktop(context) ? 20 : 16,
fontWeight: FontWeight.w500,
),
),
),
],
],
),
),
),
),
);
}),
] else ...<Widget>[
Builder(builder: (context) {
print("*** ELSE - Condition is FALSE");
print("*** gmcOpenForEnrollment: $gmcOpenForEnrollment → pass: ${gmcOpenForEnrollment != 0}");
print("*** gmcECardDownload: $gmcECardDownload → pass: ${gmcECardDownload == null}");
print("*** getFalseObjects length: ${getFalseObjects.length} → pass: ${getFalseObjects.isNotEmpty}");
print("*** gmcRelationShip full list: $gmcRelationShip");
print("*** gmcNotes original: $gmcNotes");
print("*** gmcNotes cleaned: $cleanedNotes");
return SizedBox.shrink(); // no widget shown in else
}),
],
]else ...<Widget>[
Builder(builder: (context) {
print("*** OUTER ELSE - Condition is FALSE");
print("*** gmcNotes original: $gmcNotes");
print("*** gmcNotes cleaned: $cleanedNotes");
return SizedBox.shrink();
}),
],
SizedBox(height: 15),
if (Responsive.isDesktop(context) && gmcIsValueValid)
Row(

View File

@ -1413,6 +1413,14 @@ setState(() {
Image.network(
clientLogo ?? '', // Nhance logo
height: 40,
errorBuilder: (BuildContext context, Object error,
StackTrace? stackTrace) {
return Image.asset(
'assets/Solid_gray.png',
height: 40,
fit: BoxFit.contain,
);
},
),
// Image.network(
// 'https://i.imgur.com/qOihOvk.png', // Prodapt logo

View File

@ -278,7 +278,7 @@ class _loginState extends State<login> {
print('isEmailFieldVisible $isEmailFieldVisible');
// prefs.setString('empEmail', emailController.text);
print('${emailMobileController.text}');
context.push(
context.go(
'/mailVerify',
extra: {
'type': 'email',
@ -287,7 +287,7 @@ class _loginState extends State<login> {
);
} else {
// _verifyPhoneNumber();
context.push(
context.go(
'/mailVerify',
extra: {
'type': 'mobile',
@ -690,6 +690,14 @@ class _loginState extends State<login> {
clickedForgotPassword = true;
passwordController.text = '';
passwordController.clear();
// Clear everything so the flow starts fresh
emailController.clear();
_otpController.clear();
resetPasswordController.clear();
confirmPasswordController.clear();
_formKey.currentState?.reset();
});
}
@ -800,8 +808,17 @@ class _loginState extends State<login> {
otpValueStatus = false;
otpFieldShow = false;
clickedForgotPassword = false;
resetPasswordEnable = true;
resetPasswordEnable = true; // This shows the reset fields
_isLoading = false;
hasMinLength = false;
hasUpperLower = false;
hasNumber = false;
hasSpecialChar = false;
// ADD THESE LINES TO CLEAR CACHED DATA
resetPasswordController.clear();
confirmPasswordController.clear();
_formKey.currentState?.reset();
});
// ToastHelper.showSuccessToast(context, message);
} else {
@ -871,6 +888,17 @@ class _loginState extends State<login> {
resetPasswordEnable = false;
clickedForgotPassword = false;
otpFieldShow = false;
// CLEAR THE CONTROLLERS HERE
resetPasswordController.clear();
confirmPasswordController.clear();
_otpController.clear();
// emailController.clear(); // Uncomment if you want the email cleared too
// Reset the form state to clear validation error messages
_formKey.currentState?.reset();
emailController.text = '';
emailController.clear();
});
// ToastHelper.showSuccessToast(context, message);
} else {
@ -1095,7 +1123,7 @@ class _loginState extends State<login> {
child: Image.asset(
'assets/nhance_app_logo.png',
width: 150,
height: 100,
height: 150,
)),
),
],
@ -1109,7 +1137,7 @@ class _loginState extends State<login> {
),
Container(
margin: marginInsets,
alignment: Alignment.bottomCenter,
alignment: Alignment.center,
child: SingleChildScrollView(
child: Form(
key: _formKey,
@ -1120,9 +1148,7 @@ class _loginState extends State<login> {
Expanded(
flex: _size.width < 1100 ? 6 : 12,
child: Container(
margin: _size.width > 1100
? EdgeInsets.only(left: 20, right: 20)
: EdgeInsets.only(left: 0, right: 0),
margin: EdgeInsets.symmetric(horizontal: 20),
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
@ -1638,152 +1664,114 @@ class _loginState extends State<login> {
},
),
),
if (otpFieldShow) ...[
SizedBox(height: 10),
AnimatedSwitcher(
duration: const Duration(
milliseconds: 500,
), // animation speed
switchInCurve:
Curves.easeInOutCirc,
switchOutCurve:
Curves.easeOutCirc,
child:
clickedForgotPassword
? Column(
key: const ValueKey(
'otp_block'),
crossAxisAlignment:
CrossAxisAlignment
.start,
children: [
Container(
child: Center(
child:
Text(
'OTP',
style:
TextStyle(
fontSize: Responsive.isMobile(context)
? 14
: 18,
fontWeight:
FontWeight.w600,
color:
if (otpFieldShow &&
clickedForgotPassword) ...[
const SizedBox(height: 10),
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Center(
child: Text(
'OTP',
style: TextStyle(
fontSize: Responsive
.isMobile(
context)
? 14
: 18,
fontWeight:
FontWeight
.w600,
color:
Colors.black,
),
),
),
),
const SizedBox(
height: 10),
Container(
alignment:
Alignment.center,
margin: Responsive
.isDesktop(
context)
? const EdgeInsets
.symmetric(
horizontal:
150)
: const EdgeInsets
.symmetric(
horizontal:
0),
child: Pinput(
length: 6,
inputFormatters: [
FilteringTextInputFormatter
.digitsOnly,
],
keyboardType:
TextInputType
.number,
showCursor: true,
controller:
_otpController,
validator:
(value) {
if (value ==
null ||
value
.isEmpty) {
return 'Please enter OTP';
}
if (value
.length <
6) {
return 'OTP must be 6 digits';
}
return null;
},
),
),
const SizedBox(
height: 10),
Container(
margin: Responsive
.isDesktop(
context)
? const EdgeInsets
.symmetric(
horizontal:
150)
: const EdgeInsets
.symmetric(
horizontal:
0),
alignment: Alignment
.centerRight,
child: InkWell(
onTap: () {
setState(() {
otpFieldShow =
false;
});
resendOTP();
},
mouseCursor:
SystemMouseCursors
.click,
child: Text(
'Didnt Receive Code?',
style: GoogleFonts
.poppins(
color: Colors
.blue,
fontSize: 14,
),
),
),
const SizedBox(
height:
10),
Container(
alignment: Alignment.center,
margin: Responsive.isDesktop(context)
? EdgeInsets.symmetric(
horizontal: 150)
: EdgeInsets.symmetric(
horizontal: 0),
child: Pinput(
length: 6,
// defaultPinTheme: defaultPinTheme,
// focusedPinTheme: focusedPinTheme,
// submittedPinTheme: submittedPinTheme,
inputFormatters: [
FilteringTextInputFormatter
.digitsOnly, // allows only 09
],
keyboardType: TextInputType.number,
showCursor: true,
controller: _otpController,
validator:
(value) {
if (value == null ||
value.isEmpty) {
return 'Please enter OTP';
}
if (value.length <
6) {
return 'OTP must be 6 digits';
}
// if (otpValueStatus) {
// // example
// return 'Invalid OTP';
// }
return null;
},
),
),
const SizedBox(
height:
10),
Container(
margin: Responsive.isDesktop(context)
? EdgeInsets.symmetric(
horizontal:
150)
: EdgeInsets.symmetric(
horizontal:
0),
alignment:
Alignment
.centerRight, // center the text
child:InkWell(
onTap: (){
setState(() {
otpFieldShow = false;
});
print('ABCDEF');
resendOTP();
},
mouseCursor: SystemMouseCursors.click,
child: Text(
'Didnt Receive Code?',
style: GoogleFonts.poppins(
color: Colors.blue,
fontSize: 14,
),
),
),
// RichText(
// textAlign:
// TextAlign.right,
// text:
// TextSpan(
// text:
// 'Didnt Receive Code? ', // normal text
// style:
// TextStyle(
// fontSize: Responsive.isMobile(context)
// ? 12
// : 14,
// fontWeight:
// FontWeight.normal,
// color:
// Colors.black,
// ),
// children: [
// TextSpan(
// text: 'Resend', // bold clickable part
// style: TextStyle(
// fontWeight: FontWeight.bold,
// color: Colors.white,
// decoration: TextDecoration.underline, // optional
// ),
// // recognizer: TapGestureRecognizer()
// // ..onTap = () {
// //
// // },
// ),
// ],
// ),
// ),
),
],
)
: const SizedBox
.shrink(), // Empty widget when false
),
],
),
],
SizedBox(height: 20),
@ -1856,6 +1844,7 @@ class _loginState extends State<login> {
borderRadius: BorderRadius.circular(10),
),
child: TextFormField(
key: const ValueKey('new_password_field'),
controller: resetPasswordController,
obscureText: _resetObscurePassword,
onChanged: validatePassword,
@ -1915,6 +1904,7 @@ class _loginState extends State<login> {
borderRadius: BorderRadius.circular(10),
),
child: TextFormField(
key: const ValueKey('confirm_password_field'),
controller: confirmPasswordController,
obscureText: _obscureConfirmPassword,
textAlignVertical: TextAlignVertical.center,

View File

@ -127,13 +127,15 @@ class _AddPolicyScreenState extends State<AddPolicyScreen> {
policyNoController.clear();
expDateController.clear();
setState(() => isLoading = false);
context.go('/home');
}
}
} catch (e) {
print('Submit error: $e');
} finally {
if (mounted) {
setState(() => isLoading = false);
}
}
}

View File

@ -177,7 +177,8 @@ import '../service/popup_helper.dart';
canPop: false,
onPopInvokedWithResult: (didPop, result) {
if (didPop) return;
context.go('/claims');
// context.go('/claims');
context.pop();
},
child: Scaffold(
backgroundColor: Colors.white,
@ -209,7 +210,7 @@ import '../service/popup_helper.dart';
flex: 12,
child: InkWell(
onTap: () {
context.go('/claims');
context.pop(); // context.go('/claims');
},
child: Row(
mainAxisAlignment:

View File

@ -311,11 +311,18 @@ class _claimsState extends State<claims> {
if (widget.initialTab == 2) {
context.go('/home');
}
if (widget.initialTab == 0) {
context.go('/home');
} else {
context.pop();
}
if (Navigator.canPop(context)) {
context.pop();
} else {
context.go('/home');
}
// if (widget.initialTab == 0) {
// context.pop();
// } else {
// context.pop();
// }
},
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
@ -993,14 +1000,14 @@ class _claimsState extends State<claims> {
var details = {
"retailDetails": item,
};
context.go('/retailClaimForm', extra: details);
context.push('/retailClaimForm', extra: details);
} else {
// Normal claim policy click
var details = {
"claimsDetails": item,
"fromClaimPage": 0,
};
context.go('/planclaimsform', extra: details);
context.push('/planclaimsform', extra: details);
}
},
child: MouseRegion(

View File

@ -192,11 +192,55 @@ class _generalExclusionsDeductiblesState
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'General Exclusions & Deductibles',
style: GoogleFonts.poppins(
fontSize: 18, fontWeight: FontWeight.w600),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
flex: 12,
child: InkWell(
onTap: () {
context.go('/home');
},
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Icon(
Icons.chevron_left,
color: Color(0xFF000000),
size: 30,
),
SizedBox(
width: Responsive.isDesktop(context)
? 0
: 5),
Column(
mainAxisAlignment:
MainAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
'General Exclusions & Deductibles',
textAlign: TextAlign.start,
style: GoogleFonts.poppins(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF000000),
),
),
],
),
],
),
),
),
],
),
// Text(
// 'General Exclusions & Deductibles',
// style: GoogleFonts.poppins(
// fontSize: 18, fontWeight: FontWeight.w600),
// ),
const SizedBox(height: 20),
if (type3Content.isNotEmpty) ...[
Text(type3SectionName ?? '',

View File

@ -336,31 +336,31 @@ class _helpState extends State<help> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
flex: 12,
child: InkWell(
onTap: () {
context.push('/claims');
// Navigator.pushNamed(context, 'home');
},
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Icon(
Icons
.chevron_left, // Replace with your desired icon
color: Color(0xFF000000),
size: 30,
),
],
),
),
),
],
),
// Row(
// mainAxisAlignment: MainAxisAlignment.start,
// children: [
// Expanded(
// flex: 12,
// child: InkWell(
// onTap: () {
// context.push('/claims');
// // Navigator.pushNamed(context, 'home');
// },
// child: Row(
// mainAxisAlignment: MainAxisAlignment.start,
// children: [
// Icon(
// Icons
// .chevron_left, // Replace with your desired icon
// color: Color(0xFF000000),
// size: 30,
// ),
// ],
// ),
// ),
// ),
// ],
// ),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [

View File

@ -1880,10 +1880,44 @@ class _HomeState extends State<Home> {
cards.add(_buildAddCard()); // <-- always add this card
}
// NEW: Show empty state when inactive tab has no policies
if (!isActive && cards.isEmpty) {
return _buildEmptyState("No inactive policies available");
}
return _buildCarouselSlider(cards);
}
Widget _buildEmptyState(String message) {
return Container(
width: double.infinity,
margin: const EdgeInsets.symmetric(vertical: 8),
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 20),
decoration: BoxDecoration(
color: const Color(0xFFF7F7F7),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: const Color(0xFFE0E0E0), width: 1.5),
),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.folder_off_outlined, size: 48, color: Colors.grey[400]),
const SizedBox(height: 12),
Text(
message,
textAlign: TextAlign.center,
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.grey[600],
),
),
],
),
);
}
Widget _buildCarouselSlider(List<Widget> cards) {
final int totalCards = cards.length;
final bool hasAddCard = isActive; // because you add AddCard only when isActive

View File

@ -137,6 +137,7 @@ class _planclaimsformState extends State<planclaimsform> {
bool isIntimationDateValid = true;
bool isAdmitDateValid = true;
bool isDischargeDateValid = true;
bool showFileError = false;
final session = SessionManager();
@ -591,57 +592,70 @@ class _planclaimsformState extends State<planclaimsform> {
}
Future<void> sendFormDataToApi() async {
setState(() => isSubmitting = true); // 🔥 start loader
setState(() {
isServiceValid = serviceId != null;
isPolicyValid = policyNumberId != null;
isMemberValid = selectedMemberId != null;
isSubjectValid = subjectController.text.trim().isNotEmpty;
isHospitalNameValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || hospitalNameController.text.trim().isNotEmpty;
isHospitalAddressValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || hospitalAddressController.text.trim().isNotEmpty;
isHospitalStateValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || hospitalStateController.text.trim().isNotEmpty;
isHospitalCityValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || hospitalCityController.text.trim().isNotEmpty;
isHospitalPincodeValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || hospitalPinCodeController.text.trim().isNotEmpty;
isHospitalPhoneNoValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || hospitalPhoneNoController.text.trim().isNotEmpty;
// isAdmitDischargeValid = policyTypeCondition != 1 || (admitDate != null && dischargeDate != null);
isClaimTypeValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || claimTypeId != null;
isAdmitDateValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || admitDate != null;
isDischargeDateValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || dischargeDate != null;
isClaimAmountValid = serviceId != 1 || claimAmountController.text.trim().isNotEmpty;
isAccidentDateValid = (serviceId == 2 || serviceId == 3 || serviceId == 4) ? accidentDate != null : true;
isIntimationDateValid = (serviceId == 2 || serviceId == 3 || serviceId == 4) ? intimationDate != null : true;
});
final isServiceValid = serviceId != null;
final isPolicyValid = policyNumberId != null;
final isMemberValid = selectedMemberId != null;
final isSubjectValid = subjectController.text.trim().isNotEmpty;
final isHospitalNameValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || hospitalNameController.text.trim().isNotEmpty;
final isHospitalAddressValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || hospitalAddressController.text.trim().isNotEmpty;
final isHospitalStateValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || hospitalStateController.text.trim().isNotEmpty;
final isHospitalCityValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || hospitalCityController.text.trim().isNotEmpty;
final isHospitalPincodeValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || hospitalPinCodeController.text.trim().isNotEmpty;
final isHospitalPhoneNoValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || (hospitalPhoneNoController.text.trim().length == 10);
final isAdmitDateValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || admitDate != null;
final isDischargeDateValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || dischargeDate != null;
final isClaimAmountValid = serviceId != 1 || claimAmountController.text.trim().isNotEmpty;
final isAccidentDateValid = (serviceId == 2 || serviceId == 3 || serviceId == 4) ? accidentDate != null : true;
final isIntimationDateValid = (serviceId == 2 || serviceId == 3 || serviceId == 4) ? intimationDate != null : true;
final areFilesUploaded = FileUploadService().files.isNotEmpty;
if (isServiceValid &&
isClaimTypeValid &&
isPolicyValid &&
isMemberValid &&
isSubjectValid &&
isHospitalNameValid &&
isHospitalAddressValid &&
isHospitalStateValid &&
isHospitalCityValid &&
isHospitalPincodeValid &&
isHospitalPhoneNoValid &&
isAdmitDateValid &&
isDischargeDateValid &&
isClaimAmountValid &&
isAccidentDateValid &&
isIntimationDateValid) {
if (FileUploadService().files.isEmpty) {
setState(() {
MultiFileUploadWidget.hasFiles = false;
});
ToastHelper.showErrorToast(context, 'Please upload at least one document');
return;
}
// Proceed to submit
} else {
if (!isServiceValid ||
!isPolicyValid ||
!isMemberValid ||
!isSubjectValid ||
!isHospitalNameValid ||
!isHospitalAddressValid ||
!isHospitalStateValid ||
!isHospitalCityValid ||
!isHospitalPincodeValid ||
!isHospitalPhoneNoValid ||
!isAdmitDateValid ||
!isDischargeDateValid ||
!isClaimAmountValid ||
!isAccidentDateValid ||
!isIntimationDateValid) {
setState(() {
this.isServiceValid = isServiceValid;
this.isPolicyValid = isPolicyValid;
this.isMemberValid = isMemberValid;
this.isSubjectValid = isSubjectValid;
this.isHospitalNameValid = isHospitalNameValid;
this.isHospitalAddressValid = isHospitalAddressValid;
this.isHospitalStateValid = isHospitalStateValid;
this.isHospitalCityValid = isHospitalCityValid;
this.isHospitalPincodeValid = isHospitalPincodeValid;
this.isHospitalPhoneNoValid = isHospitalPhoneNoValid;
this.isAdmitDateValid = isAdmitDateValid;
this.isDischargeDateValid = isDischargeDateValid;
this.isClaimAmountValid = isClaimAmountValid;
this.isAccidentDateValid = isAccidentDateValid;
this.isIntimationDateValid = isIntimationDateValid;
showFileError = true; // Also show file error if other fields are invalid
});
ToastHelper.showErrorToast(context, 'Please Fill Required Fields');
return;
}
if (!areFilesUploaded) {
setState(() {
showFileError = true;
});
ToastHelper.showErrorToast(context, 'Please upload the document');
return;
}
setState(() {
isSubmitting = true;
isLoading = true;
});
@ -711,7 +725,8 @@ class _planclaimsformState extends State<planclaimsform> {
_token = await TokenService.getPostToken();
final request = http.MultipartRequest('POST', Uri.parse('${Environment.apiUrl}initiateClaim'));
final request = http.MultipartRequest(
'POST', Uri.parse('${Environment.apiUrl}initiateClaim'));
request.headers['Authorization'] = 'Bearer $_token';
request.headers['APP-SIGNATURE'] = 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y';
final stringFields = fields.map((key, value) => MapEntry(key, value?.toString() ?? ''));
@ -728,7 +743,10 @@ class _planclaimsformState extends State<planclaimsform> {
print("📝 Checking label for file: ${uf.file.name}, current label: '${uf.label}'");
if ((uf.label ?? '').trim().isEmpty) {
ToastHelper.showErrorToast(context, 'Please enter name for all uploaded documents');
setState(() => isLoading = false);
setState(() {
isLoading = false;
isSubmitting = false; // reset loader
});
return;
}
}
@ -769,33 +787,20 @@ class _planclaimsformState extends State<planclaimsform> {
// Convert image PDF
final pdf = pw.Document();
final image = pw.MemoryImage(fileBytes);
pdf.addPage(
pw.Page(
build: (pw.Context context) => pw.Center(
child: pw.Image(image, fit: pw.BoxFit.contain),
),
),
);
fileBytes = await pdf.save(); // converted PDF bytes
// replace file name with .pdf extension
pdf.addPage(pw.Page(
build: (pw.Context context) =>
pw.Center(child: pw.Image(image, fit: pw.BoxFit.contain)),
));
fileBytes = await pdf.save();
final pdfFileName = pf.name.replaceAll(RegExp(r'\.\w+$'), '.pdf');
print('📄 Converted image ${pf.name} → PDF ($pdfFileName)');
request.files.add(http.MultipartFile.fromBytes(
'claim_docs[]',
fileBytes,
filename: pdfFileName,
));
'claim_docs[]', fileBytes, filename: pdfFileName));
} else {
// Already a PDF
request.files.add(http.MultipartFile.fromBytes(
'claim_docs[]',
fileBytes,
filename: pf.name,
));
'claim_docs[]', fileBytes, filename: pf.name));
}
}
}
@ -816,31 +821,25 @@ class _planclaimsformState extends State<planclaimsform> {
final responseBody = await response.stream.bytesToString();
final decoded = jsonDecode(responseBody);
if (decoded['status'] == true) {
ToastHelper.showSuccessToast(context, decoded['message']);
serviceId = null;
departmentList.clear();
setState(() {
isLoading = false;
});
setState(() => isLoading = false);
context.go('/claims', extra: 2);
print('Form data submitted successfully');
fileService.clearAll();
} else {
setState(() {
isLoading = false;
});
setState(() => isLoading = false);
ToastHelper.showErrorToast(context, "Failed: ${decoded['message']}");
}
} catch (e) {
setState(() {
isLoading = false;
});
setState(() => isLoading = false);
print('Error submitting form data: $e');
} finally {
setState(() => isSubmitting = false); // 🔥 stop loader
setState(() => isSubmitting = false); // 🔥 always stop loader
}
}
@ -932,60 +931,92 @@ class _planclaimsformState extends State<planclaimsform> {
canPop: false,
onPopInvokedWithResult: (didPop, result) {
if (didPop) return;
final route = fromClaimsPage == 0 ? 'claims' : 'help';
context.go('/$route');
context.pop();
},
child: Scaffold(
backgroundColor: Colors.white,
appBar: CustomAppBar(),
body: Stack(children: [
SingleChildScrollView(
child: Container(
padding: Responsive.isDesktop(context)
? EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width *
0.2, // 30% of screen width as horizontal padding
vertical: MediaQuery.of(context).size.height *
0.05, // 5% of screen height as vertical padding
)
: EdgeInsets.all(10),
color: Colors.white,
child: Column(children: [
Card(
elevation: 5,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(15.0), // Set border radius here
),
child: Container(
decoration: BoxDecoration(
color: Colors.white, // Set background color to white
borderRadius: BorderRadius.circular(
15.0), // Set border radius for Container
),
padding: Responsive.isDesktop(context)
? EdgeInsets.only(top: 15, bottom: 15, left: 15, right: 15)
: EdgeInsets.only(
top: 10,
bottom: 10,
left: 10,
right: 10), // Add padding to the container
child: Row(
children: [
Expanded(
flex: 12,
child: Container(
alignment: Alignment.centerLeft,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: BoxDecoration(
color: Color(
0xFFFFFCE5), // Set background color for the container
borderRadius: BorderRadius.circular(
10), // Set border radius for the container
backgroundColor: Colors.white,
appBar: CustomAppBar(),
body: Stack(children: [
SingleChildScrollView(
child: Container(
padding: Responsive.isDesktop(context)
? EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width *
0.2, // 30% of screen width as horizontal padding
vertical: MediaQuery.of(context).size.height *
0.05, // 5% of screen height as vertical padding
)
: EdgeInsets.all(10),
color: Colors.white,
child: Column(children: [
Card(
elevation: 5,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(15.0), // Set border radius here
),
child: Container(
decoration: BoxDecoration(
color: Colors.white, // Set background color to white
borderRadius: BorderRadius.circular(
15.0), // Set border radius for Container
),
padding: Responsive.isDesktop(context)
? EdgeInsets.only(top: 15, bottom: 15, left: 15, right: 15)
: EdgeInsets.only(
top: 10,
bottom: 10,
left: 10,
right: 10), // Add padding to the container
child: Row(
children: [
Expanded(
flex: 12,
child: Container(
alignment: Alignment.centerLeft,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: BoxDecoration(
color: Color(
0xFFFFFCE5), // Set background color for the container
borderRadius: BorderRadius.circular(
10), // Set border radius for the container
),
padding: Responsive.isDesktop(context)
? EdgeInsets.only(
top: 20,
bottom: 20,
left: 0,
right: 0)
: EdgeInsets.only(
top: 10,
bottom: 10,
left: 10,
right: 10),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.start,
children: [
Expanded(
flex: 1,
child: InkWell(
onTap: () {
context.pop();
},
child: Icon(
Icons
.chevron_left, // Replace with your desired icon
color: Color(0xFF000000),
size: 30,
),
),
),
padding: Responsive.isDesktop(context)
? EdgeInsets.only(
@ -1039,19 +1070,439 @@ class _planclaimsformState extends State<planclaimsform> {
fontSize:
Responsive.isDesktop(
context)
? 20
: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF000000),
? 20
: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF000000),
),
)
],
),
)
],
), // Space between rows
// Add more rows as needed
],
),
),
SizedBox(height: 15),
Container(
child: Column(
children: [
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Container(
width: double.infinity,
alignment: Alignment.centerLeft,
child: Column(
mainAxisAlignment:
MainAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Form(
key: formKey,
child: Column(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildDropdownField(
'Service',isRequired: true,
(value) {
setState(() {
serviceId = value;
selectedServiceName = departmentList.firstWhere(
(serList) => serList['id'] == value,
orElse: () => {'name': ''},
)['name'];
policyNumberId = null;
if (fromClaimsPage == 1) {
fetchPoliciesBasedOnService(serviceId);
}
isServiceValid = true;
});
},
fromClaimsPage == 0,
departmentList,
'name',
serviceId,
),
if (!isServiceValid)
Text('Please select a service', style: TextStyle(color: Colors.red)),
],
),
// buildDropdownField(
// 'Service',
// (value) {
// setState(() {
// serviceId = value;
// selectedServiceName =
// departmentList
// .firstWhere(
// (serList) =>
// serList[
// 'id'] ==
// value,
// orElse: () =>
// {'name': ''},
// )['name'];
// policyNumberId =
// null;
// if (fromClaimsPage ==
// 1) {
// fetchPoliciesBasedOnService(
// serviceId);
// }
// });
// },
// fromClaimsPage == 0,
// departmentList,
// 'name',
// serviceId, // Pass current serviceId as selectedValue
// ),
SizedBox(height: 15),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildDropdownField(
'Select Policy',isRequired: true,
(value) {
setState(() {
policyNumberId = value;
selectedMemberId = null;
String? selectedPolicyNo = policyNumberList.firstWhere(
(item) => item['id'] == value,
orElse: () => {'policy_no': ''},
)['policy_no'];
if (selectedPolicyNo != null && fromClaimsPage == 1) {
fetchMemberBasedOnPolicy(selectedPolicyNo);
}
isPolicyValid = true;
});
},
fromClaimsPage == 0,
policyNumberList,
'policy_no',
policyNumberId,
),
if (!isPolicyValid)
Text('Please select a policy', style: TextStyle(color: Colors.red)),
],
),
// // if (fromClaimsPage == 1)
// buildDropdownField(
// 'Select Policy',
// (value) {
// setState(() {
// policyNumberId =
// value;
// selectedMemberId =
// null;
// String?
// selectedPolicyNo =
// policyNumberList
// .firstWhere(
// (item) =>
// item['id'] ==
// value,
// orElse: () =>
// {
// 'policy_no': ''
// })['policy_no'];
//
// if (selectedPolicyNo !=
// null &&
// fromClaimsPage ==
// 1) {
// fetchMemberBasedOnPolicy(
// selectedPolicyNo);
// }
// });
// },
// fromClaimsPage == 0,
// policyNumberList,
// 'policy_no',
// policyNumberId, // Pass current policyNumberId as selectedValue
// ),
SizedBox(height: 15),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildDropdownField(
'Member Name',isRequired: true,
(value) {
setState(() {
selectedMemberId = value;
selectedMemberName = employeePolicyList.firstWhere(
(member) => member['id'] == value,
orElse: () => {'name': ''},
)['name'];
isMemberValid = true;
});
},
false,
employeePolicyList,
'name',
selectedMemberId,
),
if (!isMemberValid)
Text('Please select a member', style: TextStyle(color: Colors.red)),
],
),
SizedBox(height: 15),
// buildDropdownField(
// 'Member Name',
// (value) {
// setState(() {
// selectedMemberId =
// value;
// selectedMemberName =
// employeePolicyList
// .firstWhere(
// (member) =>
// member[
// 'id'] ==
// value,
// orElse: () =>
// {'name': ''},
// )['name'];
// });
// },
// false,
// employeePolicyList,
// 'name',
// selectedMemberId, // Pass current selectedMemberId as selectedValue
// ),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// buildTextField('Subject',
// subjectController),
// if (!isSubjectValid)
// Text('Please enter the Subject', style: TextStyle(color: Colors.red)),
SizedBox(height: 15),
buildTextAreaField(
'Message',
messageController),
SizedBox(height: 15),
// if (policyTypeCondition ==
// 3)
// buildTextField(
// 'Accident Details',
// accidentDetailsController),
SizedBox(height: 15),
if (policyTypeCondition == 1 || policyTypeCondition == 72) ...[
buildTextField('Hospital Name', hospitalNameController, isRequired: true),
if (!isHospitalNameValid)
Text('Please enter the Hospital Name', style: TextStyle(color: Colors.red)),
SizedBox(height: 15),
buildTextAreaField('Hospital Address', hospitalAddressController, isRequired: true),
if (!isHospitalAddressValid)
Text('Please enter the Hospital Address', style: TextStyle(color: Colors.red)),
SizedBox(height: 15),
buildTextField('Hospital City', hospitalCityController,isRequired: true,inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z ]'))]),
if (!isHospitalCityValid)
Text('Please enter the Hospital City', style: TextStyle(color: Colors.red)),
SizedBox(height: 15),
buildTextField('Hospital State', hospitalStateController,isRequired: true,inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z ]'))]),
if (!isHospitalStateValid)
Text('Please enter the Hospital State', style: TextStyle(color: Colors.red)),
SizedBox(height: 15),
buildTextField('Hospital Pincode', hospitalPinCodeController,isRequired: true,keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(6),
]),
if (!isHospitalPincodeValid)
Text('Please enter the Hospital Pincode', style: TextStyle(color: Colors.red)),
SizedBox(height: 15),
buildTextField('Hospital Phone No', hospitalPhoneNoController,
isRequired: true,
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(10),
],minLength: 10,),
if (!isHospitalPhoneNoValid)
Text(
hospitalPhoneNoController.text.trim().isEmpty
? 'Please enter the Hospital Phone No'
: 'Hospital Phone No must be 10 digits', // specific message
style: TextStyle(color: Colors.red),
),
)
],
),
)
],
), // Space between rows
// Add more rows as needed
],
SizedBox(height: 15),
if (serviceId == 2 ||
serviceId == 3 ||
serviceId == 4) ...[
buildDatePickerField(
label:
'Date of Birth',
selectedDate:
birthDate,
allowFuture: false,
onDateSelected:
(selectedDate) {
setState(() {
birthDate =
selectedDate;
});
},
),
SizedBox(height: 15),
buildDatePickerField(
label: 'Accident Date',
isRequired: true,
selectedDate: accidentDate,
allowFuture: false,
minDate: parsedPolicyStartDate,
maxDate: parsedPolicyEndDate,
onDateSelected: (selectedDate) {
setState(() {
if (deathDate != null) deathDate = null;
if (intimationDate != null) intimationDate = null;
accidentDate = selectedDate;
});
},
),
if (!isAccidentDateValid)
Text('Please select the Accident Date', style: TextStyle(color: Colors.red)),
SizedBox(height: 15),
buildDatePickerField(
label:
'Date of Death',
selectedDate:
deathDate,
isRequired: true,
allowFuture: false,
minDate: accidentDate ?? parsedPolicyStartDate,
maxDate: parsedPolicyEndDate,
onDateSelected:
(selectedDate) {
setState(() {
deathDate =
selectedDate;
});
},
),
SizedBox(height: 15),
buildDatePickerField(
label: 'Date of Intimation',
isRequired: true,
selectedDate: intimationDate,
allowFuture: false,
minDate: accidentDate ?? deathDate ?? parsedPolicyStartDate,
maxDate: parsedPolicyEndDate,
onDateSelected: (selectedDate) {
setState(() {
intimationDate = selectedDate;
});
},
),
if (!isIntimationDateValid)
Text('Please select the Intimation Date', style: TextStyle(color: Colors.red)),
],
SizedBox(height: 15),
// buildTextAreaField(
// 'Accident Details',
// accidentDetailsController),
// SizedBox(height: 15),
if (policyTypeCondition == 1 || policyTypeCondition == 72) ...[
// 🟡 Admit Date no future allowed
buildDatePickerField(
label: 'Admit Date',
isRequired: true,
selectedDate: admitDate,
allowFuture: false,
onDateSelected: (selectedDate) {
setState(() {
admitDate = selectedDate;
dischargeDate = null; // reset discharge when admit changes
});
},
),
if (!isAdmitDateValid)
const Text('Please select the Admit Date', style: TextStyle(color: Colors.red)),
const SizedBox(height: 20),
// 🟢 Discharge Date must be after Admit Date
buildDatePickerField(
label: 'Discharge Date',
isRequired: true,
selectedDate: dischargeDate,
allowFuture: true,
minDate: admitDate != null
? admitDate!.add(const Duration(days: 1))
: DateTime.now().add(const Duration(days: 1)),
maxDate: null,
onDateSelected: (selectedDate) {
setState(() {
dischargeDate = selectedDate;
});
},
),
if (!isDischargeDateValid)
const Text('Please select the Discharge Date', style: TextStyle(color: Colors.red)),
],
SizedBox(height: 15),
if (serviceId == 1) ...[
buildTextField('Claims Amount', claimAmountController, isRequired: true, keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
]),
if (!isClaimAmountValid)
Text('Please enter the Claims Amount', style: TextStyle(color: Colors.red)),
],
SizedBox(height: 15),
if (serviceId == 2 ||
serviceId == 3 ||
serviceId == 4)
buildTextField(
'Sum Insured',
sumInsuredController,
isRequired: true,
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
],
),
]
),
SizedBox(height: 15),
// ThemedUploadField(
// hintText: "Upload Documents",
// txtwidth: MediaQuery.of(context).size.width < 600
// ? MediaQuery.of(context).size.width // Full width on mobile
// : MediaQuery.of(context).size.width * 0.26, // 26% on desktop
// txtheight: 45,
// onFilesSelected: (files) {
// print("Picked files: ${files.map((f) => f.name).toList()}");
// setState(() {
// uploadedFiles = files; // store all selected files
// });
// },
// ),
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
MultiFileUploadWidget(isRequired: true,showError: showFileError),
],
),
),
],
),
),
],
),
),
),
SizedBox(height: 15),
@ -1615,32 +2066,38 @@ class _planclaimsformState extends State<planclaimsform> {
);
}
Widget buildTextField(
String label,
TextEditingController controller, {
bool isRequired = false, // 👈 ADD THIS
TextInputType keyboardType = TextInputType.text,
List<TextInputFormatter>? inputFormatters,
int? minLength,
}) {
return TextFormField(
controller: controller,
keyboardType: keyboardType,
inputFormatters: inputFormatters,
decoration: InputDecoration(labelText: label),
decoration: InputDecoration(
labelText: isRequired ? '$label *' : label, // 👈 ADD THIS
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter the $label';
}
if (minLength != null && value.trim().length < minLength) { // 👈 ADD this
return '$label must be at least $minLength digits';
}
return null;
},
);
}
Widget buildTextAreaField(String label, TextEditingController controller) {
Widget buildTextAreaField(String label, TextEditingController controller ,{ bool isRequired = false}) {
return TextFormField(
controller: controller,
decoration: InputDecoration(labelText: label),
decoration: InputDecoration(labelText: isRequired ? '$label *' : label),
maxLines: 5,
validator: (value) {
if (value == null || value.isEmpty) {
@ -1656,6 +2113,7 @@ class _planclaimsformState extends State<planclaimsform> {
required DateTime? selectedDate,
required bool allowFuture,
required ValueChanged<DateTime?> onDateSelected,
bool isRequired = false,
DateTime? minDate,
DateTime? maxDate,
}) {
@ -1706,7 +2164,7 @@ class _planclaimsformState extends State<planclaimsform> {
},
child: InputDecorator(
decoration: InputDecoration(
labelText: label,
labelText: isRequired ? '$label *' : label,
border: const OutlineInputBorder(),
),
child: Text(
@ -1726,11 +2184,12 @@ class _planclaimsformState extends State<planclaimsform> {
bool readOnly,
List<Map<String, dynamic>> itemsList,
String displayField,
int? selectedValue // Added selectedValue parameter
int? selectedValue, // Added selectedValue parameter
{bool isRequired = false,}
) {
return DropdownButtonFormField<int>(
value: selectedValue,
decoration: InputDecoration(labelText: label),
decoration: InputDecoration(labelText: isRequired ? '$label *' : label,),
items: itemsList.map<DropdownMenuItem<int>>((item) {
return DropdownMenuItem<int>(
value: item['id'], // Ensure 'id' is correctly referenced

View File

@ -330,12 +330,8 @@ class ApiService {
}
Future<void> _clearLocalStorageAndRedirect() async {
final prefs = await SharedPreferences.getInstance();
await prefs.clear();
// Assuming you have access to the context
ToastHelper.showErrorToast(context, 'Session Out');
context.go('/login');
// Navigator.pushNamed(context, 'login');
if (!context.mounted) return;
await TokenService().logout(context);
}
Future<Map<String, dynamic>> getBotDetails(
@ -425,7 +421,12 @@ class ApiService {
ToastHelper.showWarningToast(context, message);
return {};
} else if (response.statusCode == 429) {
<<<<<<< HEAD
final body = jsonDecode(response.body);
=======
if (!context.mounted) return {};
final body = jsonDecode(response.body);
>>>>>>> 24f532f750daddbb5d414d9056a4e1c4c760a2c4
final message = body['message'];
ToastHelper.showWarningToast(context, message);
return {};

View File

@ -4,8 +4,15 @@ import 'file_upload_service.dart';
class MultiFileUploadWidget extends StatefulWidget {
final bool forceMobile;
final bool isRequired;
final bool showError; // ADD THIS
const MultiFileUploadWidget({super.key, this.forceMobile = false});
const MultiFileUploadWidget({
super.key,
this.forceMobile = false,
this.isRequired = false,
this.showError = false, // ADD THIS
});
@override
State<MultiFileUploadWidget> createState() => _MultiFileUploadWidgetState();
@ -18,27 +25,12 @@ class _MultiFileUploadWidgetState extends State<MultiFileUploadWidget> {
String? errorMessage;
void _pickFiles() async {
final error = await fileService.pickFiles(maxFileSizeInMB: 10); // 5 MB limit
final error = await fileService.pickFiles(maxFileSizeInMB: 10);
if (error != null) {
if (mounted) {
setState(() {
errorMessage = error;
});
// also show alert dialog for big error messages
// showDialog(
// context: context,
// builder: (ctx) => AlertDialog(
// title: const Text("File Upload Error"),
// content: Text(error),
// actions: [
// TextButton(
// onPressed: () => Navigator.pop(ctx),
// child: const Text("OK"),
// ),
// ],
// ),
// );
}
} else {
setState(() {
@ -55,6 +47,31 @@ class _MultiFileUploadWidgetState extends State<MultiFileUploadWidget> {
});
}
// Build the button label with RichText to show * in red
Widget _buildButtonLabel() {
if (!widget.isRequired) {
return const Text(
"Upload Documents",
style: TextStyle(fontSize: 14, color: Colors.black),
overflow: TextOverflow.ellipsis,
);
}
return RichText(
text: const TextSpan(
children: [
TextSpan(
text: "Upload Documents ",
style: TextStyle(fontSize: 14, color: Colors.black),
),
TextSpan(
text: "*", // red * like other fields
style: TextStyle(fontSize: 14, color: Colors.red),
),
],
),
);
}
@override
Widget build(BuildContext context) {
final files = fileService.files;
@ -67,21 +84,14 @@ class _MultiFileUploadWidgetState extends State<MultiFileUploadWidget> {
onPressed: _pickFiles,
icon: const Icon(Icons.file_upload_outlined,
color: Color(0xFFE26728), size: 24),
label: const Text(
"Upload Documents",
style: TextStyle(
fontSize: 14,
color: Colors.black,
),
overflow: TextOverflow.ellipsis,
),
label: _buildButtonLabel(), // use rich text label
style: OutlinedButton.styleFrom(
side: const BorderSide(color: Color(0xFFE26728)),
),
),
const SizedBox(height: 6),
const Text(
"Supports only PDF, PNG, JPG, JPEG,HEIC formats (max 10 MB each)",
"Supports only PDF, PNG, JPG, JPEG, HEIC formats (max 10 MB each)",
style: TextStyle(fontSize: 12, color: Colors.grey),
),
] else ...[
@ -91,14 +101,7 @@ class _MultiFileUploadWidgetState extends State<MultiFileUploadWidget> {
onPressed: _pickFiles,
icon: const Icon(Icons.file_upload_outlined,
color: Color(0xFFE26728), size: 24),
label: const Text(
"Upload Documents",
style: TextStyle(
fontSize: 14,
color: Colors.black,
),
overflow: TextOverflow.ellipsis,
),
label: _buildButtonLabel(), // use rich text label
style: OutlinedButton.styleFrom(
side: const BorderSide(color: Color(0xFFE26728)),
),
@ -106,7 +109,7 @@ class _MultiFileUploadWidgetState extends State<MultiFileUploadWidget> {
const SizedBox(width: 12),
const Expanded(
child: Text(
"Supports only PDF, PNG, JPG, JPEG,HEIC formats (max 10 MB each)",
"Supports only PDF, PNG, JPG, JPEG, HEIC formats (max 10 MB each)",
style: TextStyle(fontSize: 12, color: Colors.grey),
overflow: TextOverflow.ellipsis,
),
@ -115,10 +118,11 @@ class _MultiFileUploadWidgetState extends State<MultiFileUploadWidget> {
),
],
if (fileService.files.isEmpty && errorMessage == null) ...[
// Show error only when required AND no files
if (widget.isRequired && widget.showError && files.isEmpty && errorMessage == null) ...[
const SizedBox(height: 4),
const Text(
"Required",
'Please upload the document',
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
@ -134,7 +138,7 @@ class _MultiFileUploadWidgetState extends State<MultiFileUploadWidget> {
const SizedBox(height: 8),
...fileService.files.asMap().entries.map((entry) {
final index = entry.key;
final uploaded = entry.value; // UploadedFile
final uploaded = entry.value;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -142,7 +146,8 @@ class _MultiFileUploadWidgetState extends State<MultiFileUploadWidget> {
ListTile(
dense: true,
contentPadding: EdgeInsets.zero,
title: Text(uploaded.file.name, style: const TextStyle(fontSize: 14)),
title: Text(uploaded.file.name,
style: const TextStyle(fontSize: 14)),
trailing: IconButton(
icon: const Icon(Icons.close, color: Colors.red),
onPressed: () => _removeFile(index),

View File

@ -66,6 +66,14 @@ class _setPasswordState extends State<setPassword> {
final confirmPassword = confirmPasswordController.text.trim();
try {
if (_formKey.currentState!.validate()) {
final strongPasswordRegex =
RegExp(r'^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&]).{8,}$');
if (!strongPasswordRegex.hasMatch(newPassword)) {
ToastHelper.showErrorToast(
context,
'Enter a valid password (8+ chars with letter, number, special char)');
return;
}
if (confirmPassword != newPassword) {
ToastHelper.showErrorToast(context, 'Passwords do not match');
return;
@ -213,15 +221,20 @@ class _setPasswordState extends State<setPassword> {
}
return WillPopScope(
onWillPop: () async {
// Close the app on mobile back button press
exit(0); // This will exit the app
return false; // Return false to prevent any other actions
// 🖥 Desktop / Web always go back to login
if (kIsWeb || Responsive.isDesktop(context)) {
context.go('/login');
return false;
}
// 📱 Mobile keep existing behavior (exit app)
exit(0);
return false;
},
child: Scaffold(
body: SingleChildScrollView(
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
child: Container(
height: _size.height,
constraints: BoxConstraints(minHeight: _size.height),
color: Colors.white,
child: Stack(
children: [
@ -308,7 +321,7 @@ class _setPasswordState extends State<setPassword> {
// ),
Container(
margin: marginInsets,
alignment: Alignment.bottomCenter,
alignment: Alignment.center,
child: SingleChildScrollView(
child: Form(
key: _formKey,
@ -353,8 +366,7 @@ class _setPasswordState extends State<setPassword> {
Expanded(
flex: 12,
child: Align(
alignment: Alignment
.topLeft, // Always top-left
alignment: Alignment.center,
child: _size.width <= 1100
? Image.asset(
'assets/nhance_app_logo.png',
@ -378,10 +390,9 @@ class _setPasswordState extends State<setPassword> {
),
SizedBox(
height: Responsive.isDesktop(context)
? _size.height * 0.1
: 10,
? null
: _size.height * 0.2,
),
SizedBox(height: 10),
Container(
margin: Responsive.isDesktop(context)
? EdgeInsets.symmetric(
@ -464,6 +475,25 @@ class _setPasswordState extends State<setPassword> {
},
),
),
validator: (value) {
final password = value ?? '';
if (password.isEmpty) {
return 'Please enter your new password';
}
if (password.length < 8) {
return 'Password must be at least 8 characters';
}
if (!RegExp(r'[A-Za-z]').hasMatch(password)) {
return 'Password must include at least 1 letter';
}
if (!RegExp(r'\d').hasMatch(password)) {
return 'Password must include at least 1 number';
}
if (!RegExp(r'[@$!%*#?&]').hasMatch(password)) {
return 'Password must include at least 1 special character';
}
return null;
},
),
),
const SizedBox(height: 8),
@ -604,10 +634,10 @@ class _setPasswordState extends State<setPassword> {
),
),
),
// _size.height * 0.3
SizedBox(
height: Responsive.isDesktop(context)
? _size.height * 0.3
? null
: _size.height * 0.2,
),
// SizedBox(