saml flow
This commit is contained in:
parent
750a3becad
commit
1288a67256
@ -19,12 +19,12 @@ if (project.hasProperty('google-services.json')) {
|
|||||||
|
|
||||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||||
if (flutterVersionCode == null) {
|
if (flutterVersionCode == null) {
|
||||||
flutterVersionCode = '58'
|
flutterVersionCode = '61'
|
||||||
}
|
}
|
||||||
|
|
||||||
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||||
if (flutterVersionName == null) {
|
if (flutterVersionName == null) {
|
||||||
flutterVersionName = '2.0.20'
|
flutterVersionName = '2.0.23'
|
||||||
}
|
}
|
||||||
|
|
||||||
def keystoreProperties = new Properties()
|
def keystoreProperties = new Properties()
|
||||||
|
|||||||
BIN
assets/images/login/microsoft.png
Normal file
BIN
assets/images/login/microsoft.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 200 B |
@ -43,16 +43,19 @@ class Environment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// static String get chatbotUrl {
|
/// SAML IdP entry (`…/saml/login?email=…`). Web: same-tab redirect. Mobile: WebView.
|
||||||
// switch (flavor) {
|
static String get SsoLogin {
|
||||||
// case Flavor.dev:
|
switch (flavor) {
|
||||||
// return "https://venbait.in/nhance/dev/chatbot";
|
case Flavor.dev:
|
||||||
// case Flavor.uat:
|
return "https://venbait.in/saml/login";
|
||||||
// return "https://uat.nhanceindia.in/zenith/chatbot";
|
case Flavor.uat:
|
||||||
// case Flavor.prod:
|
return "https://appstage.nhanceindia.in/apex/saml/login";
|
||||||
// return "https://app.nhanceindia.in/zenith/chatbot";
|
case Flavor.prod:
|
||||||
// }
|
return "https://app.nhanceindia.in/saml/login";
|
||||||
// }
|
case Flavor.prod1:
|
||||||
|
return "https://app.nhanceindia.in/saml/login";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static String get baseHref {
|
static String get baseHref {
|
||||||
switch (flavor) {
|
switch (flavor) {
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
import 'dart:developer' as developer;
|
import 'dart:developer' as developer;
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart' show debugPrint, kDebugMode;
|
||||||
|
import 'package:nhance_app_pwa/config/environment.dart';
|
||||||
|
|
||||||
String _timestamp() => DateTime.now().toIso8601String();
|
String _timestamp() => DateTime.now().toIso8601String();
|
||||||
|
|
||||||
enum LogLevel { debug, info, warning, error }
|
enum LogLevel { debug, info, warning, error }
|
||||||
|
|
||||||
class LoggerConfig {
|
class LoggerConfig {
|
||||||
// Logging is always disabled in non-debug builds.
|
|
||||||
static bool enabled = true;
|
static bool enabled = true;
|
||||||
|
|
||||||
// Keep debug logs off by default to avoid local slowdown on noisy screens.
|
/// Logs below this level are dropped ([logDebug] when this is [LogLevel.info]).
|
||||||
static LogLevel minLevel = LogLevel.info;
|
static LogLevel minLevel = LogLevel.info;
|
||||||
|
|
||||||
// Stack parsing is expensive; keep it disabled unless specifically needed.
|
// Stack parsing is expensive; keep it disabled unless specifically needed.
|
||||||
@ -42,8 +42,20 @@ void _log(
|
|||||||
Object? error,
|
Object? error,
|
||||||
StackTrace? stackTrace,
|
StackTrace? stackTrace,
|
||||||
}) {
|
}) {
|
||||||
if (!kDebugMode || !LoggerConfig.enabled) return;
|
if (!LoggerConfig.enabled) return;
|
||||||
if (level.index < LoggerConfig.minLevel.index) return;
|
|
||||||
|
// Verbose minimum: UAT (all modes), or local Dev when using a debug Flutter build.
|
||||||
|
final LogLevel effectiveMinLevel = switch (Environment.flavor) {
|
||||||
|
Flavor.uat => LogLevel.debug,
|
||||||
|
Flavor.dev when kDebugMode => LogLevel.debug,
|
||||||
|
_ => LoggerConfig.minLevel,
|
||||||
|
};
|
||||||
|
if (level.index < effectiveMinLevel.index) return;
|
||||||
|
|
||||||
|
// Debug lines: debug Flutter build (typical `flutter run`), or any UAT build.
|
||||||
|
if (level == LogLevel.debug && !kDebugMode && Environment.flavor != Flavor.uat) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final inferredTag = tag ??
|
final inferredTag = tag ??
|
||||||
(LoggerConfig.includeCallerFromStack
|
(LoggerConfig.includeCallerFromStack
|
||||||
@ -58,8 +70,21 @@ void _log(
|
|||||||
error: error,
|
error: error,
|
||||||
stackTrace: stackTrace,
|
stackTrace: stackTrace,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// `developer.log` is easy to miss in release/profile (IDE filters, logcat tags).
|
||||||
|
// UAT: always mirror to stdout-style logging so `adb logcat`, Xcode, and web consoles show lines.
|
||||||
|
// Debug builds: mirror too for parity with `print` during local runs.
|
||||||
|
if (Environment.flavor == Flavor.uat || kDebugMode) {
|
||||||
|
debugPrint(fullMessage);
|
||||||
|
if (Environment.flavor == Flavor.uat && (error != null || stackTrace != null)) {
|
||||||
|
debugPrint('$error\n$stackTrace');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// **Dev + localhost:** shown when you use `flutter run` (debug mode); not in `flutter run --release`.
|
||||||
|
/// **UAT:** also emitted in profile/release (verbose).
|
||||||
|
/// **Prod / Prod1:** debug Flutter build only; in release/profile use [logInfo] or `print`.
|
||||||
void logDebug(Object? message, {String? tag}) {
|
void logDebug(Object? message, {String? tag}) {
|
||||||
_log(LogLevel.debug, message, tag: tag);
|
_log(LogLevel.debug, message, tag: tag);
|
||||||
}
|
}
|
||||||
|
|||||||
126
lib/main.dart
126
lib/main.dart
@ -12,6 +12,7 @@ import 'package:nhance_app_pwa/pages/enrollment/addons.dart';
|
|||||||
import 'package:nhance_app_pwa/pages/enrollment/empDetails.dart';
|
import 'package:nhance_app_pwa/pages/enrollment/empDetails.dart';
|
||||||
import 'package:nhance_app_pwa/pages/enrollment/empReview.dart';
|
import 'package:nhance_app_pwa/pages/enrollment/empReview.dart';
|
||||||
import 'package:nhance_app_pwa/pages/login.dart';
|
import 'package:nhance_app_pwa/pages/login.dart';
|
||||||
|
import 'package:nhance_app_pwa/pages/sso_login_return_page.dart';
|
||||||
import 'package:nhance_app_pwa/pages/postEnrollment/AddPolicyScreen.dart';
|
import 'package:nhance_app_pwa/pages/postEnrollment/AddPolicyScreen.dart';
|
||||||
import 'package:nhance_app_pwa/pages/postEnrollment/claimprocess.dart';
|
import 'package:nhance_app_pwa/pages/postEnrollment/claimprocess.dart';
|
||||||
import 'package:nhance_app_pwa/pages/postEnrollment/claims.dart';
|
import 'package:nhance_app_pwa/pages/postEnrollment/claims.dart';
|
||||||
@ -98,6 +99,7 @@ Future<String?> tokenRedirectLogic(
|
|||||||
logDebug('ABCDEFGH');
|
logDebug('ABCDEFGH');
|
||||||
const guestRoutes = [
|
const guestRoutes = [
|
||||||
'/login',
|
'/login',
|
||||||
|
'/sso-login',
|
||||||
'/verify',
|
'/verify',
|
||||||
'/mailVerify',
|
'/mailVerify',
|
||||||
'/pinPage',
|
'/pinPage',
|
||||||
@ -107,21 +109,32 @@ Future<String?> tokenRedirectLogic(
|
|||||||
final hasToken = await TokenService.hasValidToken();
|
final hasToken = await TokenService.hasValidToken();
|
||||||
logDebug('hasToken : $hasToken');
|
logDebug('hasToken : $hasToken');
|
||||||
final location = state.matchedLocation;
|
final location = state.matchedLocation;
|
||||||
|
final session = SessionManager();
|
||||||
|
|
||||||
logDebug('Location: $location');
|
logDebug('Location: $location');
|
||||||
logDebug('hasToken: $hasToken');
|
logDebug('hasToken: $hasToken');
|
||||||
|
|
||||||
// Already logged in → prevent guest pages
|
if (hasToken) {
|
||||||
|
await session.restoreSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!kIsWeb && hasToken) {
|
||||||
|
final reauth = await _mobileReauthRedirectIfNeeded(location);
|
||||||
|
if (reauth != null) return reauth;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logged in on a guest route → home (only when mobile session is unlocked).
|
||||||
if (hasToken && guestRoutes.contains(location)) {
|
if (hasToken && guestRoutes.contains(location)) {
|
||||||
|
if (!kIsWeb && !session.isMobileAppSessionUnlocked) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return '/home';
|
return '/home';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not logged in → allow guest pages directly
|
|
||||||
if (!hasToken && guestRoutes.contains(location)) {
|
if (!hasToken && guestRoutes.contains(location)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Protected route without token
|
|
||||||
if (!hasToken && !guestRoutes.contains(location)) {
|
if (!hasToken && !guestRoutes.contains(location)) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
ToastHelper.showErrorToast(
|
ToastHelper.showErrorToast(
|
||||||
@ -130,15 +143,56 @@ Future<String?> tokenRedirectLogic(
|
|||||||
return '/login';
|
return '/login';
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Restore session (decode JWT etc.)
|
return null;
|
||||||
await SessionManager().restoreSession();
|
}
|
||||||
|
|
||||||
return null; // no redirect
|
/// After a full app restart (new process), require MPIN or password login before
|
||||||
|
/// protected routes. Tokens remain in secure storage. Minimizing does not lock.
|
||||||
|
Future<String?> _mobileReauthRedirectIfNeeded(String location) async {
|
||||||
|
const allowedWhileLocked = <String>{
|
||||||
|
'/login',
|
||||||
|
'/pinPage',
|
||||||
|
'/verify',
|
||||||
|
'/mailVerify',
|
||||||
|
'/sso-login',
|
||||||
|
'/pinSettingPage',
|
||||||
|
'/changePin',
|
||||||
|
};
|
||||||
|
final session = SessionManager();
|
||||||
|
if (session.isMobileAppSessionUnlocked) return null;
|
||||||
|
if (allowedWhileLocked.contains(location)) return null;
|
||||||
|
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
final skip = prefs.getString('is_mpin_skipped');
|
||||||
|
return skip == '0' ? '/pinPage' : '/login';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// When not using `-t lib/config/main_uat.dart`, pass e.g. `--dart-define=APP_FLAVOR=uat`
|
||||||
|
/// so [Environment.flavor] (and logger UAT rules) match the build.
|
||||||
|
void _applyAppFlavorFromDartDefine() {
|
||||||
|
const fromDefine = String.fromEnvironment('APP_FLAVOR', defaultValue: '');
|
||||||
|
switch (fromDefine.toLowerCase()) {
|
||||||
|
case 'uat':
|
||||||
|
Environment.flavor = Flavor.uat;
|
||||||
|
break;
|
||||||
|
case 'prod':
|
||||||
|
Environment.flavor = Flavor.prod;
|
||||||
|
break;
|
||||||
|
case 'prod1':
|
||||||
|
Environment.flavor = Flavor.prod1;
|
||||||
|
break;
|
||||||
|
case 'dev':
|
||||||
|
Environment.flavor = Flavor.dev;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> startApp() async {
|
Future<void> startApp() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
_applyAppFlavorFromDartDefine();
|
||||||
|
|
||||||
await TokenService.clearLegacyWebAuthFromSharedPreferences();
|
await TokenService.clearLegacyWebAuthFromSharedPreferences();
|
||||||
|
|
||||||
@ -156,8 +210,11 @@ Future<void> startApp() async {
|
|||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/splash', builder: (context, state) => const SplashScreen()),
|
path: '/splash', builder: (context, state) => const SplashScreen()),
|
||||||
GoRoute(path: '/login', builder: (context, state) => login()),
|
GoRoute(path: '/login', builder: (context, state) => const login()),
|
||||||
|
GoRoute(
|
||||||
|
path: '/sso-login',
|
||||||
|
builder: (context, state) => const SsoLoginReturnPage(),
|
||||||
|
),
|
||||||
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/mailVerify',
|
path: '/mailVerify',
|
||||||
@ -179,57 +236,74 @@ Future<void> startApp() async {
|
|||||||
path: '/changePin',
|
path: '/changePin',
|
||||||
builder: (context, state) => changePin(),
|
builder: (context, state) => changePin(),
|
||||||
),
|
),
|
||||||
GoRoute(path: '/home', builder: (context, state) => Home()),
|
|
||||||
GoRoute(path: '/profile', builder: (context, state) => profile()),
|
|
||||||
GoRoute(path: '/help', builder: (context, state) => help()),
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/claimprocess', builder: (context, state) => claimprocess()),
|
path: '/home',
|
||||||
GoRoute(path: '/wellness', builder: (context, state) => wellness()),
|
builder: (context, state) => ChatbotHost(
|
||||||
|
child: Home(),
|
||||||
|
triggerIntroOnHome: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/profile',
|
||||||
|
builder: (context, state) => ChatbotHost(child: profile()),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/help',
|
||||||
|
builder: (context, state) => ChatbotHost(child: help()),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/claimprocess', builder: (context, state) => ChatbotHost(child: claimprocess()),
|
||||||
|
),
|
||||||
|
GoRoute(path: '/wellness', builder: (context, state) => ChatbotHost(child:wellness()),
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/privacypolicy', builder: (context, state) => privacypolicy()),
|
path: '/privacypolicy', builder: (context, state) => privacypolicy()),
|
||||||
GoRoute(path: '/termsofuse', builder: (context, state) => termsofuse()),
|
GoRoute(path: '/termsofuse', builder: (context, state) => termsofuse()),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/generalExclusionsDeductibles',
|
path: '/generalExclusionsDeductibles',
|
||||||
builder: (context, state) => generalExclusionsDeductibles()),
|
builder: (context, state) => ChatbotHost(child:generalExclusionsDeductibles()),
|
||||||
|
),
|
||||||
|
|
||||||
GoRoute(path: '/tickets', builder: (context, state) => tickets()),
|
GoRoute(path: '/tickets', builder: (context, state) => ChatbotHost(child:tickets()),
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/policies',
|
path: '/policies',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final arguments =
|
final arguments =
|
||||||
state.extra as Map<String, dynamic>?; // 👈 receive here
|
state.extra as Map<String, dynamic>?; // 👈 receive here
|
||||||
return policies(arguments: arguments);
|
return ChatbotHost(child:policies(arguments: arguments));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/claims',
|
path: '/claims',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final int tabIndex = state.extra as int? ?? 0; // default to 0
|
final int tabIndex = state.extra as int? ?? 0; // default to 0
|
||||||
return claims(initialTab: tabIndex);
|
return ChatbotHost(child: claims(initialTab: tabIndex));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/planclaimsform',
|
path: '/planclaimsform',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final details = state.extra as Map<String, dynamic>?;
|
final details = state.extra as Map<String, dynamic>?;
|
||||||
return planclaimsform(details: details);
|
return ChatbotHost(child: planclaimsform(details: details));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/retailClaimForm',
|
path: '/retailClaimForm',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final details = state.extra as Map<String, dynamic>?;
|
final details = state.extra as Map<String, dynamic>?;
|
||||||
return retailClaimForm(details: details);
|
return ChatbotHost(child:retailClaimForm(details: details));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/raisedTicketHistory',
|
path: '/raisedTicketHistory',
|
||||||
builder: (context, state) => raisedTicketHistory()),
|
builder: (context, state) => ChatbotHost(child:raisedTicketHistory()),
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/tickettracklist/:ticketID',
|
path: '/tickettracklist/:ticketID',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final ticketID = state.pathParameters['ticketID']!;
|
final ticketID = state.pathParameters['ticketID']!;
|
||||||
return tickettracklist(ticketID: ticketID);
|
return ChatbotHost(child:tickettracklist(ticketID: ticketID));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
// GoRoute(
|
// GoRoute(
|
||||||
@ -245,7 +319,7 @@ Future<void> startApp() async {
|
|||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/faqs',
|
path: '/faqs',
|
||||||
builder: (context, state) => faqs(),
|
builder: (context, state) => ChatbotHost(child: faqs()),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/addOnsDetails', builder: (context, state) => addOnsDetails()),
|
path: '/addOnsDetails', builder: (context, state) => addOnsDetails()),
|
||||||
@ -294,7 +368,7 @@ Future<void> startApp() async {
|
|||||||
|
|
||||||
// ✅ Middleware hook
|
// ✅ Middleware hook
|
||||||
redirect: (context, state) async =>
|
redirect: (context, state) async =>
|
||||||
await tokenRedirectLogic(context, state),
|
await tokenRedirectLogic(context, state),
|
||||||
);
|
);
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
@ -332,4 +406,4 @@ class _MyAppState extends State<MyApp> {
|
|||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -173,7 +173,7 @@ class _changesPasswordState extends State<changesPassword> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
});
|
});
|
||||||
// ToastHelper.showErrorToast(context, 'Something went wrong');
|
// ToastHelper.showErrorToast(context, 'Unable to process. Please try again later');
|
||||||
logDebug('Error: $e');
|
logDebug('Error: $e');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -307,29 +307,42 @@ class _MyEmailVerifyState extends State<MyEmailVerify> {
|
|||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
context.go('/login');
|
context.go('/login');
|
||||||
|
|
||||||
} else if (response.statusCode == 403) {
|
|
||||||
setState(() {
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
await SessionManager().clear();
|
|
||||||
ToastHelper.showErrorToast(context, 'Session Out');
|
|
||||||
if (!context.mounted) return;
|
|
||||||
context.go('/login');
|
|
||||||
|
|
||||||
} else if (response.statusCode == 451) {
|
|
||||||
setState(() {
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
final body = jsonDecode(response.body);
|
|
||||||
final message = body['message'];
|
|
||||||
ToastHelper.showWarningToast(context, message);
|
|
||||||
} else if (response.statusCode == 429) {
|
} else if (response.statusCode == 429) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
});
|
});
|
||||||
final body = jsonDecode(response.body);
|
Map<String, dynamic> data = json.decode(response.body);
|
||||||
final message = body['message'];
|
if (data.containsKey('error')) {
|
||||||
ToastHelper.showWarningToast(context, message);
|
final message = data['error']['message'];
|
||||||
|
ToastHelper.showErrorToast(context, message);
|
||||||
|
} else {
|
||||||
|
final message = data['message'];
|
||||||
|
ToastHelper.showErrorToast(context, message);
|
||||||
|
}
|
||||||
|
} else if (response.statusCode == 403) {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
Map<String, dynamic> data = json.decode(response.body);
|
||||||
|
if (data.containsKey('error')) {
|
||||||
|
final message = data['error']['message'];
|
||||||
|
ToastHelper.showErrorToast(context, message);
|
||||||
|
} else {
|
||||||
|
final message = data['message'];
|
||||||
|
ToastHelper.showErrorToast(context, message);
|
||||||
|
}
|
||||||
|
} else if (response.statusCode == 451) {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
Map<String, dynamic> data = json.decode(response.body);
|
||||||
|
if (data.containsKey('error')) {
|
||||||
|
final message = data['error']['message'];
|
||||||
|
ToastHelper.showErrorToast(context, message);
|
||||||
|
} else {
|
||||||
|
final message = data['message'];
|
||||||
|
ToastHelper.showErrorToast(context, message);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
@ -340,7 +353,7 @@ class _MyEmailVerifyState extends State<MyEmailVerify> {
|
|||||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
prefs.clear();
|
prefs.clear();
|
||||||
logDebug('Error: $e');
|
logDebug('Error: $e');
|
||||||
ToastHelper.showWarningToast(context, 'Something went wrong');
|
ToastHelper.showWarningToast(context, 'Unable to process. Please try again later');
|
||||||
// Show a Snackbar if there's an error while verifying OTP
|
// Show a Snackbar if there's an error while verifying OTP
|
||||||
logDebug('Failed to verify OTP. Please try again.');
|
logDebug('Failed to verify OTP. Please try again.');
|
||||||
} finally {
|
} finally {
|
||||||
@ -630,12 +643,48 @@ class _MyEmailVerifyState extends State<MyEmailVerify> {
|
|||||||
ToastHelper.showErrorToast(context, message);
|
ToastHelper.showErrorToast(context, message);
|
||||||
logDebug('Invalid mobile number');
|
logDebug('Invalid mobile number');
|
||||||
}
|
}
|
||||||
|
} else if (response.statusCode == 429) {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
Map<String, dynamic> data = json.decode(response.body);
|
||||||
|
if (data.containsKey('error')) {
|
||||||
|
final message = data['error']['message'];
|
||||||
|
ToastHelper.showErrorToast(context, message);
|
||||||
|
} else {
|
||||||
|
final message = data['message'];
|
||||||
|
ToastHelper.showErrorToast(context, message);
|
||||||
|
}
|
||||||
|
} else if (response.statusCode == 403) {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
Map<String, dynamic> data = json.decode(response.body);
|
||||||
|
if (data.containsKey('error')) {
|
||||||
|
final message = data['error']['message'];
|
||||||
|
ToastHelper.showErrorToast(context, message);
|
||||||
|
} else {
|
||||||
|
final message = data['message'];
|
||||||
|
ToastHelper.showErrorToast(context, message);
|
||||||
|
}
|
||||||
|
} else if (response.statusCode == 451) {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
Map<String, dynamic> data = json.decode(response.body);
|
||||||
|
if (data.containsKey('error')) {
|
||||||
|
final message = data['error']['message'];
|
||||||
|
ToastHelper.showErrorToast(context, message);
|
||||||
|
} else {
|
||||||
|
final message = data['message'];
|
||||||
|
ToastHelper.showErrorToast(context, message);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ToastHelper.showErrorToast(context, 'Something went wrong');
|
ToastHelper.showErrorToast(context, 'Unable to process. Please try again later');
|
||||||
throw Exception('Failed to verify mobile number');
|
throw Exception('Failed to verify mobile number');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ToastHelper.showErrorToast(context, 'Something went wrong');
|
ToastHelper.showErrorToast(context, 'Unable to process. Please try again later');
|
||||||
logDebug('Error: $e');
|
logDebug('Error: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -689,10 +738,12 @@ class _MyEmailVerifyState extends State<MyEmailVerify> {
|
|||||||
|
|
||||||
if (_postToken != null && _postToken.isNotEmpty) {
|
if (_postToken != null && _postToken.isNotEmpty) {
|
||||||
ToastHelper.showSuccessToast(context, 'Successfully Logged In');
|
ToastHelper.showSuccessToast(context, 'Successfully Logged In');
|
||||||
|
SessionManager().unlockMobileAppSession();
|
||||||
context.replace('/home');
|
context.replace('/home');
|
||||||
// context.go('/home');
|
// context.go('/home');
|
||||||
// Navigator.pushReplacementNamed(context, 'home');
|
// Navigator.pushReplacementNamed(context, 'home');
|
||||||
} else {
|
} else {
|
||||||
|
SessionManager().unlockMobileAppSession();
|
||||||
context.replace('/empDetails');
|
context.replace('/empDetails');
|
||||||
// context.go('/empDetails');
|
// context.go('/empDetails');
|
||||||
// Navigator.pushReplacementNamed(context, 'empDetails');
|
// Navigator.pushReplacementNamed(context, 'empDetails');
|
||||||
@ -716,12 +767,48 @@ class _MyEmailVerifyState extends State<MyEmailVerify> {
|
|||||||
context.go('/pinSettingPage');
|
context.go('/pinSettingPage');
|
||||||
// Navigator.pushReplacementNamed(context, 'pinSettingPage');
|
// Navigator.pushReplacementNamed(context, 'pinSettingPage');
|
||||||
}
|
}
|
||||||
|
} else if (response.statusCode == 429) {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
Map<String, dynamic> data = json.decode(response.body);
|
||||||
|
if (data.containsKey('error')) {
|
||||||
|
final message = data['error']['message'];
|
||||||
|
ToastHelper.showErrorToast(context, message);
|
||||||
|
} else {
|
||||||
|
final message = data['message'];
|
||||||
|
ToastHelper.showErrorToast(context, message);
|
||||||
|
}
|
||||||
|
} else if (response.statusCode == 403) {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
Map<String, dynamic> data = json.decode(response.body);
|
||||||
|
if (data.containsKey('error')) {
|
||||||
|
final message = data['error']['message'];
|
||||||
|
ToastHelper.showErrorToast(context, message);
|
||||||
|
} else {
|
||||||
|
final message = data['message'];
|
||||||
|
ToastHelper.showErrorToast(context, message);
|
||||||
|
}
|
||||||
|
} else if (response.statusCode == 451) {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
Map<String, dynamic> data = json.decode(response.body);
|
||||||
|
if (data.containsKey('error')) {
|
||||||
|
final message = data['error']['message'];
|
||||||
|
ToastHelper.showErrorToast(context, message);
|
||||||
|
} else {
|
||||||
|
final message = data['message'];
|
||||||
|
ToastHelper.showErrorToast(context, message);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ToastHelper.showErrorToast(context, 'Something went wrong');
|
ToastHelper.showErrorToast(context, 'Unable to process. Please try again later');
|
||||||
throw Exception('Failed to verify pin number');
|
throw Exception('Failed to verify pin number');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ToastHelper.showErrorToast(context, 'Something went wrong');
|
ToastHelper.showErrorToast(context, 'Unable to process. Please try again later');
|
||||||
logDebug('Error: $e');
|
logDebug('Error: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -760,11 +847,11 @@ class _MyEmailVerifyState extends State<MyEmailVerify> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ToastHelper.showErrorToast(context, 'Something went wrong');
|
ToastHelper.showErrorToast(context, 'Unable to process. Please try again later');
|
||||||
throw Exception('Failed to verify pin number');
|
throw Exception('Failed to verify pin number');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ToastHelper.showErrorToast(context, 'Something went wrong');
|
ToastHelper.showErrorToast(context, 'Unable to process. Please try again later');
|
||||||
logDebug('Error: $e');
|
logDebug('Error: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -347,7 +347,7 @@ class _empDetailsState extends State<empDetails> {
|
|||||||
gpaDataIsEmpty = 0;
|
gpaDataIsEmpty = 0;
|
||||||
});
|
});
|
||||||
// Handle other status codes
|
// Handle other status codes
|
||||||
ToastHelper.showWarningToast(context, 'Something went wrong');
|
ToastHelper.showWarningToast(context, 'Unable to process. Please try again later');
|
||||||
logDebug('Request failed with status: ${response['code']}');
|
logDebug('Request failed with status: ${response['code']}');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -1161,6 +1161,181 @@ class _empDetailsState extends State<empDetails> {
|
|||||||
|
|
||||||
logDebug("Normalized relationship list: $relationshipObjects");
|
logDebug("Normalized relationship list: $relationshipObjects");
|
||||||
|
|
||||||
|
Future<bool> showEditConfirmationDialog(
|
||||||
|
BuildContext dialogContext,
|
||||||
|
Map<String, dynamic> oldData,
|
||||||
|
Map<String, dynamic> newData,
|
||||||
|
bool relationshipEnabled,
|
||||||
|
) async {
|
||||||
|
final oldRelationship = (oldData["relationship"] ?? "").toString().trim();
|
||||||
|
final oldName = (oldData["name"] ?? "").toString().trim();
|
||||||
|
final oldDob = (oldData["dob"] ?? "").toString().trim();
|
||||||
|
|
||||||
|
final newRelationship = (newData["relationship"] ?? "").toString().trim();
|
||||||
|
final newName = (newData["memberName"] ?? "").toString().trim();
|
||||||
|
final newDob = (newData["dateOfBirth"] ?? "").toString().trim();
|
||||||
|
|
||||||
|
final List<Map<String, String>> changedFields = [];
|
||||||
|
if (oldRelationship != newRelationship) {
|
||||||
|
changedFields.add({
|
||||||
|
"label": "Relationship",
|
||||||
|
"old": oldRelationship,
|
||||||
|
"new": newRelationship,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (oldName != newName) {
|
||||||
|
changedFields.add({
|
||||||
|
"label": "Member Name",
|
||||||
|
"old": oldName,
|
||||||
|
"new": newName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (oldDob != newDob) {
|
||||||
|
changedFields.add({
|
||||||
|
"label": "Date of Birth",
|
||||||
|
"old": oldDob,
|
||||||
|
"new": newDob,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changedFields.isEmpty) {
|
||||||
|
ToastHelper.showWarningToast(
|
||||||
|
dialogContext, "No changes detected to save");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Map<String, String>> reviewFields = [];
|
||||||
|
if (relationshipEnabled || oldRelationship != newRelationship) {
|
||||||
|
reviewFields.add({
|
||||||
|
"label": "Relationship",
|
||||||
|
"old": oldRelationship,
|
||||||
|
"new": newRelationship,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
reviewFields.add({
|
||||||
|
"label": "Member Name",
|
||||||
|
"old": oldName,
|
||||||
|
"new": newName,
|
||||||
|
});
|
||||||
|
reviewFields.add({
|
||||||
|
"label": "Date of Birth",
|
||||||
|
"old": oldDob,
|
||||||
|
"new": newDob,
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool? confirmed = await showDialog<bool>(
|
||||||
|
context: dialogContext,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (confirmContext) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(
|
||||||
|
"Confirm Changes",
|
||||||
|
style: GoogleFonts.poppins(fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
content: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Please review old and new values before saving:",
|
||||||
|
style: GoogleFonts.poppins(fontSize: 13),
|
||||||
|
),
|
||||||
|
SizedBox(height: 12),
|
||||||
|
...reviewFields.map((field) {
|
||||||
|
final oldValue = (field["old"] ?? '').trim().isEmpty
|
||||||
|
? '-'
|
||||||
|
: (field["old"] ?? '');
|
||||||
|
final newValue = (field["new"] ?? '').trim().isEmpty
|
||||||
|
? '-'
|
||||||
|
: (field["new"] ?? '');
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 12),
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFF9F9F9),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(color: const Color(0xFFE0E0E0)),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
field["label"] ?? '',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: const Color(0xFFE26728),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 13,
|
||||||
|
color: const Color(0xFF232526),
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
const TextSpan(
|
||||||
|
text: "OLD: ",
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: Color(0xFFD32F2F),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(text: oldValue),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 13,
|
||||||
|
color: const Color(0xFF232526),
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
const TextSpan(
|
||||||
|
text: "NEW: ",
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: Color(0xFF2E7D32),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(text: newValue),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(confirmContext, false),
|
||||||
|
child: Text("Cancel"),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Color(0xFFE26728),
|
||||||
|
),
|
||||||
|
onPressed: () => Navigator.pop(confirmContext, true),
|
||||||
|
child: Text("Confirm", style: TextStyle(color: Colors.white)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return confirmed == true;
|
||||||
|
}
|
||||||
|
|
||||||
// Reset UI Fields
|
// Reset UI Fields
|
||||||
_relationShipController.clear();
|
_relationShipController.clear();
|
||||||
_memberNameController.clear();
|
_memberNameController.clear();
|
||||||
@ -1323,7 +1498,7 @@ class _empDetailsState extends State<empDetails> {
|
|||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Color(0xFFE26728),
|
backgroundColor: Color(0xFFE26728),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
if (_memberNameController.text.isEmpty ||
|
if (_memberNameController.text.isEmpty ||
|
||||||
_dobController.text.isEmpty ||
|
_dobController.text.isEmpty ||
|
||||||
dropdownValue == null) {
|
dropdownValue == null) {
|
||||||
@ -1342,6 +1517,18 @@ class _empDetailsState extends State<empDetails> {
|
|||||||
logDebug(floaterData);
|
logDebug(floaterData);
|
||||||
logDebug(selectedFloaterData);
|
logDebug(selectedFloaterData);
|
||||||
|
|
||||||
|
if (action == "Edit" && floaterData != null) {
|
||||||
|
final shouldProceed = await showEditConfirmationDialog(
|
||||||
|
context,
|
||||||
|
floaterData,
|
||||||
|
formData,
|
||||||
|
action != "Edit",
|
||||||
|
);
|
||||||
|
if (!shouldProceed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
saveFamilyMemberDetails(
|
saveFamilyMemberDetails(
|
||||||
formData,
|
formData,
|
||||||
selectedFloaterData ?? floaterData ?? {},
|
selectedFloaterData ?? floaterData ?? {},
|
||||||
|
|||||||
@ -32,6 +32,7 @@ class _empReviewDetailsState extends State<empReviewDetails> {
|
|||||||
List<String> typeOrder = [
|
List<String> typeOrder = [
|
||||||
'GPA',
|
'GPA',
|
||||||
'GMC',
|
'GMC',
|
||||||
|
'GMC - OPD',
|
||||||
'GMC - Parents',
|
'GMC - Parents',
|
||||||
'GMC - Topup',
|
'GMC - Topup',
|
||||||
'GMC - Topup(Parents)'
|
'GMC - Topup(Parents)'
|
||||||
@ -111,10 +112,13 @@ class _empReviewDetailsState extends State<empReviewDetails> {
|
|||||||
dynamic topUpSiSelectedSI;
|
dynamic topUpSiSelectedSI;
|
||||||
dynamic topUpSumInsured;
|
dynamic topUpSumInsured;
|
||||||
dynamic topUpFloaterTextHeading;
|
dynamic topUpFloaterTextHeading;
|
||||||
|
dynamic opdFloaterTextHeading;
|
||||||
dynamic topUpECardDownload;
|
dynamic topUpECardDownload;
|
||||||
bool topUpIsPremiumSummary = false;
|
bool topUpIsPremiumSummary = false;
|
||||||
dynamic topUpDisclaimer;
|
dynamic topUpDisclaimer;
|
||||||
|
|
||||||
int showHideTopUpCard = 0;
|
int showHideTopUpCard = 0;
|
||||||
|
int showHideOpdCard = 0;
|
||||||
int showHideAddOnsCard = 0;
|
int showHideAddOnsCard = 0;
|
||||||
int showHideTopUpParentCard = 0;
|
int showHideTopUpParentCard = 0;
|
||||||
dynamic iAgreeForAddOn = [];
|
dynamic iAgreeForAddOn = [];
|
||||||
@ -133,7 +137,25 @@ class _empReviewDetailsState extends State<empReviewDetails> {
|
|||||||
dynamic topUpParentFloterTextHeading;
|
dynamic topUpParentFloterTextHeading;
|
||||||
dynamic topUpParentDisclaimer;
|
dynamic topUpParentDisclaimer;
|
||||||
// int topUpParentOpenForEnrollment = 0;
|
// int topUpParentOpenForEnrollment = 0;
|
||||||
|
dynamic opdPolicies = [];
|
||||||
|
dynamic opdClientPolicyId;
|
||||||
|
dynamic opdSlabRates;
|
||||||
|
dynamic opdFamilyFloater;
|
||||||
|
dynamic opdPolicyName;
|
||||||
|
dynamic opdPolicyType;
|
||||||
|
dynamic opdMappedFamilyFloatersSi;
|
||||||
|
dynamic opdECardDownload;
|
||||||
|
int opdOpenForEnrollment = 0;
|
||||||
|
dynamic opdTypeName;
|
||||||
|
dynamic opdSiValue;
|
||||||
|
dynamic opdSiSelectedSI;
|
||||||
|
dynamic opdSiPremiumValue;
|
||||||
|
dynamic opdSiPremiumGst;
|
||||||
|
dynamic opdSiTotalAmt;
|
||||||
|
bool opdIsPremiumSummary = false;
|
||||||
|
dynamic opdDisclaimer;
|
||||||
int activeSiData = 0;
|
int activeSiData = 0;
|
||||||
|
int activeOPDSiData = 0;
|
||||||
int activeSiParentData = 0;
|
int activeSiParentData = 0;
|
||||||
int activeDependentData = 0;
|
int activeDependentData = 0;
|
||||||
int activePremiumSummary = 0;
|
int activePremiumSummary = 0;
|
||||||
@ -215,6 +237,7 @@ class _empReviewDetailsState extends State<empReviewDetails> {
|
|||||||
getClientLogoAndDetails();
|
getClientLogoAndDetails();
|
||||||
}
|
}
|
||||||
getGmcSiTopUp();
|
getGmcSiTopUp();
|
||||||
|
getGmcOPD();
|
||||||
getGmcSiParentTopUp();
|
getGmcSiParentTopUp();
|
||||||
getGmcDependentAddOns();
|
getGmcDependentAddOns();
|
||||||
getGpaEmpPolicyDetails(enrollmentEmpPrimaryId);
|
getGpaEmpPolicyDetails(enrollmentEmpPrimaryId);
|
||||||
@ -268,6 +291,11 @@ class _empReviewDetailsState extends State<empReviewDetails> {
|
|||||||
return intValue != 0;
|
return intValue != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _isOpenForEnrollment(dynamic value) {
|
||||||
|
if (value is int) return value == 1;
|
||||||
|
return value?.toString() == '1';
|
||||||
|
}
|
||||||
|
|
||||||
void primarySummary() async {
|
void primarySummary() async {
|
||||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
if (prefs.containsKey('siData')) {
|
if (prefs.containsKey('siData')) {
|
||||||
@ -286,6 +314,21 @@ class _empReviewDetailsState extends State<empReviewDetails> {
|
|||||||
topUpSiTotalAmt = firstSiData['topUpSiTotalAmt'];
|
topUpSiTotalAmt = firstSiData['topUpSiTotalAmt'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (prefs.containsKey('opdData')) {
|
||||||
|
activeOPDSiData = 1;
|
||||||
|
String? opdDataJson = prefs.getString('opdData');
|
||||||
|
logDebug('siDataJson');
|
||||||
|
logDebug(opdDataJson);
|
||||||
|
List<dynamic> siDataList = jsonDecode(opdDataJson!);
|
||||||
|
if (siDataList.isNotEmpty) {
|
||||||
|
// Access the first map in the list
|
||||||
|
Map<String, dynamic> firstSiData = siDataList.first;
|
||||||
|
// Get the value of topUpSiPremiumValue
|
||||||
|
opdSiPremiumValue = firstSiData['opdSiPremiumValue'];
|
||||||
|
opdSiPremiumGst = firstSiData['opdSiPremiumGst'];
|
||||||
|
opdSiTotalAmt = firstSiData['opdSiTotalAmt'];
|
||||||
|
}
|
||||||
|
}
|
||||||
if (prefs.containsKey('siParentData')) {
|
if (prefs.containsKey('siParentData')) {
|
||||||
activeSiParentData = 1;
|
activeSiParentData = 1;
|
||||||
String? siParentDataJson = prefs.getString('siParentData');
|
String? siParentDataJson = prefs.getString('siParentData');
|
||||||
@ -327,7 +370,8 @@ class _empReviewDetailsState extends State<empReviewDetails> {
|
|||||||
logDebug(addOnsDependentTotalAmt);
|
logDebug(addOnsDependentTotalAmt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (prefs.containsKey('siData') &&
|
|
||||||
|
if (prefs.containsKey('siData') && prefs.containsKey('opdData') &&
|
||||||
prefs.containsKey('siParentData') &&
|
prefs.containsKey('siParentData') &&
|
||||||
prefs.containsKey('dependentData')) {
|
prefs.containsKey('dependentData')) {
|
||||||
activePremiumSummary = 0;
|
activePremiumSummary = 0;
|
||||||
@ -343,9 +387,9 @@ class _empReviewDetailsState extends State<empReviewDetails> {
|
|||||||
logDebug('gmcTotalAmt: $gmcTotalAmt');
|
logDebug('gmcTotalAmt: $gmcTotalAmt');
|
||||||
|
|
||||||
if (topUpSiTotalAmt != null) totalPayableAmt += topUpSiTotalAmt;
|
if (topUpSiTotalAmt != null) totalPayableAmt += topUpSiTotalAmt;
|
||||||
|
if (opdSiTotalAmt != null) totalPayableAmt += opdSiTotalAmt;
|
||||||
if (topUpParentSiTotalAmt != null) totalPayableAmt += topUpParentSiTotalAmt;
|
if (topUpParentSiTotalAmt != null) totalPayableAmt += topUpParentSiTotalAmt;
|
||||||
if (addOnsDependentTotalAmt != null)
|
if (addOnsDependentTotalAmt != null) totalPayableAmt += addOnsDependentTotalAmt;
|
||||||
totalPayableAmt += addOnsDependentTotalAmt;
|
|
||||||
|
|
||||||
logDebug('Final Total Payable Amount: $totalPayableAmt');
|
logDebug('Final Total Payable Amount: $totalPayableAmt');
|
||||||
}
|
}
|
||||||
@ -423,7 +467,7 @@ class _empReviewDetailsState extends State<empReviewDetails> {
|
|||||||
|
|
||||||
for (var item in gpaPolicies) {
|
for (var item in gpaPolicies) {
|
||||||
// Extract disclaimer value from each object
|
// Extract disclaimer value from each object
|
||||||
if (item['OpenForEnrollment'] == '1') {
|
if (_isOpenForEnrollment(item['OpenForEnrollment'])) {
|
||||||
dynamic disclaimer = item['disclaimer'];
|
dynamic disclaimer = item['disclaimer'];
|
||||||
if (disclaimer != null && disclaimer.isNotEmpty) {
|
if (disclaimer != null && disclaimer.isNotEmpty) {
|
||||||
// Temporary list to hold new entries
|
// Temporary list to hold new entries
|
||||||
@ -472,7 +516,7 @@ class _empReviewDetailsState extends State<empReviewDetails> {
|
|||||||
gpaDataIsEmpty = 0;
|
gpaDataIsEmpty = 0;
|
||||||
});
|
});
|
||||||
// Handle other status codes
|
// Handle other status codes
|
||||||
ToastHelper.showWarningToast(context, 'Something went wrong');
|
ToastHelper.showWarningToast(context, 'Unable to process. Please try again later');
|
||||||
logDebug('Request failed with status: ${response['code']}');
|
logDebug('Request failed with status: ${response['code']}');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -508,7 +552,7 @@ class _empReviewDetailsState extends State<empReviewDetails> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (var item in gmcPolicies) {
|
for (var item in gmcPolicies) {
|
||||||
if (item['OpenForEnrollment'] == '1') {
|
if (_isOpenForEnrollment(item['OpenForEnrollment'])) {
|
||||||
dynamic disclaimer = item['disclaimer'];
|
dynamic disclaimer = item['disclaimer'];
|
||||||
if (disclaimer != null && disclaimer.isNotEmpty) {
|
if (disclaimer != null && disclaimer.isNotEmpty) {
|
||||||
// Temporary list to hold new entries
|
// Temporary list to hold new entries
|
||||||
@ -632,7 +676,7 @@ class _empReviewDetailsState extends State<empReviewDetails> {
|
|||||||
topUpDisclaimer =
|
topUpDisclaimer =
|
||||||
await topUpSiPolicies['gmc_si_topup']['disclaimer'];
|
await topUpSiPolicies['gmc_si_topup']['disclaimer'];
|
||||||
|
|
||||||
if (topUpOpenForEnrollment == '1' && activeSiData == 1) {
|
if (_isOpenForEnrollment(topUpOpenForEnrollment) && activeSiData == 1) {
|
||||||
if (topUpDisclaimer != null && topUpDisclaimer.isNotEmpty) {
|
if (topUpDisclaimer != null && topUpDisclaimer.isNotEmpty) {
|
||||||
// Temporary list to hold new entries
|
// Temporary list to hold new entries
|
||||||
List<Map<String, dynamic>> newEntries = [];
|
List<Map<String, dynamic>> newEntries = [];
|
||||||
@ -693,6 +737,128 @@ class _empReviewDetailsState extends State<empReviewDetails> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> getGmcOPD() async {
|
||||||
|
try {
|
||||||
|
if (enrollmentClient_id == null ||
|
||||||
|
enrollmentEmpCodeString == null ||
|
||||||
|
enrollmentEmpClientBranchId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final response = await apiService.getGmcSiTopUpToApi(
|
||||||
|
enrollmentClient_id!,
|
||||||
|
enrollmentEmpCodeString!,
|
||||||
|
'GMC-OPD',
|
||||||
|
enrollmentEmpClientBranchId!);
|
||||||
|
if (response['status'] == 'success') {
|
||||||
|
if (response.containsKey('data')) {
|
||||||
|
setState(() {
|
||||||
|
opdPolicies = response['data'];
|
||||||
|
});
|
||||||
|
if (opdPolicies.isNotEmpty) {
|
||||||
|
int _toInt(dynamic value) {
|
||||||
|
if (value is int) return value;
|
||||||
|
if (value is double) return value.toInt();
|
||||||
|
return int.tryParse(value?.toString() ?? '0') ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
final opdNode = opdPolicies['gmc_opd'];
|
||||||
|
opdClientPolicyId = opdNode['client_policy_id'];
|
||||||
|
opdSlabRates = opdNode['SlabRates'];
|
||||||
|
opdFloaterTextHeading = opdNode['floter_text_heading'];
|
||||||
|
opdPolicyName = opdNode['policy_name'];
|
||||||
|
opdPolicyType = opdNode['type'];
|
||||||
|
opdFamilyFloater = opdNode['policy_terms']['family_floater'];
|
||||||
|
opdMappedFamilyFloatersSi =
|
||||||
|
opdNode['family_floaters_of_only_si_array'];
|
||||||
|
opdECardDownload = opdNode['eCardDownload'];
|
||||||
|
opdOpenForEnrollment = _toInt(opdNode['OpenForEnrollment']);
|
||||||
|
opdTypeName = opdNode['type'];
|
||||||
|
opdSiValue = opdNode['family_floaters_of_only_si_value'];
|
||||||
|
logDebug(opdSiValue);
|
||||||
|
|
||||||
|
if (opdSiValue == 0) {
|
||||||
|
showHideOpdCard = 0;
|
||||||
|
} else {
|
||||||
|
showHideOpdCard = 1;
|
||||||
|
}
|
||||||
|
opdSiSelectedSI = opdSiValue != 0 ? opdSiValue.toString() : null;
|
||||||
|
|
||||||
|
// // OPD can have SI value 0 initially; still show card when policy exists.
|
||||||
|
// showHideOpdCard = (opdPolicyName != null ||
|
||||||
|
// opdPolicyType != null ||
|
||||||
|
// (opdMappedFamilyFloatersSi is List &&
|
||||||
|
// opdMappedFamilyFloatersSi.isNotEmpty))
|
||||||
|
// ? 1
|
||||||
|
// : 0;
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
opdSiPremiumValue =
|
||||||
|
_toInt(opdNode['family_floaters_of_only_si_premium_value']);
|
||||||
|
opdSiPremiumGst =
|
||||||
|
_toInt(opdNode['family_floaters_of_only_si_gst_value']);
|
||||||
|
opdSiTotalAmt = opdSiPremiumValue + opdSiPremiumGst;
|
||||||
|
opdIsPremiumSummary =
|
||||||
|
isPremiumEnabled(opdNode?['is_premium_summery']);
|
||||||
|
});
|
||||||
|
|
||||||
|
opdDisclaimer = opdNode['disclaimer'];
|
||||||
|
|
||||||
|
if (_isOpenForEnrollment(opdOpenForEnrollment) &&
|
||||||
|
activeOPDSiData == 1) {
|
||||||
|
if (opdDisclaimer != null && opdDisclaimer.isNotEmpty) {
|
||||||
|
// Temporary list to hold new entries
|
||||||
|
List<Map<String, dynamic>> newEntries = [];
|
||||||
|
|
||||||
|
if (opdDisclaimer is String) {
|
||||||
|
// Wrap string in a map with id and checked
|
||||||
|
newEntries.add({
|
||||||
|
'id': allDisclaimer.length + 1, // Unique ID based on length
|
||||||
|
'text': opdDisclaimer,
|
||||||
|
'checked': false,
|
||||||
|
'type': opdTypeName
|
||||||
|
});
|
||||||
|
} else if (opdDisclaimer is List) {
|
||||||
|
// Convert each string in the list to a map with id and checked
|
||||||
|
newEntries.addAll(
|
||||||
|
List<Map<String, dynamic>>.from(
|
||||||
|
opdDisclaimer.map((text) => {
|
||||||
|
'id': allDisclaimer.length + newEntries.length + 1,
|
||||||
|
'text': text,
|
||||||
|
'checked': false,
|
||||||
|
'type': opdTypeName
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
logDebug('Unexpected type for disclaimer');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new entries to the main list, ensuring no duplicate texts
|
||||||
|
newEntries.forEach((entry) {
|
||||||
|
if (!allDisclaimer
|
||||||
|
.any((item) => item['text'] == entry['text'])) {
|
||||||
|
allDisclaimer.add(entry);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sort disclaimers after adding
|
||||||
|
_sortDisclaimerByTypeOrder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logDebug('No data found in the response');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logDebug('API request failed with status: ${response['status']}');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logDebug('Request failed with status: ${response['code']}');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logDebug('Exception occurred: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> getGmcSiParentTopUp() async {
|
Future<void> getGmcSiParentTopUp() async {
|
||||||
try {
|
try {
|
||||||
if (enrollmentClient_id == null ||
|
if (enrollmentClient_id == null ||
|
||||||
@ -786,7 +952,7 @@ class _empReviewDetailsState extends State<empReviewDetails> {
|
|||||||
await topUpSiParentPolicies['gmc_si_parent_topup']
|
await topUpSiParentPolicies['gmc_si_parent_topup']
|
||||||
['disclaimer'];
|
['disclaimer'];
|
||||||
|
|
||||||
if (topUpParentOpenForEnrollment == '1' &&
|
if (_isOpenForEnrollment(topUpParentOpenForEnrollment) &&
|
||||||
activeSiParentData == 1) {
|
activeSiParentData == 1) {
|
||||||
if (topUpParentDisclaimer != null &&
|
if (topUpParentDisclaimer != null &&
|
||||||
topUpParentDisclaimer.isNotEmpty) {
|
topUpParentDisclaimer.isNotEmpty) {
|
||||||
@ -936,7 +1102,7 @@ setState(() {
|
|||||||
logDebug(
|
logDebug(
|
||||||
'addOnsDependentOpenForEnrollment $addOnsDependentOpenForEnrollment');
|
'addOnsDependentOpenForEnrollment $addOnsDependentOpenForEnrollment');
|
||||||
logDebug('activeDependentData $activeDependentData');
|
logDebug('activeDependentData $activeDependentData');
|
||||||
if (addOnsDependentOpenForEnrollment == '1' &&
|
if (_isOpenForEnrollment(addOnsDependentOpenForEnrollment) &&
|
||||||
activeDependentData == 1) {
|
activeDependentData == 1) {
|
||||||
logDebug('addOnsDependentOpenForEnrollment');
|
logDebug('addOnsDependentOpenForEnrollment');
|
||||||
if (addOnsDependentDisclaimer != null &&
|
if (addOnsDependentDisclaimer != null &&
|
||||||
@ -1025,6 +1191,18 @@ setState(() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (opdMappedFamilyFloatersSi != null) {
|
||||||
|
dynamic getSiTrueObjects = opdMappedFamilyFloatersSi
|
||||||
|
.where((element) => element['is_value_exist'] == true)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
logDebug('getSiTrueObjects : $getSiTrueObjects');
|
||||||
|
|
||||||
|
if (getSiTrueObjects.isNotEmpty) {
|
||||||
|
iAgreeForAddOn.add(int.parse(opdClientPolicyId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (topUpParentMappedFamilyFloatersSi != null) {
|
if (topUpParentMappedFamilyFloatersSi != null) {
|
||||||
dynamic getSiTrueObjects = topUpParentMappedFamilyFloatersSi
|
dynamic getSiTrueObjects = topUpParentMappedFamilyFloatersSi
|
||||||
.where((element) => element['is_value_exist'] == true)
|
.where((element) => element['is_value_exist'] == true)
|
||||||
@ -1172,7 +1350,7 @@ setState(() {
|
|||||||
color: Colors.red,
|
color: Colors.red,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
content: Text('Something went wrong. Enrollemnt not completed'),
|
content: Text('Unable to process. Please try again later. Enrollemnt not completed'),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
TextButton(
|
TextButton(
|
||||||
child: Text(
|
child: Text(
|
||||||
@ -1251,6 +1429,7 @@ setState(() {
|
|||||||
double total = 0.0; // Initialize to avoid unwanted accumulation
|
double total = 0.0; // Initialize to avoid unwanted accumulation
|
||||||
|
|
||||||
if (topUpSiTotalAmt != null) total += topUpSiTotalAmt!;
|
if (topUpSiTotalAmt != null) total += topUpSiTotalAmt!;
|
||||||
|
if (opdSiTotalAmt != null) total += opdSiTotalAmt!;
|
||||||
if (topUpParentSiTotalAmt != null) total += topUpParentSiTotalAmt!;
|
if (topUpParentSiTotalAmt != null) total += topUpParentSiTotalAmt!;
|
||||||
if (addOnsDependentTotalAmt != null) total += addOnsDependentTotalAmt!;
|
if (addOnsDependentTotalAmt != null) total += addOnsDependentTotalAmt!;
|
||||||
if (gpaTotalAmt != null) total += gpaTotalAmt!;
|
if (gpaTotalAmt != null) total += gpaTotalAmt!;
|
||||||
@ -1633,7 +1812,7 @@ setState(() {
|
|||||||
gmchasPremiumSummary ||
|
gmchasPremiumSummary ||
|
||||||
topUpIsPremiumSummary ||
|
topUpIsPremiumSummary ||
|
||||||
topUpParentIsPremiumSummary ||
|
topUpParentIsPremiumSummary ||
|
||||||
addOnsDependentIsPremiumSummary)...[
|
addOnsDependentIsPremiumSummary || opdIsPremiumSummary)...[
|
||||||
Card(
|
Card(
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
@ -1716,7 +1895,7 @@ setState(() {
|
|||||||
CrossAxisAlignment.start,
|
CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'₹${allTotalSum()}/Year',
|
'₹${formatAmount(allTotalSum())}/Year',
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
style:
|
style:
|
||||||
GoogleFonts.poppins(
|
GoogleFonts.poppins(
|
||||||
@ -1936,7 +2115,7 @@ setState(() {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'₹ ${addOnsDependentSumInsured != null ? addOnsDependentSumInsured : 'NA'}',
|
'₹ ${formatNullableAmount(addOnsDependentSumInsured)}',
|
||||||
textAlign:
|
textAlign:
|
||||||
TextAlign.right,
|
TextAlign.right,
|
||||||
style: GoogleFonts
|
style: GoogleFonts
|
||||||
@ -2193,7 +2372,7 @@ setState(() {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'₹ ${topUpSumInsured != null ? topUpSumInsured : 'NA'}',
|
'₹ ${formatNullableAmount(topUpSumInsured)}',
|
||||||
textAlign:
|
textAlign:
|
||||||
TextAlign.right,
|
TextAlign.right,
|
||||||
style: GoogleFonts
|
style: GoogleFonts
|
||||||
@ -2271,6 +2450,187 @@ setState(() {
|
|||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
SizedBox(height: showHideTopUpCard == 1 ? 16 : 0),
|
SizedBox(height: showHideTopUpCard == 1 ? 16 : 0),
|
||||||
|
if (showHideOpdCard == 1)
|
||||||
|
Card(
|
||||||
|
elevation: 0,
|
||||||
|
color: Colors.white,
|
||||||
|
child: Padding(
|
||||||
|
padding: Responsive.isDesktop(context)
|
||||||
|
? EdgeInsets.all(30)
|
||||||
|
: EdgeInsets.all(10),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Color(0xFFFFF1DD),
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
),
|
||||||
|
padding: Responsive.isDesktop(context)
|
||||||
|
? EdgeInsets.only(
|
||||||
|
top: 15,
|
||||||
|
bottom: 15,
|
||||||
|
left: 25,
|
||||||
|
right: 25)
|
||||||
|
: EdgeInsets.only(
|
||||||
|
top: 10,
|
||||||
|
bottom: 10,
|
||||||
|
left: 10,
|
||||||
|
right: 10),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: Responsive.isDesktop(context)
|
||||||
|
? 9
|
||||||
|
: 7,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
Responsive.isDesktop(
|
||||||
|
context)
|
||||||
|
? opdPolicyName
|
||||||
|
: opdPolicyType,
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
style:
|
||||||
|
GoogleFonts.poppins(
|
||||||
|
fontSize: Responsive
|
||||||
|
.isDesktop(
|
||||||
|
context)
|
||||||
|
? 20
|
||||||
|
: 14,
|
||||||
|
fontWeight:
|
||||||
|
FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (opdECardDownload != null)
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
_launchURL(
|
||||||
|
opdECardDownload);
|
||||||
|
},
|
||||||
|
child: MouseRegion(
|
||||||
|
cursor:
|
||||||
|
SystemMouseCursors
|
||||||
|
.click,
|
||||||
|
child: Icon(
|
||||||
|
Icons.file_download,
|
||||||
|
color: Color(
|
||||||
|
0xFFE26728),
|
||||||
|
size: 25,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: Responsive.isDesktop(
|
||||||
|
context)
|
||||||
|
? 3
|
||||||
|
: 5,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
opdFloaterTextHeading ?? '',
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
style:
|
||||||
|
GoogleFonts.poppins(
|
||||||
|
fontSize: Responsive
|
||||||
|
.isDesktop(
|
||||||
|
context)
|
||||||
|
? 18
|
||||||
|
: 13,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'₹ ${formatNullableAmount(opdSiValue)}',
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
style:
|
||||||
|
GoogleFonts.poppins(
|
||||||
|
fontSize: Responsive
|
||||||
|
.isDesktop(
|
||||||
|
context)
|
||||||
|
? 20
|
||||||
|
: 14,
|
||||||
|
fontWeight:
|
||||||
|
FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 15),
|
||||||
|
Container(
|
||||||
|
padding: Responsive.isDesktop(context)
|
||||||
|
? EdgeInsets.only(
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 15,
|
||||||
|
right: 15)
|
||||||
|
: EdgeInsets.only(
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 5,
|
||||||
|
right: 5),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
|
children:
|
||||||
|
(opdMappedFamilyFloatersSi ??
|
||||||
|
[])
|
||||||
|
.where((item) =>
|
||||||
|
item['is_value_exist'] ==
|
||||||
|
true)
|
||||||
|
.map<Widget>((item) {
|
||||||
|
Map<String, dynamic> data =
|
||||||
|
item['data'];
|
||||||
|
String formattedDate =
|
||||||
|
data['dob'] ?? '';
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.arrow_right,
|
||||||
|
color:
|
||||||
|
Color(0xFFE26728)),
|
||||||
|
SizedBox(
|
||||||
|
width: Responsive.isDesktop(
|
||||||
|
context)
|
||||||
|
? 10
|
||||||
|
: 5),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'${data['name']} ~ ${data['relationship']} ~ DOB : $formattedDate',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: Responsive
|
||||||
|
.isDesktop(
|
||||||
|
context)
|
||||||
|
? 18
|
||||||
|
: 14,
|
||||||
|
color: Color(0xFF232526),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
SizedBox(height: showHideOpdCard == 1 ? 16 : 0),
|
||||||
if (showHideTopUpParentCard == 1)
|
if (showHideTopUpParentCard == 1)
|
||||||
Card(
|
Card(
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
@ -2460,7 +2820,7 @@ setState(() {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'₹ ${topUpParentSiValue != null ? topUpParentSiValue : 'NA'}',
|
'₹ ${formatNullableAmount(topUpParentSiValue)}',
|
||||||
textAlign:
|
textAlign:
|
||||||
TextAlign.right,
|
TextAlign.right,
|
||||||
style: GoogleFonts
|
style: GoogleFonts
|
||||||
@ -2552,7 +2912,7 @@ setState(() {
|
|||||||
gmchasPremiumSummary ||
|
gmchasPremiumSummary ||
|
||||||
topUpIsPremiumSummary ||
|
topUpIsPremiumSummary ||
|
||||||
topUpParentIsPremiumSummary ||
|
topUpParentIsPremiumSummary ||
|
||||||
addOnsDependentIsPremiumSummary)...[
|
addOnsDependentIsPremiumSummary || opdIsPremiumSummary)...[
|
||||||
Container(
|
Container(
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment:
|
crossAxisAlignment:
|
||||||
@ -2804,6 +3164,102 @@ setState(() {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
|
if (activeOPDSiData == 1 &&
|
||||||
|
opdIsPremiumSummary)
|
||||||
|
TableRow(
|
||||||
|
children: [
|
||||||
|
TableCell(
|
||||||
|
child: Padding(
|
||||||
|
padding:
|
||||||
|
EdgeInsets
|
||||||
|
.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
(Responsive.isDesktop(
|
||||||
|
context)
|
||||||
|
? (opdPolicyName ??
|
||||||
|
'Default OPD Name')
|
||||||
|
: 'GMC - OPD'),
|
||||||
|
textAlign:
|
||||||
|
TextAlign
|
||||||
|
.start,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize:
|
||||||
|
Responsive.isDesktop(context)
|
||||||
|
? 14
|
||||||
|
: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TableCell(
|
||||||
|
child: Padding(
|
||||||
|
padding:
|
||||||
|
EdgeInsets
|
||||||
|
.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
'₹$opdSiPremiumValue/Year',
|
||||||
|
textAlign: Responsive
|
||||||
|
.isDesktop(
|
||||||
|
context)
|
||||||
|
? TextAlign
|
||||||
|
.start
|
||||||
|
: TextAlign
|
||||||
|
.start,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize:
|
||||||
|
Responsive.isDesktop(context)
|
||||||
|
? 14
|
||||||
|
: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TableCell(
|
||||||
|
child: Padding(
|
||||||
|
padding:
|
||||||
|
EdgeInsets
|
||||||
|
.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
'₹$opdSiPremiumGst/-',
|
||||||
|
textAlign: Responsive
|
||||||
|
.isDesktop(
|
||||||
|
context)
|
||||||
|
? TextAlign
|
||||||
|
.start
|
||||||
|
: TextAlign
|
||||||
|
.start,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize:
|
||||||
|
Responsive.isDesktop(context)
|
||||||
|
? 14
|
||||||
|
: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TableCell(
|
||||||
|
child: Padding(
|
||||||
|
padding:
|
||||||
|
EdgeInsets
|
||||||
|
.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
'₹$opdSiTotalAmt/Year',
|
||||||
|
textAlign: Responsive
|
||||||
|
.isDesktop(
|
||||||
|
context)
|
||||||
|
? TextAlign
|
||||||
|
.start
|
||||||
|
: TextAlign
|
||||||
|
.start,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize:
|
||||||
|
Responsive.isDesktop(context)
|
||||||
|
? 14
|
||||||
|
: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
if (activeSiParentData ==
|
if (activeSiParentData ==
|
||||||
1 &&
|
1 &&
|
||||||
topUpParentIsPremiumSummary)
|
topUpParentIsPremiumSummary)
|
||||||
@ -3051,7 +3507,7 @@ setState(() {
|
|||||||
.end,
|
.end,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'₹${allTotalSum()}/Year',
|
'₹${formatAmount(allTotalSum())}/Year',
|
||||||
textAlign:
|
textAlign:
|
||||||
TextAlign.right,
|
TextAlign.right,
|
||||||
style: GoogleFonts
|
style: GoogleFonts
|
||||||
@ -3198,17 +3654,12 @@ setState(() {
|
|||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
// Remove siData, siParentData, and dependentData from local storage
|
// Remove siData, siParentData, and dependentData from local storage
|
||||||
final SharedPreferences
|
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
prefs =
|
|
||||||
await SharedPreferences
|
|
||||||
.getInstance();
|
|
||||||
prefs.remove('siData');
|
prefs.remove('siData');
|
||||||
prefs
|
prefs.remove('siParentData');
|
||||||
.remove('siParentData');
|
prefs.remove('dependentData');
|
||||||
prefs.remove(
|
prefs.remove('opdData');
|
||||||
'dependentData');
|
context.go('/addOnsDetails');
|
||||||
context
|
|
||||||
.go('/addOnsDetails');
|
|
||||||
// Navigator.pushNamed(
|
// Navigator.pushNamed(
|
||||||
// context, 'addOnsDetails');
|
// context, 'addOnsDetails');
|
||||||
},
|
},
|
||||||
@ -3569,7 +4020,7 @@ setState(() {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'₹ ${gpaSumInsured != null ? gpaSumInsured : 'NA'}',
|
'₹ ${formatNullableAmount(gpaSumInsured)}',
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
fontSize:
|
fontSize:
|
||||||
@ -3766,7 +4217,7 @@ setState(() {
|
|||||||
gmchasPremiumSummary ||
|
gmchasPremiumSummary ||
|
||||||
topUpIsPremiumSummary ||
|
topUpIsPremiumSummary ||
|
||||||
topUpParentIsPremiumSummary ||
|
topUpParentIsPremiumSummary ||
|
||||||
addOnsDependentIsPremiumSummary)
|
addOnsDependentIsPremiumSummary || opdIsPremiumSummary)
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -3829,6 +4280,15 @@ setState(() {
|
|||||||
: value.toStringAsFixed(2);
|
: value.toStringAsFixed(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String formatNullableAmount(dynamic value, {String fallback = 'NA'}) {
|
||||||
|
if (value == null) return fallback;
|
||||||
|
final numericValue = value is num
|
||||||
|
? value.toDouble()
|
||||||
|
: double.tryParse(value.toString());
|
||||||
|
if (numericValue == null) return value.toString();
|
||||||
|
return formatAmount(numericValue);
|
||||||
|
}
|
||||||
|
|
||||||
List<Widget> generateGmcCards(List<dynamic> data) {
|
List<Widget> generateGmcCards(List<dynamic> data) {
|
||||||
List<Widget> cards = [];
|
List<Widget> cards = [];
|
||||||
// gmcEnrollmentStatus = isEnrollmentOpen(data);
|
// gmcEnrollmentStatus = isEnrollmentOpen(data);
|
||||||
@ -3996,7 +4456,7 @@ setState(() {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'₹ ${gmcSumInsured != null ? gmcSumInsured : 'NA'}',
|
'₹ ${formatNullableAmount(gmcSumInsured)}',
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
fontSize:
|
fontSize:
|
||||||
@ -4368,7 +4828,7 @@ setState(() {
|
|||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(8.0),
|
padding: EdgeInsets.all(8.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
'₹${(policy["si_premium_value"] ?? 0) + (policy["si_gst_value"] ?? 0)}/-',
|
'₹${(policy["si_premium_value"] ?? 0) + (policy["si_gst_value"] ?? 0)}/Year',
|
||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: Responsive.isDesktop(context) ? 14 : 12),
|
fontSize: Responsive.isDesktop(context) ? 14 : 12),
|
||||||
@ -4444,7 +4904,7 @@ setState(() {
|
|||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(8.0),
|
padding: EdgeInsets.all(8.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
'₹${(policy["family_floaters_of_dependent_and_si_premium_value"] ?? 0) + (policy["family_floaters_of_dependent_and_gst_value"] ?? 0)}/-',
|
'₹${(policy["family_floaters_of_dependent_and_si_premium_value"] ?? 0) + (policy["family_floaters_of_dependent_and_gst_value"] ?? 0)}/Year',
|
||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: Responsive.isDesktop(context) ? 14 : 12),
|
fontSize: Responsive.isDesktop(context) ? 14 : 12),
|
||||||
|
|||||||
7
lib/pages/helpers/browser_href.dart
Normal file
7
lib/pages/helpers/browser_href.dart
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import 'browser_href_stub.dart'
|
||||||
|
if (dart.library.html) 'browser_href_web.dart' as impl;
|
||||||
|
|
||||||
|
String currentBrowserHref() => impl.currentBrowserHref();
|
||||||
|
|
||||||
|
void stripPayloadParamFromBrowserUrl() =>
|
||||||
|
impl.stripPayloadParamFromBrowserUrl();
|
||||||
3
lib/pages/helpers/browser_href_stub.dart
Normal file
3
lib/pages/helpers/browser_href_stub.dart
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
String currentBrowserHref() => '';
|
||||||
|
|
||||||
|
void stripPayloadParamFromBrowserUrl() {}
|
||||||
22
lib/pages/helpers/browser_href_web.dart
Normal file
22
lib/pages/helpers/browser_href_web.dart
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import 'dart:html' as html;
|
||||||
|
|
||||||
|
String currentBrowserHref() => html.window.location.href;
|
||||||
|
|
||||||
|
/// Removes `payload` from the query string so a failed SSO return does not
|
||||||
|
/// re-trigger handling when [GoRouter] rebuilds `/login`.
|
||||||
|
void stripPayloadParamFromBrowserUrl() {
|
||||||
|
final u = Uri.parse(html.window.location.href);
|
||||||
|
if (!u.queryParameters.containsKey('payload')) return;
|
||||||
|
final m = Map<String, String>.from(u.queryParameters);
|
||||||
|
m.remove('payload');
|
||||||
|
final clean = Uri(
|
||||||
|
scheme: u.scheme,
|
||||||
|
userInfo: u.userInfo.isEmpty ? null : u.userInfo,
|
||||||
|
host: u.host.isEmpty ? null : u.host,
|
||||||
|
port: u.hasPort ? u.port : null,
|
||||||
|
path: u.path,
|
||||||
|
queryParameters: m.isEmpty ? null : m,
|
||||||
|
fragment: u.fragment,
|
||||||
|
);
|
||||||
|
html.window.history.replaceState(null, '', clean.toString());
|
||||||
|
}
|
||||||
4
lib/pages/helpers/sso_same_tab_redirect.dart
Normal file
4
lib/pages/helpers/sso_same_tab_redirect.dart
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import 'sso_same_tab_redirect_stub.dart'
|
||||||
|
if (dart.library.html) 'sso_same_tab_redirect_web.dart' as impl;
|
||||||
|
|
||||||
|
void ssoRedirectSameTab(String url) => impl.ssoRedirectSameTab(url);
|
||||||
2
lib/pages/helpers/sso_same_tab_redirect_stub.dart
Normal file
2
lib/pages/helpers/sso_same_tab_redirect_stub.dart
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/// Non-web: caller should not invoke same-tab redirect.
|
||||||
|
void ssoRedirectSameTab(String url) {}
|
||||||
6
lib/pages/helpers/sso_same_tab_redirect_web.dart
Normal file
6
lib/pages/helpers/sso_same_tab_redirect_web.dart
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import 'dart:html' as html;
|
||||||
|
|
||||||
|
/// Full-page navigation so Microsoft IdP and SAML complete in the same tab.
|
||||||
|
void ssoRedirectSameTab(String url) {
|
||||||
|
html.window.location.assign(url);
|
||||||
|
}
|
||||||
2444
lib/pages/login.dart
2444
lib/pages/login.dart
File diff suppressed because it is too large
Load Diff
274
lib/pages/login_saml_auth.dart
Normal file
274
lib/pages/login_saml_auth.dart
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:jwt_decode/jwt_decode.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
import '../config/environment.dart';
|
||||||
|
import '../customAppBar/toastHelper.dart';
|
||||||
|
import '../logger.dart';
|
||||||
|
import 'service/SessionManager.dart';
|
||||||
|
import 'service/TokenService.dart';
|
||||||
|
|
||||||
|
/// Where the IdP should return the browser after SAML (web).
|
||||||
|
///
|
||||||
|
/// Uses **`/login`** (not `/sso-login`) so static hosts that do not rewrite
|
||||||
|
/// unknown paths to `index.html` still return **200** and Flutter can read
|
||||||
|
/// `?payload=`. `/sso-login` is still supported via `web/index.html` redirect.
|
||||||
|
String buildWebSsoRelayStateUrl() {
|
||||||
|
if (!kIsWeb) return '';
|
||||||
|
final origin = Uri.base.origin;
|
||||||
|
final frag = Uri.base.fragment;
|
||||||
|
// Hash routing: e.g. `http://host/#/login` → return with payload on `#/login`
|
||||||
|
if (frag.startsWith('/')) {
|
||||||
|
return '$origin/#/login';
|
||||||
|
}
|
||||||
|
final href = Environment.baseHref;
|
||||||
|
if (href.isEmpty || href == '/') {
|
||||||
|
return '$origin/login';
|
||||||
|
}
|
||||||
|
final trimmed =
|
||||||
|
href.endsWith('/') ? href.substring(0, href.length - 1) : href;
|
||||||
|
return '$origin$trimmed/login';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decodes the `payload` query value (base64 / base64url) into a JSON map.
|
||||||
|
Map<String, dynamic> decodeSsoPayloadQueryValue(String encoded) {
|
||||||
|
var normalized = encoded.trim().replaceAll('-', '+').replaceAll('_', '/');
|
||||||
|
final mod = normalized.length % 4;
|
||||||
|
if (mod > 0) {
|
||||||
|
normalized = normalized.padRight(normalized.length + (4 - mod), '=');
|
||||||
|
}
|
||||||
|
final jsonStr = utf8.decode(base64.decode(normalized));
|
||||||
|
final decoded = json.decode(jsonStr);
|
||||||
|
if (decoded is Map<String, dynamic>) return decoded;
|
||||||
|
if (decoded is Map) {
|
||||||
|
return Map<String, dynamic>.from(decoded);
|
||||||
|
}
|
||||||
|
throw const FormatException('SSO payload is not a JSON object');
|
||||||
|
}
|
||||||
|
|
||||||
|
String? readPayloadFromRouterUri(Uri uri) {
|
||||||
|
final direct = uri.queryParameters['payload'];
|
||||||
|
if (direct != null && direct.isNotEmpty) return direct;
|
||||||
|
final frag = uri.fragment;
|
||||||
|
if (frag.contains('?')) {
|
||||||
|
final queryPart = frag.split('?').last;
|
||||||
|
return Uri.splitQueryString(queryPart)['payload'];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Locates the raw `payload` query value on a SAML return URL, e.g.
|
||||||
|
/// `https://staging.nhanceindia.in/sso-login?payload=eyJ...` or
|
||||||
|
/// `https://host/#/sso-login?payload=eyJ...`.
|
||||||
|
String? readSsoPayloadEncodedFromReturnUrl(String url) {
|
||||||
|
final trimmed = url.trim();
|
||||||
|
if (trimmed.isEmpty) return null;
|
||||||
|
|
||||||
|
final uri = Uri.tryParse(trimmed);
|
||||||
|
if (uri != null) {
|
||||||
|
final fromUri = readPayloadFromRouterUri(uri);
|
||||||
|
if (fromUri != null && fromUri.isNotEmpty) {
|
||||||
|
return fromUri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: stop before `#` so `...?payload=eyJ...#/login` does not swallow the hash.
|
||||||
|
final ampOrQ = RegExp(r'(?:\?|&)payload=([^&#]+)');
|
||||||
|
final match = ampOrQ.firstMatch(trimmed);
|
||||||
|
if (match != null) {
|
||||||
|
final raw = match.group(1);
|
||||||
|
if (raw != null && raw.isNotEmpty) {
|
||||||
|
return Uri.decodeQueryComponent(raw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final hashIdx = trimmed.indexOf('#');
|
||||||
|
if (hashIdx != -1) {
|
||||||
|
final afterHash = trimmed.substring(hashIdx + 1);
|
||||||
|
if (afterHash.contains('payload=')) {
|
||||||
|
final queryPart =
|
||||||
|
afterHash.contains('?') ? afterHash.split('?').last : afterHash;
|
||||||
|
final fromFrag = Uri.splitQueryString(queryPart)['payload'];
|
||||||
|
if (fromFrag != null && fromFrag.isNotEmpty) {
|
||||||
|
return fromFrag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic>? decodeSsoPayloadFromUrl(String url) {
|
||||||
|
try {
|
||||||
|
final raw = readSsoPayloadEncodedFromReturnUrl(url);
|
||||||
|
if (raw == null || raw.isEmpty) return null;
|
||||||
|
return decodeSsoPayloadQueryValue(raw);
|
||||||
|
} catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// JWT claim maps for pre-enrollment (`data`) and post-enrollment tokens when
|
||||||
|
/// those fields hold a JWT string (typically after successful SAML).
|
||||||
|
class SsoPayloadJwtDecode {
|
||||||
|
final Map<String, dynamic>? preTokenClaims;
|
||||||
|
final Map<String, dynamic>? postTokenClaims;
|
||||||
|
|
||||||
|
const SsoPayloadJwtDecode({
|
||||||
|
this.preTokenClaims,
|
||||||
|
this.postTokenClaims,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
SsoPayloadJwtDecode decodeJwtClaimsFromSsoPayloadMap(
|
||||||
|
Map<String, dynamic> data) {
|
||||||
|
Map<String, dynamic>? preClaims;
|
||||||
|
Map<String, dynamic>? postClaims;
|
||||||
|
|
||||||
|
final pre = data['data'];
|
||||||
|
if (pre is String && pre.contains('.')) {
|
||||||
|
try {
|
||||||
|
preClaims = Jwt.parseJwt(pre);
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
final rawPe = data['post_enrollment'];
|
||||||
|
if (rawPe is Map) {
|
||||||
|
final pt = rawPe['data'];
|
||||||
|
if (pt is String && pt.contains('.')) {
|
||||||
|
try {
|
||||||
|
postClaims = Jwt.parseJwt(pt);
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SsoPayloadJwtDecode(
|
||||||
|
preTokenClaims: preClaims,
|
||||||
|
postTokenClaims: postClaims,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String prettySsoJsonForConsole(Object? value) {
|
||||||
|
if (value == null) return 'null';
|
||||||
|
try {
|
||||||
|
return const JsonEncoder.withIndent(' ').convert(value);
|
||||||
|
} catch (_) {
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes JWT claim maps to stdout / the browser DevTools console. Unlike
|
||||||
|
/// [logDebug], this is not filtered by [LoggerConfig.minLevel].
|
||||||
|
void printSsoDecodedTokensToConsole(SsoPayloadJwtDecode jwt) {
|
||||||
|
print(
|
||||||
|
'[SSO] decodedToken (pre / data field):\n${prettySsoJsonForConsole(jwt.preTokenClaims)}');
|
||||||
|
print(
|
||||||
|
'[SSO] decodedToken (post_enrollment.data):\n${prettySsoJsonForConsole(jwt.postTokenClaims)}');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _persistClientBranding() async {
|
||||||
|
final session = SessionManager();
|
||||||
|
final pre = await TokenService.getPreToken();
|
||||||
|
if (pre == null || pre.isEmpty) return;
|
||||||
|
|
||||||
|
final url = Uri.parse(Environment.apiUrlEnrollment +
|
||||||
|
'getClientDetails?post_client_id=${session.client_id}&post_branch_id=${session.empClientBranchId}&pre_client_id=${session.enrollmentClient_id}&pre_branch_id=${session.enrollmentEmpClientBranchId}');
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await http.get(
|
||||||
|
url,
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer $pre',
|
||||||
|
'APP-SIGNATURE':
|
||||||
|
'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (response.statusCode != 200) return;
|
||||||
|
final data = json.decode(response.body) as Map<String, dynamic>;
|
||||||
|
if (!data.containsKey('data')) return;
|
||||||
|
final clientDetails = data['data'];
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setString(
|
||||||
|
'clientLogo', clientDetails['client']['client_logo'] as String? ?? '');
|
||||||
|
await prefs.setString(
|
||||||
|
'clientName', clientDetails['client']['client_name'] as String? ?? '');
|
||||||
|
await prefs.setString('addon_subheading',
|
||||||
|
clientDetails['client']['addon_subheading'] as String? ?? '');
|
||||||
|
} catch (e) {
|
||||||
|
logDebug('SSO client branding fetch failed: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Completes login after SAML callback payload has been decoded to a map.
|
||||||
|
Future<void> completeLoginFromSsoPayloadMap(
|
||||||
|
BuildContext context, Map<String, dynamic> data) async {
|
||||||
|
logDebug('SSO payload map: $data');
|
||||||
|
print('[SSO] payload map (decoded from URL):\n${prettySsoJsonForConsole(data)}');
|
||||||
|
final jwt = decodeJwtClaimsFromSsoPayloadMap(data);
|
||||||
|
printSsoDecodedTokensToConsole(jwt);
|
||||||
|
if (jwt.preTokenClaims != null) {
|
||||||
|
logDebug('SSO decodedToken (pre / data): ${jwt.preTokenClaims}');
|
||||||
|
}
|
||||||
|
if (jwt.postTokenClaims != null) {
|
||||||
|
logDebug('SSO decodedToken (post_enrollment / data): ${jwt.postTokenClaims}');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data['status']?.toString() != 'success') {
|
||||||
|
final msg = data['message']?.toString() ?? 'SSO sign-in was not successful';
|
||||||
|
if (context.mounted) ToastHelper.showErrorToast(context, msg);
|
||||||
|
if (context.mounted) context.go('/login');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? preToken;
|
||||||
|
if (data['data'] is String) {
|
||||||
|
preToken = data['data'] as String;
|
||||||
|
}
|
||||||
|
String? postToken;
|
||||||
|
Map<String, dynamic>? postEnrollment;
|
||||||
|
final rawPe = data['post_enrollment'];
|
||||||
|
if (rawPe is Map) {
|
||||||
|
postEnrollment = Map<String, dynamic>.from(rawPe);
|
||||||
|
}
|
||||||
|
if (postEnrollment != null && postEnrollment['data'] is String) {
|
||||||
|
postToken = postEnrollment['data'] as String;
|
||||||
|
}
|
||||||
|
|
||||||
|
await TokenService.saveTokens(preToken: preToken, postToken: postToken);
|
||||||
|
|
||||||
|
if (postEnrollment != null &&
|
||||||
|
postEnrollment['status']?.toString() == 'success' &&
|
||||||
|
postToken != null &&
|
||||||
|
postToken.isNotEmpty) {
|
||||||
|
await SessionManager().initializeFromPostToken(postToken);
|
||||||
|
}
|
||||||
|
if (data['status']?.toString() == 'success' &&
|
||||||
|
preToken != null &&
|
||||||
|
preToken.isNotEmpty) {
|
||||||
|
await SessionManager().initializeFromPreToken(preToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _persistClientBranding();
|
||||||
|
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
|
if (postToken != null && postToken.isNotEmpty) {
|
||||||
|
ToastHelper.showSuccessToast(context, 'Successfully Logged In');
|
||||||
|
SessionManager().unlockMobileAppSession();
|
||||||
|
context.go('/home');
|
||||||
|
} else {
|
||||||
|
SessionManager().unlockMobileAppSession();
|
||||||
|
context.go('/empDetails');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> completeLoginFromSsoPayloadString(
|
||||||
|
BuildContext context, String encodedPayload) async {
|
||||||
|
final data = decodeSsoPayloadQueryValue(encodedPayload);
|
||||||
|
await completeLoginFromSsoPayloadMap(context, data);
|
||||||
|
}
|
||||||
86
lib/pages/login_saml_webview_screen.dart
Normal file
86
lib/pages/login_saml_webview_screen.dart
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:webview_flutter/webview_flutter.dart';
|
||||||
|
|
||||||
|
import 'login_saml_auth.dart';
|
||||||
|
|
||||||
|
/// Mobile: in-app WebView for SAML entry; when URL contains `payload=`, decodes and pops.
|
||||||
|
class LoginSamlWebViewScreen extends StatefulWidget {
|
||||||
|
final String initialUrl;
|
||||||
|
|
||||||
|
const LoginSamlWebViewScreen({super.key, required this.initialUrl});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<LoginSamlWebViewScreen> createState() => _LoginSamlWebViewScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoginSamlWebViewScreenState extends State<LoginSamlWebViewScreen> {
|
||||||
|
late final WebViewController _controller;
|
||||||
|
bool _finished = false;
|
||||||
|
|
||||||
|
void _tryFinishWithUrl(String url) {
|
||||||
|
if (_finished || !mounted) return;
|
||||||
|
if (!url.contains('payload=')) return;
|
||||||
|
final decoded = decodeSsoPayloadFromUrl(url);
|
||||||
|
if (decoded != null) {
|
||||||
|
_finished = true;
|
||||||
|
Navigator.of(context).pop(decoded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _tryFinishFromHttpError(HttpResponseError error) {
|
||||||
|
if (_finished || !mounted) return;
|
||||||
|
final status = error.response?.statusCode;
|
||||||
|
if (status == null) return;
|
||||||
|
if (status < 400) return;
|
||||||
|
final reqUri = error.request?.uri;
|
||||||
|
final resUri = error.response?.uri;
|
||||||
|
if (reqUri != null) {
|
||||||
|
_tryFinishWithUrl(reqUri.toString());
|
||||||
|
}
|
||||||
|
if (!_finished && resUri != null) {
|
||||||
|
_tryFinishWithUrl(resUri.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_controller = WebViewController()
|
||||||
|
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||||
|
..setNavigationDelegate(
|
||||||
|
NavigationDelegate(
|
||||||
|
onNavigationRequest: (NavigationRequest request) {
|
||||||
|
if (!_finished && request.url.contains('payload=')) {
|
||||||
|
_tryFinishWithUrl(request.url);
|
||||||
|
if (_finished) return NavigationDecision.prevent;
|
||||||
|
}
|
||||||
|
return NavigationDecision.navigate;
|
||||||
|
},
|
||||||
|
onPageStarted: (String url) => _tryFinishWithUrl(url),
|
||||||
|
onPageFinished: (String url) => _tryFinishWithUrl(url),
|
||||||
|
onUrlChange: (UrlChange change) {
|
||||||
|
final url = change.url;
|
||||||
|
if (url != null && url.isNotEmpty) {
|
||||||
|
_tryFinishWithUrl(url);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onHttpError: _tryFinishFromHttpError,
|
||||||
|
onWebResourceError: (WebResourceError error) {
|
||||||
|
final url = error.url;
|
||||||
|
if (url != null && url.isNotEmpty) {
|
||||||
|
_tryFinishWithUrl(url);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
..loadRequest(Uri.parse(widget.initialUrl));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('Sign in with SSO')),
|
||||||
|
body: WebViewWidget(controller: _controller),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1043,7 +1043,7 @@ class _claimsState extends State<claims> {
|
|||||||
Text(
|
Text(
|
||||||
isRetail ? insurerShortName : policyName,
|
isRetail ? insurerShortName : policyName,
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: Responsive.isDesktop(context) ? 18 : 14,
|
fontSize: Responsive.isDesktop(context) ? 18 : 11,
|
||||||
fontWeight: FontWeight.w600),
|
fontWeight: FontWeight.w600),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -1065,7 +1065,7 @@ class _claimsState extends State<claims> {
|
|||||||
Text(
|
Text(
|
||||||
isRetail ? policyType : policyStatus,
|
isRetail ? policyType : policyStatus,
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: Responsive.isDesktop(context) ? 18 : 14,
|
fontSize: Responsive.isDesktop(context) ? 18 : 11,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: isRetail
|
color: isRetail
|
||||||
? Colors.black
|
? Colors.black
|
||||||
@ -1093,7 +1093,7 @@ class _claimsState extends State<claims> {
|
|||||||
Text(
|
Text(
|
||||||
isRetail ? vehicleNo : "₹ $siValue",
|
isRetail ? vehicleNo : "₹ $siValue",
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: Responsive.isDesktop(context) ? 18 : 14,
|
fontSize: Responsive.isDesktop(context) ? 18 : 11,
|
||||||
fontWeight: FontWeight.w600),
|
fontWeight: FontWeight.w600),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -222,7 +222,7 @@ class _helpState extends State<help> {
|
|||||||
// trackClaimsClosedList = [];
|
// trackClaimsClosedList = [];
|
||||||
// });
|
// });
|
||||||
// } else {
|
// } else {
|
||||||
// ToastHelper.showErrorToast(context, 'Something went wrong');
|
// ToastHelper.showErrorToast(context, 'Unable to process. Please try again later');
|
||||||
// logDebug('API request failed with status: ${response['status']}');
|
// logDebug('API request failed with status: ${response['status']}');
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
@ -266,7 +266,7 @@ class _helpState extends State<help> {
|
|||||||
// setState(() {
|
// setState(() {
|
||||||
// isLoading = false;
|
// isLoading = false;
|
||||||
// });
|
// });
|
||||||
// ToastHelper.showErrorToast(context, 'Something went wrong');
|
// ToastHelper.showErrorToast(context, 'Unable to process. Please try again later');
|
||||||
// logDebug('API request failed with status: ${response['status']}');
|
// logDebug('API request failed with status: ${response['status']}');
|
||||||
// }
|
// }
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@ -236,6 +236,7 @@ class _planclaimsformState extends State<planclaimsform> {
|
|||||||
return {
|
return {
|
||||||
'id': int.parse(memberList['id']),
|
'id': int.parse(memberList['id']),
|
||||||
'name': memberList['name'],
|
'name': memberList['name'],
|
||||||
|
'relationship': memberList['relationship'],
|
||||||
};
|
};
|
||||||
}).toList();
|
}).toList();
|
||||||
logDebug('employeePolicyList : $employeePolicyList');
|
logDebug('employeePolicyList : $employeePolicyList');
|
||||||
@ -566,6 +567,7 @@ class _planclaimsformState extends State<planclaimsform> {
|
|||||||
return {
|
return {
|
||||||
'id': int.parse(memberList['id']),
|
'id': int.parse(memberList['id']),
|
||||||
'name': memberList['name'],
|
'name': memberList['name'],
|
||||||
|
'relationship': memberList['relationship'],
|
||||||
};
|
};
|
||||||
}).toList();
|
}).toList();
|
||||||
logDebug('employeePolicyList : $employeePolicyList');
|
logDebug('employeePolicyList : $employeePolicyList');
|
||||||
@ -753,59 +755,59 @@ class _planclaimsformState extends State<planclaimsform> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ✅ NEW VALIDATION: At least one PDF must be uploaded
|
// ✅ NEW VALIDATION: At least one PDF must be uploaded
|
||||||
// bool hasPdf = uploadedFiles.any((uf) {
|
bool hasPdf = uploadedFiles.any((uf) {
|
||||||
// final ext = uf.file.extension?.toLowerCase() ?? '';
|
final ext = uf.file.extension?.toLowerCase() ?? '';
|
||||||
// return ext == 'pdf';
|
return ext == 'pdf';
|
||||||
// });
|
});
|
||||||
//
|
|
||||||
// if (!hasPdf) {
|
if (!hasPdf) {
|
||||||
// ToastHelper.showErrorToast(context, 'Please upload at least one PDF document');
|
ToastHelper.showErrorToast(context, 'Please upload at least one PDF document');
|
||||||
// setState(() => isLoading = false);
|
setState(() => isLoading = false);
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// Add files
|
// Add files
|
||||||
// for (var uf in uploadedFiles) {
|
|
||||||
// final pf = uf.file;
|
|
||||||
// if (pf.bytes != null) {
|
|
||||||
// request.files.add(http.MultipartFile.fromBytes(
|
|
||||||
// 'claim_docs[]',
|
|
||||||
// pf.bytes!,
|
|
||||||
// filename: pf.name,
|
|
||||||
// ));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Add files — convert images to PDF if needed
|
|
||||||
for (var uf in uploadedFiles) {
|
for (var uf in uploadedFiles) {
|
||||||
final pf = uf.file;
|
final pf = uf.file;
|
||||||
final ext = pf.extension?.toLowerCase() ?? '';
|
|
||||||
|
|
||||||
if (pf.bytes != null) {
|
if (pf.bytes != null) {
|
||||||
Uint8List fileBytes = pf.bytes!;
|
request.files.add(http.MultipartFile.fromBytes(
|
||||||
|
'claim_docs[]',
|
||||||
if (['jpg', 'jpeg', 'png', 'heic'].contains(ext)) {
|
pf.bytes!,
|
||||||
// ✅ Convert image → PDF
|
filename: pf.name,
|
||||||
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();
|
|
||||||
final pdfFileName = pf.name.replaceAll(RegExp(r'\.\w+$'), '.pdf');
|
|
||||||
|
|
||||||
logDebug('📄 Converted image ${pf.name} → PDF ($pdfFileName)');
|
|
||||||
request.files.add(http.MultipartFile.fromBytes(
|
|
||||||
'claim_docs[]', fileBytes, filename: pdfFileName));
|
|
||||||
} else {
|
|
||||||
// ✅ Already a PDF
|
|
||||||
request.files.add(http.MultipartFile.fromBytes(
|
|
||||||
'claim_docs[]', fileBytes, filename: pf.name));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add files — convert images to PDF if needed
|
||||||
|
// for (var uf in uploadedFiles) {
|
||||||
|
// final pf = uf.file;
|
||||||
|
// final ext = pf.extension?.toLowerCase() ?? '';
|
||||||
|
|
||||||
|
// if (pf.bytes != null) {
|
||||||
|
// Uint8List fileBytes = pf.bytes!;
|
||||||
|
|
||||||
|
// if (['jpg', 'jpeg', 'png', 'heic'].contains(ext)) {
|
||||||
|
// // ✅ 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();
|
||||||
|
// final pdfFileName = pf.name.replaceAll(RegExp(r'\.\w+$'), '.pdf');
|
||||||
|
|
||||||
|
// logDebug('📄 Converted image ${pf.name} → PDF ($pdfFileName)');
|
||||||
|
// request.files.add(http.MultipartFile.fromBytes(
|
||||||
|
// 'claim_docs[]', fileBytes, filename: pdfFileName));
|
||||||
|
// } else {
|
||||||
|
// // ✅ Already a PDF
|
||||||
|
// request.files.add(http.MultipartFile.fromBytes(
|
||||||
|
// 'claim_docs[]', fileBytes, filename: pf.name));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
// ✅ Combine all names into a JSON array string
|
// ✅ Combine all names into a JSON array string
|
||||||
final claimDocNames = uploadedFiles.map((uf) => uf.label.trim()).toList();
|
final claimDocNames = uploadedFiles.map((uf) => uf.label.trim()).toList();
|
||||||
|
|||||||
@ -141,7 +141,7 @@ class _raisedTicketHistoryState extends State<raisedTicketHistory> {
|
|||||||
// setState(() {
|
// setState(() {
|
||||||
// isLoading = false;
|
// isLoading = false;
|
||||||
// });
|
// });
|
||||||
// ToastHelper.showErrorToast(context, 'Something went wrong');
|
// ToastHelper.showErrorToast(context, 'Unable to process. Please try again later');
|
||||||
// logDebug('API request failed with status: ${response['status']}');
|
// logDebug('API request failed with status: ${response['status']}');
|
||||||
// }
|
// }
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@ -193,7 +193,7 @@ class _retailClaimFormsState extends State<retailClaimForm> {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logDebug("Retail claim submit ERROR: $e");
|
logDebug("Retail claim submit ERROR: $e");
|
||||||
ToastHelper.showErrorToast(context, "Something went wrong");
|
ToastHelper.showErrorToast(context, "Unable to process. Please try again later");
|
||||||
}
|
}
|
||||||
|
|
||||||
setState(() => isLoading = false);
|
setState(() => isLoading = false);
|
||||||
|
|||||||
@ -212,7 +212,7 @@ class _tickettracklistState extends State<tickettracklist> {
|
|||||||
// setState(() {
|
// setState(() {
|
||||||
// isLoading = false;
|
// isLoading = false;
|
||||||
// });
|
// });
|
||||||
// ToastHelper.showErrorToast(context, 'Something went wrong');
|
// ToastHelper.showErrorToast(context, 'Unable to process. Please try again later');
|
||||||
// logDebug('API request failed with status: ${response['status']}');
|
// logDebug('API request failed with status: ${response['status']}');
|
||||||
// }
|
// }
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@ -156,6 +156,22 @@ class SessionManager {
|
|||||||
|
|
||||||
bool isReady = false;
|
bool isReady = false;
|
||||||
|
|
||||||
|
/// Mobile only: false after a full app restart until login / MPIN succeeds.
|
||||||
|
/// Stays true while the process stays alive (e.g. user only minimized the app).
|
||||||
|
/// Web always behaves as unlocked ([isMobileAppSessionUnlocked] is true).
|
||||||
|
bool _mobileAppSessionUnlocked = false;
|
||||||
|
|
||||||
|
bool get isMobileAppSessionUnlocked =>
|
||||||
|
kIsWeb ? true : _mobileAppSessionUnlocked;
|
||||||
|
|
||||||
|
void unlockMobileAppSession() {
|
||||||
|
if (!kIsWeb) _mobileAppSessionUnlocked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void lockMobileAppSession() {
|
||||||
|
if (!kIsWeb) _mobileAppSessionUnlocked = false;
|
||||||
|
}
|
||||||
|
|
||||||
// ===== Post Enrollment Fields =====
|
// ===== Post Enrollment Fields =====
|
||||||
String? mobileNo;
|
String? mobileNo;
|
||||||
String? empClientBranchId;
|
String? empClientBranchId;
|
||||||
@ -279,5 +295,6 @@ class SessionManager {
|
|||||||
await prefs.clear();
|
await prefs.clear();
|
||||||
await DataManager().clearData();
|
await DataManager().clearData();
|
||||||
await TokenService.clearTokens();
|
await TokenService.clearTokens();
|
||||||
|
lockMobileAppSession();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -120,12 +120,12 @@ import 'package:nhance_app_pwa/logger.dart';
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logDebug('checkLoginPin Something went wrong');
|
logDebug('checkLoginPin Unable to process. Please try again later');
|
||||||
ToastHelper.showErrorToast(context, 'Something went wrong');
|
ToastHelper.showErrorToast(context, 'Unable to process. Please try again later');
|
||||||
throw Exception('Failed to verify pin number');
|
throw Exception('Failed to verify pin number');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ToastHelper.showErrorToast(context, 'Something went wrong');
|
ToastHelper.showErrorToast(context, 'Unable to process. Please try again later');
|
||||||
logDebug('Error: $e');
|
logDebug('Error: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -233,7 +233,7 @@ import 'package:nhance_app_pwa/logger.dart';
|
|||||||
throw Exception('Failed to load data');
|
throw Exception('Failed to load data');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ToastHelper.showErrorToast(context, 'Something went wrong');
|
ToastHelper.showErrorToast(context, 'Unable to process. Please try again later');
|
||||||
context.go('/login');
|
context.go('/login');
|
||||||
logDebug('Error: $e');
|
logDebug('Error: $e');
|
||||||
}
|
}
|
||||||
@ -309,6 +309,7 @@ import 'package:nhance_app_pwa/logger.dart';
|
|||||||
logDebug('Token12345: ${await TokenService.getPostToken()}');
|
logDebug('Token12345: ${await TokenService.getPostToken()}');
|
||||||
logDebug('Token00000');
|
logDebug('Token00000');
|
||||||
// if (context.mounted) {
|
// if (context.mounted) {
|
||||||
|
SessionManager().unlockMobileAppSession();
|
||||||
context.go('/home');
|
context.go('/home');
|
||||||
// }
|
// }
|
||||||
// Navigator.pushReplacementNamed(context, 'home');
|
// Navigator.pushReplacementNamed(context, 'home');
|
||||||
@ -353,6 +354,7 @@ import 'package:nhance_app_pwa/logger.dart';
|
|||||||
// enrollmentEmp_status == 'active') {
|
// enrollmentEmp_status == 'active') {
|
||||||
// Navigator.pushReplacementNamed(context, 'home');
|
// Navigator.pushReplacementNamed(context, 'home');
|
||||||
// } else {
|
// } else {
|
||||||
|
SessionManager().unlockMobileAppSession();
|
||||||
context.go('/empDetails');
|
context.go('/empDetails');
|
||||||
// Navigator.pushReplacementNamed(context, 'empDetails');
|
// Navigator.pushReplacementNamed(context, 'empDetails');
|
||||||
// }
|
// }
|
||||||
@ -370,7 +372,7 @@ import 'package:nhance_app_pwa/logger.dart';
|
|||||||
try {
|
try {
|
||||||
await enterPinApi(_pinController.text);
|
await enterPinApi(_pinController.text);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ToastHelper.showErrorToast(context, 'Something went wrong');
|
ToastHelper.showErrorToast(context, 'Unable to process. Please try again later');
|
||||||
logDebug('Error: $e');
|
logDebug('Error: $e');
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
@ -469,7 +471,7 @@ import 'package:nhance_app_pwa/logger.dart';
|
|||||||
throw Exception('Failed to load data');
|
throw Exception('Failed to load data');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// ToastHelper.showErrorToast(context, 'Something went wrong');
|
// ToastHelper.showErrorToast(context, 'Unable to process. Please try again later');
|
||||||
logDebug('Error: $e');
|
logDebug('Error: $e');
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
|||||||
@ -135,7 +135,7 @@ class _changePinState extends State<changePin> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ToastHelper.showErrorToast(context, 'Something went wrong');
|
ToastHelper.showErrorToast(context, 'Unable to process. Please try again later');
|
||||||
logDebug('Error: $e');
|
logDebug('Error: $e');
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
|||||||
@ -143,9 +143,11 @@ class _pinSettingPageState extends State<pinSettingPage> {
|
|||||||
await _authService.authenticateWithBiometrics();
|
await _authService.authenticateWithBiometrics();
|
||||||
if (biometricEnabled) {
|
if (biometricEnabled) {
|
||||||
if (_postToken != null && _postToken.isNotEmpty) {
|
if (_postToken != null && _postToken.isNotEmpty) {
|
||||||
|
SessionManager().unlockMobileAppSession();
|
||||||
context.go('/home');
|
context.go('/home');
|
||||||
// Navigator.pushReplacementNamed(context, 'home');
|
// Navigator.pushReplacementNamed(context, 'home');
|
||||||
} else {
|
} else {
|
||||||
|
SessionManager().unlockMobileAppSession();
|
||||||
context.go('/empDetails');
|
context.go('/empDetails');
|
||||||
// Navigator.pushReplacementNamed(context, 'empDetails');
|
// Navigator.pushReplacementNamed(context, 'empDetails');
|
||||||
}
|
}
|
||||||
@ -158,9 +160,11 @@ class _pinSettingPageState extends State<pinSettingPage> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (_postToken != null && _postToken.isNotEmpty) {
|
if (_postToken != null && _postToken.isNotEmpty) {
|
||||||
|
SessionManager().unlockMobileAppSession();
|
||||||
context.go('/home');
|
context.go('/home');
|
||||||
// Navigator.pushReplacementNamed(context, 'home');
|
// Navigator.pushReplacementNamed(context, 'home');
|
||||||
} else {
|
} else {
|
||||||
|
SessionManager().unlockMobileAppSession();
|
||||||
context.go('/empDetails');
|
context.go('/empDetails');
|
||||||
// Navigator.pushReplacementNamed(context, 'empDetails');
|
// Navigator.pushReplacementNamed(context, 'empDetails');
|
||||||
}
|
}
|
||||||
@ -194,7 +198,7 @@ class _pinSettingPageState extends State<pinSettingPage> {
|
|||||||
}
|
}
|
||||||
// }
|
// }
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ToastHelper.showErrorToast(context, 'Something went wrong');
|
ToastHelper.showErrorToast(context, 'Unable to process. Please try again later');
|
||||||
print('Error: $e');
|
print('Error: $e');
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
@ -237,9 +241,11 @@ class _pinSettingPageState extends State<pinSettingPage> {
|
|||||||
|
|
||||||
await _authService.saveSkipStatus(0);
|
await _authService.saveSkipStatus(0);
|
||||||
if (_postToken != null && _postToken.isNotEmpty) {
|
if (_postToken != null && _postToken.isNotEmpty) {
|
||||||
|
SessionManager().unlockMobileAppSession();
|
||||||
context.go('/home');
|
context.go('/home');
|
||||||
// Navigator.pushReplacementNamed(context, 'home');
|
// Navigator.pushReplacementNamed(context, 'home');
|
||||||
} else {
|
} else {
|
||||||
|
SessionManager().unlockMobileAppSession();
|
||||||
context.go('/empDetails');
|
context.go('/empDetails');
|
||||||
// Navigator.pushReplacementNamed(context, 'empDetails');
|
// Navigator.pushReplacementNamed(context, 'empDetails');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -163,7 +163,7 @@ class _setPasswordState extends State<setPassword> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
});
|
});
|
||||||
// ToastHelper.showErrorToast(context, 'Something went wrong');
|
// ToastHelper.showErrorToast(context, 'Unable to process. Please try again later');
|
||||||
logDebug('Error: $e');
|
logDebug('Error: $e');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
62
lib/pages/sso_login_return_page.dart
Normal file
62
lib/pages/sso_login_return_page.dart
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
import '../customAppBar/toastHelper.dart';
|
||||||
|
import '../logger.dart';
|
||||||
|
import 'helpers/browser_href.dart';
|
||||||
|
import 'login_saml_auth.dart';
|
||||||
|
|
||||||
|
/// Handles the browser return URL after SAML (query or hash `payload=`).
|
||||||
|
class SsoLoginReturnPage extends StatefulWidget {
|
||||||
|
const SsoLoginReturnPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SsoLoginReturnPage> createState() => _SsoLoginReturnPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SsoLoginReturnPageState extends State<SsoLoginReturnPage> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => _consumePayload());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _consumePayload() async {
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
final routerUri = GoRouterState.of(context).uri;
|
||||||
|
final href = kIsWeb ? currentBrowserHref() : routerUri.toString();
|
||||||
|
|
||||||
|
Map<String, dynamic>? decoded;
|
||||||
|
for (final candidate in <String>[
|
||||||
|
href,
|
||||||
|
if (kIsWeb) Uri.base.toString(),
|
||||||
|
routerUri.toString(),
|
||||||
|
]) {
|
||||||
|
if (candidate.isEmpty) continue;
|
||||||
|
decoded = decodeSsoPayloadFromUrl(candidate);
|
||||||
|
if (decoded != null) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
if (decoded == null) {
|
||||||
|
logDebug('SSO return: no payload in href=$href router=${routerUri.toString()}');
|
||||||
|
ToastHelper.showWarningToast(
|
||||||
|
context, 'SSO return missing or invalid payload');
|
||||||
|
context.go('/login');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logDebug('SSO return: decoded payload (keys)=${decoded.keys.toList()}');
|
||||||
|
await completeLoginFromSsoPayloadMap(context, decoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const Scaffold(
|
||||||
|
body: Center(child: CircularProgressIndicator()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -313,7 +313,7 @@ import 'package:nhance_app_pwa/logger.dart';
|
|||||||
// });
|
// });
|
||||||
// final SharedPreferences prefs = await SharedPreferences.getInstance();
|
// final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
// prefs.clear();
|
// prefs.clear();
|
||||||
// ToastHelper.showWarningToast(context, 'Something went wrong');
|
// ToastHelper.showWarningToast(context, 'Unable to process. Please try again later');
|
||||||
// throw Exception('Failed to verify OTP');
|
// throw Exception('Failed to verify OTP');
|
||||||
// }
|
// }
|
||||||
// } catch (e) {
|
// } catch (e) {
|
||||||
@ -323,7 +323,7 @@ import 'package:nhance_app_pwa/logger.dart';
|
|||||||
// logDebug('Error: $e');
|
// logDebug('Error: $e');
|
||||||
// final SharedPreferences prefs = await SharedPreferences.getInstance();
|
// final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
// prefs.clear();
|
// prefs.clear();
|
||||||
// ToastHelper.showWarningToast(context, 'Something went wrong');
|
// ToastHelper.showWarningToast(context, 'Unable to process. Please try again later');
|
||||||
// // Show a Snackbar if there's an error while verifying OTP
|
// // Show a Snackbar if there's an error while verifying OTP
|
||||||
// logDebug('Failed to verify OTP. Please try again.');
|
// logDebug('Failed to verify OTP. Please try again.');
|
||||||
// }
|
// }
|
||||||
@ -674,12 +674,12 @@ import 'package:nhance_app_pwa/logger.dart';
|
|||||||
// // Navigator.pushReplacementNamed(context, 'pinSettingPage');
|
// // Navigator.pushReplacementNamed(context, 'pinSettingPage');
|
||||||
// }
|
// }
|
||||||
// } else {
|
// } else {
|
||||||
// logDebug('checkLoginPin Something went wrong');
|
// logDebug('checkLoginPin Unable to process. Please try again later');
|
||||||
// ToastHelper.showErrorToast(context, 'Something went wrong');
|
// ToastHelper.showErrorToast(context, 'Unable to process. Please try again later');
|
||||||
// throw Exception('Failed to verify pin number');
|
// throw Exception('Failed to verify pin number');
|
||||||
// }
|
// }
|
||||||
// } catch (e) {
|
// } catch (e) {
|
||||||
// ToastHelper.showErrorToast(context, 'Something went wrong');
|
// ToastHelper.showErrorToast(context, 'Unable to process. Please try again later');
|
||||||
// logDebug('Error: $e');
|
// logDebug('Error: $e');
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|||||||
@ -17,8 +17,8 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
#version: 1.2.41+98
|
#version: 1.2.41+98
|
||||||
version: 1.0.32+38
|
version: 1.0.35+41
|
||||||
#version: 2.0.20+58
|
#version: 2.0.23+61
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.3.3 <4.0.0'
|
sdk: '>=3.3.3 <4.0.0'
|
||||||
@ -101,6 +101,7 @@ flutter:
|
|||||||
# To add assets to your application, add an assets section, like this:
|
# To add assets to your application, add an assets section, like this:
|
||||||
assets:
|
assets:
|
||||||
- assets/
|
- assets/
|
||||||
|
- assets/images/login/
|
||||||
# - images/a_dot_burr.jpeg
|
# - images/a_dot_burr.jpeg
|
||||||
# - images/a_dot_ham.jpeg
|
# - images/a_dot_ham.jpeg
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,22 @@
|
|||||||
|
|
||||||
<base href="$FLUTTER_BASE_HREF">
|
<base href="$FLUTTER_BASE_HREF">
|
||||||
|
|
||||||
|
<!-- If the IdP redirects to /sso-login?payload=…, many CDNs return 404 (no SPA
|
||||||
|
fallback). Send the browser to /login?payload=… so index.html loads. -->
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
try {
|
||||||
|
var path = window.location.pathname || '';
|
||||||
|
var search = window.location.search || '';
|
||||||
|
if (!search || search.indexOf('payload=') === -1) return;
|
||||||
|
if (!/\/sso-login\/?$/.test(path)) return;
|
||||||
|
var origin = window.location.origin;
|
||||||
|
var prefix = path.replace(/\/sso-login\/?$/, '') || '';
|
||||||
|
window.location.replace(origin + prefix + '/login' + search);
|
||||||
|
} catch (e) {}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||||
<meta name="description" content="Nhance Benefits">
|
<meta name="description" content="Nhance Benefits">
|
||||||
@ -47,6 +63,9 @@
|
|||||||
<script src="flutter.js" defer></script>
|
<script src="flutter.js" defer></script>
|
||||||
</head>
|
</head>
|
||||||
<body style="overflow:hidden">
|
<body style="overflow:hidden">
|
||||||
|
<script>
|
||||||
|
try { console.log('[SSO] page URL (before Flutter):', window.location.href); } catch (e) {}
|
||||||
|
</script>
|
||||||
<div id="loading_indicator" class="container overlay">
|
<div id="loading_indicator" class="container overlay">
|
||||||
<img class="indicator" src="assets/nhance-loader.gif" alt="">
|
<img class="indicator" src="assets/nhance-loader.gif" alt="">
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user