Smal Flow
This commit is contained in:
parent
1288a67256
commit
f4e28d2629
@ -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 = '61'
|
flutterVersionCode = '63'
|
||||||
}
|
}
|
||||||
|
|
||||||
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||||
if (flutterVersionName == null) {
|
if (flutterVersionName == null) {
|
||||||
flutterVersionName = '2.0.23'
|
flutterVersionName = '2.0.25'
|
||||||
}
|
}
|
||||||
|
|
||||||
def keystoreProperties = new Properties()
|
def keystoreProperties = new Properties()
|
||||||
|
|||||||
@ -4,6 +4,9 @@ enum Flavor { dev, uat, prod, prod1 }
|
|||||||
class Environment {
|
class Environment {
|
||||||
static Flavor flavor = Flavor.dev; // overwritten by each main_*.dart
|
static Flavor flavor = Flavor.dev; // overwritten by each main_*.dart
|
||||||
|
|
||||||
|
/// Chatbot FAB + window. Set to `true` when ready to enable.
|
||||||
|
static const bool chatbotEnabled = false;
|
||||||
|
|
||||||
static bool get isProd => flavor == Flavor.prod || flavor == Flavor.prod1;
|
static bool get isProd => flavor == Flavor.prod || flavor == Flavor.prod1;
|
||||||
|
|
||||||
static String get apiUrl {
|
static String get apiUrl {
|
||||||
@ -51,12 +54,11 @@ class Environment {
|
|||||||
case Flavor.uat:
|
case Flavor.uat:
|
||||||
return "https://appstage.nhanceindia.in/apex/saml/login";
|
return "https://appstage.nhanceindia.in/apex/saml/login";
|
||||||
case Flavor.prod:
|
case Flavor.prod:
|
||||||
return "https://app.nhanceindia.in/saml/login";
|
return "https://app.nhanceindia.in/enrolment/saml/login";
|
||||||
case Flavor.prod1:
|
case Flavor.prod1:
|
||||||
return "https://app.nhanceindia.in/saml/login";
|
return "https://app.nhanceindia.in/enrolment/saml/login";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static String get baseHref {
|
static String get baseHref {
|
||||||
switch (flavor) {
|
switch (flavor) {
|
||||||
case Flavor.dev:
|
case Flavor.dev:
|
||||||
|
|||||||
@ -11,6 +11,11 @@ import '../pages/service/SessionManager.dart';
|
|||||||
import '../pages/service/TokenService.dart';
|
import '../pages/service/TokenService.dart';
|
||||||
import '../pages/service/popup_helper.dart';
|
import '../pages/service/popup_helper.dart';
|
||||||
class CustomAppBar extends StatefulWidget implements PreferredSizeWidget {
|
class CustomAppBar extends StatefulWidget implements PreferredSizeWidget {
|
||||||
|
/// When true, shows only the client logo (no nav menu).
|
||||||
|
final bool logoOnly;
|
||||||
|
|
||||||
|
const CustomAppBar({super.key, this.logoOnly = false});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_CustomAppBarState createState() => _CustomAppBarState();
|
_CustomAppBarState createState() => _CustomAppBarState();
|
||||||
|
|
||||||
@ -102,7 +107,7 @@ class _CustomAppBarState extends State<CustomAppBar> {
|
|||||||
// Expanded(
|
// Expanded(
|
||||||
// // flex: Responsive.isDesktop(context) ? 1 : 9,
|
// // flex: Responsive.isDesktop(context) ? 1 : 9,
|
||||||
// child:
|
// child:
|
||||||
if(Responsive.isDesktop(context))
|
if (!widget.logoOnly && Responsive.isDesktop(context))
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
22
lib/customAppBar/nhance_loading_screen.dart
Normal file
22
lib/customAppBar/nhance_loading_screen.dart
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Full-screen Nhance loader (same style as splash / home overlays).
|
||||||
|
class NhanceLoadingScreen extends StatelessWidget {
|
||||||
|
const NhanceLoadingScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Container(
|
||||||
|
color: const Color(0x98FFFCE5),
|
||||||
|
child: Center(
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/nhance-loader.gif',
|
||||||
|
height: 60,
|
||||||
|
width: 60,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:nhance_app_pwa/config/environment.dart';
|
||||||
import 'package:nhance_app_pwa/customAppBar/responsive.dart';
|
import 'package:nhance_app_pwa/customAppBar/responsive.dart';
|
||||||
import 'package:nhance_app_pwa/features/chatbot/application/chatbot_controller.dart';
|
import 'package:nhance_app_pwa/features/chatbot/application/chatbot_controller.dart';
|
||||||
import 'package:nhance_app_pwa/features/chatbot/presentation/widgets/chatbot_fab.dart';
|
import 'package:nhance_app_pwa/features/chatbot/presentation/widgets/chatbot_fab.dart';
|
||||||
@ -36,6 +37,10 @@ class _ChatbotHostState extends ConsumerState<ChatbotHost> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
if (!Environment.chatbotEnabled) {
|
||||||
|
return widget.child;
|
||||||
|
}
|
||||||
|
|
||||||
final state = ref.watch(chatbotControllerProvider);
|
final state = ref.watch(chatbotControllerProvider);
|
||||||
final controller = ref.read(chatbotControllerProvider.notifier);
|
final controller = ref.read(chatbotControllerProvider.notifier);
|
||||||
|
|
||||||
|
|||||||
@ -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/login_saml_auth.dart';
|
||||||
import 'package:nhance_app_pwa/pages/sso_login_return_page.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';
|
||||||
@ -47,6 +48,7 @@ import 'package:go_router/go_router.dart';
|
|||||||
|
|
||||||
import 'config/environment.dart';
|
import 'config/environment.dart';
|
||||||
import 'logger.dart';
|
import 'logger.dart';
|
||||||
|
import 'router/app_router.dart';
|
||||||
// import 'dart:html' as html;
|
// import 'dart:html' as html;
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
@ -97,6 +99,10 @@ class _SplashScreenState extends State<SplashScreen> {
|
|||||||
Future<String?> tokenRedirectLogic(
|
Future<String?> tokenRedirectLogic(
|
||||||
BuildContext context, GoRouterState state) async {
|
BuildContext context, GoRouterState state) async {
|
||||||
logDebug('ABCDEFGH');
|
logDebug('ABCDEFGH');
|
||||||
|
|
||||||
|
final ssoLoginRedirect = redirectLoginWithSsoPayload(state);
|
||||||
|
if (ssoLoginRedirect != null) return ssoLoginRedirect;
|
||||||
|
|
||||||
const guestRoutes = [
|
const guestRoutes = [
|
||||||
'/login',
|
'/login',
|
||||||
'/sso-login',
|
'/sso-login',
|
||||||
@ -104,6 +110,8 @@ Future<String?> tokenRedirectLogic(
|
|||||||
'/mailVerify',
|
'/mailVerify',
|
||||||
'/pinPage',
|
'/pinPage',
|
||||||
'/splash',
|
'/splash',
|
||||||
|
'/privacypolicy',
|
||||||
|
'/termsofuse',
|
||||||
];
|
];
|
||||||
|
|
||||||
final hasToken = await TokenService.hasValidToken();
|
final hasToken = await TokenService.hasValidToken();
|
||||||
@ -116,6 +124,10 @@ Future<String?> tokenRedirectLogic(
|
|||||||
|
|
||||||
if (hasToken) {
|
if (hasToken) {
|
||||||
await session.restoreSession();
|
await session.restoreSession();
|
||||||
|
final destination = await TokenService.defaultRouteForStoredTokens();
|
||||||
|
if (location == '/home' && destination == '/empDetails') {
|
||||||
|
return '/empDetails';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!kIsWeb && hasToken) {
|
if (!kIsWeb && hasToken) {
|
||||||
@ -123,12 +135,12 @@ Future<String?> tokenRedirectLogic(
|
|||||||
if (reauth != null) return reauth;
|
if (reauth != null) return reauth;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logged in on a guest route → home (only when mobile session is unlocked).
|
// Logged in on a guest route → home or enrollment (pre-only).
|
||||||
if (hasToken && guestRoutes.contains(location)) {
|
if (hasToken && guestRoutes.contains(location)) {
|
||||||
if (!kIsWeb && !session.isMobileAppSessionUnlocked) {
|
if (!kIsWeb && !session.isMobileAppSessionUnlocked) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return '/home';
|
return await TokenService.defaultRouteForStoredTokens();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasToken && guestRoutes.contains(location)) {
|
if (!hasToken && guestRoutes.contains(location)) {
|
||||||
@ -157,6 +169,8 @@ Future<String?> _mobileReauthRedirectIfNeeded(String location) async {
|
|||||||
'/sso-login',
|
'/sso-login',
|
||||||
'/pinSettingPage',
|
'/pinSettingPage',
|
||||||
'/changePin',
|
'/changePin',
|
||||||
|
'/privacypolicy',
|
||||||
|
'/termsofuse',
|
||||||
};
|
};
|
||||||
final session = SessionManager();
|
final session = SessionManager();
|
||||||
if (session.isMobileAppSessionUnlocked) return null;
|
if (session.isMobileAppSessionUnlocked) return null;
|
||||||
@ -371,6 +385,8 @@ Future<void> startApp() async {
|
|||||||
await tokenRedirectLogic(context, state),
|
await tokenRedirectLogic(context, state),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
registerAppGoRouter(router);
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
ProviderScope(
|
ProviderScope(
|
||||||
child: MyApp(router: router),
|
child: MyApp(router: router),
|
||||||
|
|||||||
@ -1364,14 +1364,10 @@ class _MyEmailVerifyState extends State<MyEmailVerify> {
|
|||||||
// ? _size.height * 0.1
|
// ? _size.height * 0.1
|
||||||
// : _size.height * 0.2,
|
// : _size.height * 0.2,
|
||||||
// ),
|
// ),
|
||||||
|
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: Responsive.isDesktop(context)
|
height: 15,
|
||||||
? _size.height * 0.2
|
|
||||||
: _size.height * 0.3,
|
|
||||||
),
|
),
|
||||||
// SizedBox(
|
|
||||||
// height: _size.height * 0.1,
|
|
||||||
// ),
|
|
||||||
Container(
|
Container(
|
||||||
alignment: Responsive.isDesktop(context) ? Alignment.center : Alignment.bottomCenter,
|
alignment: Responsive.isDesktop(context) ? Alignment.center : Alignment.bottomCenter,
|
||||||
padding:
|
padding:
|
||||||
@ -1411,6 +1407,11 @@ class _MyEmailVerifyState extends State<MyEmailVerify> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: Responsive.isDesktop(context)
|
||||||
|
? _size.height * 0.2
|
||||||
|
: _size.height * 0.3,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import '../../../config/environment.dart';
|
import '../../../config/environment.dart';
|
||||||
@ -526,10 +525,7 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _clearLocalStorageAndRedirect() async {
|
Future<void> _clearLocalStorageAndRedirect() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
if (!context.mounted) return;
|
||||||
await prefs.clear();
|
await TokenService().logout(context);
|
||||||
// Assuming you have access to the context
|
|
||||||
ToastHelper.showErrorToast(context, 'Session Out');
|
|
||||||
Navigator.pushNamed(context, 'phone');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
// import 'package:firebase_auth/firebase_auth.dart';
|
// import 'package:firebase_auth/firebase_auth.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
@ -14,6 +15,7 @@ import 'dart:io';
|
|||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import '../config/environment.dart';
|
import '../config/environment.dart';
|
||||||
|
import '../customAppBar/nhance_loading_screen.dart';
|
||||||
import '../customAppBar/responsive.dart';
|
import '../customAppBar/responsive.dart';
|
||||||
import '../customAppBar/toastHelper.dart';
|
import '../customAppBar/toastHelper.dart';
|
||||||
import '../models/platform_helper_mobile.dart'
|
import '../models/platform_helper_mobile.dart'
|
||||||
@ -56,12 +58,14 @@ class _loginState extends State<login> {
|
|||||||
late String _verificationId;
|
late String _verificationId;
|
||||||
int? _resendToken;
|
int? _resendToken;
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
|
bool _isLoadingSAML = false;
|
||||||
bool isEmailFieldVisible = false;
|
bool isEmailFieldVisible = false;
|
||||||
bool _obscurePassword = true;
|
bool _obscurePassword = true;
|
||||||
bool _resetObscurePassword = true;
|
bool _resetObscurePassword = true;
|
||||||
bool _obscureConfirmPassword = true;
|
bool _obscureConfirmPassword = true;
|
||||||
late SessionManager session;
|
late SessionManager session;
|
||||||
Map<String, dynamic>? _ssoReturnPayloadCaptured;
|
Map<String, dynamic>? _ssoReturnPayloadCaptured;
|
||||||
|
bool _isProcessingSsoReturn = false;
|
||||||
bool clickedForgotPassword = false;
|
bool clickedForgotPassword = false;
|
||||||
bool resetPasswordEnable = false;
|
bool resetPasswordEnable = false;
|
||||||
bool otpFieldShow = false;
|
bool otpFieldShow = false;
|
||||||
@ -87,12 +91,30 @@ class _loginState extends State<login> {
|
|||||||
bool get isPasswordValid =>
|
bool get isPasswordValid =>
|
||||||
hasMinLength && hasUpperLower && hasNumber && hasSpecialChar;
|
hasMinLength && hasUpperLower && hasNumber && hasSpecialChar;
|
||||||
|
|
||||||
|
final TapGestureRecognizer _privacyPolicyTap = TapGestureRecognizer();
|
||||||
|
final TapGestureRecognizer _termsOfUseTap = TapGestureRecognizer();
|
||||||
|
|
||||||
|
static final TextStyle _legalLinkStyle = GoogleFonts.poppins(
|
||||||
|
color: const Color(0xFF00989E),
|
||||||
|
fontSize: 9,
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
countryController.text = "+91";
|
|
||||||
super.initState();
|
super.initState();
|
||||||
|
countryController.text = "+91";
|
||||||
|
_privacyPolicyTap.onTap = () {
|
||||||
|
if (mounted) context.go('/privacypolicy');
|
||||||
|
};
|
||||||
|
_termsOfUseTap.onTap = () {
|
||||||
|
if (mounted) context.go('/termsofuse');
|
||||||
|
};
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
_captureSsoPayloadFromBrowserUrl();
|
_captureSsoPayloadFromBrowserUrl();
|
||||||
|
if (_ssoReturnPayloadCaptured != null) {
|
||||||
|
_isProcessingSsoReturn = true;
|
||||||
|
}
|
||||||
WidgetsBinding.instance
|
WidgetsBinding.instance
|
||||||
.addPostFrameCallback((_) => _tryConsumeSsoPayloadFromReturnUrl());
|
.addPostFrameCallback((_) => _tryConsumeSsoPayloadFromReturnUrl());
|
||||||
}
|
}
|
||||||
@ -138,15 +160,19 @@ class _loginState extends State<login> {
|
|||||||
|
|
||||||
if (decoded == null || !mounted) return;
|
if (decoded == null || !mounted) return;
|
||||||
|
|
||||||
|
if (mounted) setState(() => _isProcessingSsoReturn = true);
|
||||||
try {
|
try {
|
||||||
await completeLoginFromSsoPayloadMap(context, decoded);
|
await completeLoginFromSsoPayloadMap(context, decoded);
|
||||||
} finally {
|
} finally {
|
||||||
if (kIsWeb) stripPayloadParamFromBrowserUrl();
|
if (kIsWeb) stripPayloadParamFromBrowserUrl();
|
||||||
|
if (mounted) setState(() => _isProcessingSsoReturn = false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_privacyPolicyTap.dispose();
|
||||||
|
_termsOfUseTap.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,7 +210,7 @@ class _loginState extends State<login> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() => _isLoading = true);
|
// setState(() => _isLoadingSAML = true);
|
||||||
|
|
||||||
final base = Uri.parse(Environment.SsoLogin);
|
final base = Uri.parse(Environment.SsoLogin);
|
||||||
final qp = <String, String>{'email': emailInput};
|
final qp = <String, String>{'email': emailInput};
|
||||||
@ -196,6 +222,13 @@ class _loginState extends State<login> {
|
|||||||
}
|
}
|
||||||
final samlEntry = base.replace(queryParameters: qp);
|
final samlEntry = base.replace(queryParameters: qp);
|
||||||
|
|
||||||
|
final samlError = await fetchSamlEntryFailureMessage(samlEntry);
|
||||||
|
if (!mounted) return;
|
||||||
|
if (samlError != null) {
|
||||||
|
ToastHelper.showErrorToast(context, samlError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
skipLoadingReset = true;
|
skipLoadingReset = true;
|
||||||
ssoRedirectSameTab(samlEntry.toString());
|
ssoRedirectSameTab(samlEntry.toString());
|
||||||
@ -211,6 +244,7 @@ class _loginState extends State<login> {
|
|||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
if (result != null && result.isNotEmpty) {
|
if (result != null && result.isNotEmpty) {
|
||||||
|
if (mounted) setState(() => _isProcessingSsoReturn = true);
|
||||||
await completeLoginFromSsoPayloadMap(context, result);
|
await completeLoginFromSsoPayloadMap(context, result);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -221,7 +255,7 @@ class _loginState extends State<login> {
|
|||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted && !skipLoadingReset) {
|
if (mounted && !skipLoadingReset) {
|
||||||
setState(() => _isLoading = false);
|
// setState(() => _isLoadingSAML = false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1375,6 +1409,10 @@ class _loginState extends State<login> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
if (_isProcessingSsoReturn) {
|
||||||
|
return const NhanceLoadingScreen();
|
||||||
|
}
|
||||||
|
|
||||||
Size _size = MediaQuery.of(context).size;
|
Size _size = MediaQuery.of(context).size;
|
||||||
EdgeInsets marginInsets = EdgeInsets.zero;
|
EdgeInsets marginInsets = EdgeInsets.zero;
|
||||||
|
|
||||||
@ -1779,7 +1817,7 @@ class _loginState extends State<login> {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 45,
|
height: 45,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: _isLoading ? null : _handleSsoLoginButtonTap,
|
onPressed: _handleSsoLoginButtonTap,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
foregroundColor: Colors.black,
|
foregroundColor: Colors.black,
|
||||||
@ -2490,17 +2528,10 @@ class _loginState extends State<login> {
|
|||||||
),
|
),
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: _size.width <= 1100 ? 0 : 0,
|
height: 10,
|
||||||
),
|
),
|
||||||
SizedBox(
|
|
||||||
height: Responsive.isDesktop(context)
|
|
||||||
? _size.height * 0.1
|
|
||||||
: _size.height * 0.2,
|
|
||||||
),
|
|
||||||
// SizedBox(
|
|
||||||
// height: _size.height * 0.1,
|
|
||||||
// ),
|
|
||||||
Container(
|
Container(
|
||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.bottomCenter,
|
||||||
padding:
|
padding:
|
||||||
@ -2515,32 +2546,13 @@ class _loginState extends State<login> {
|
|||||||
fontSize: 9,
|
fontSize: 9,
|
||||||
),
|
),
|
||||||
children: <InlineSpan>[
|
children: <InlineSpan>[
|
||||||
WidgetSpan(
|
TextSpan(
|
||||||
child: MouseRegion(
|
text: 'privacy policy ',
|
||||||
cursor: SystemMouseCursors
|
style: _legalLinkStyle,
|
||||||
.click,
|
recognizer:
|
||||||
child: GestureDetector(
|
_privacyPolicyTap,
|
||||||
onTap: () {
|
mouseCursor:
|
||||||
context.go(
|
SystemMouseCursors.click,
|
||||||
'/privacypolicy');
|
|
||||||
// Navigator.pushNamed(
|
|
||||||
// context,
|
|
||||||
// 'privacypolicy');
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
'privacy policy ',
|
|
||||||
style:
|
|
||||||
GoogleFonts.poppins(
|
|
||||||
color:
|
|
||||||
Color(0xFF00989E),
|
|
||||||
fontSize: 9,
|
|
||||||
decoration:
|
|
||||||
TextDecoration
|
|
||||||
.underline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: 'and ',
|
text: 'and ',
|
||||||
@ -2549,37 +2561,25 @@ class _loginState extends State<login> {
|
|||||||
fontSize: 9,
|
fontSize: 9,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
WidgetSpan(
|
TextSpan(
|
||||||
child: MouseRegion(
|
text: 'terms of use',
|
||||||
cursor: SystemMouseCursors
|
style: _legalLinkStyle,
|
||||||
.click,
|
recognizer: _termsOfUseTap,
|
||||||
child: GestureDetector(
|
mouseCursor:
|
||||||
onTap: () {
|
SystemMouseCursors.click,
|
||||||
context
|
|
||||||
.go('/termsofuse');
|
|
||||||
// Navigator.pushNamed(
|
|
||||||
// context,
|
|
||||||
// 'termsofuse');
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
'terms of use',
|
|
||||||
style:
|
|
||||||
GoogleFonts.poppins(
|
|
||||||
color:
|
|
||||||
Color(0xFF00989E),
|
|
||||||
fontSize: 9,
|
|
||||||
decoration:
|
|
||||||
TextDecoration
|
|
||||||
.underline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: _size.width <= 1100 ? 0 : 0,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: Responsive.isDesktop(context)
|
||||||
|
? _size.height * 0.1
|
||||||
|
: _size.height * 0.2,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import 'dart:convert';
|
|||||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'helpers/browser_href.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:jwt_decode/jwt_decode.dart';
|
import 'package:jwt_decode/jwt_decode.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
@ -15,24 +16,38 @@ import 'service/TokenService.dart';
|
|||||||
|
|
||||||
/// Where the IdP should return the browser after SAML (web).
|
/// Where the IdP should return the browser after SAML (web).
|
||||||
///
|
///
|
||||||
/// Uses **`/login`** (not `/sso-login`) so static hosts that do not rewrite
|
/// Uses **`/sso-login`** so the app shows a loading screen (not the login form)
|
||||||
/// unknown paths to `index.html` still return **200** and Flutter can read
|
/// and then navigates straight to home or enrollment after tokens are saved.
|
||||||
/// `?payload=`. `/sso-login` is still supported via `web/index.html` redirect.
|
|
||||||
String buildWebSsoRelayStateUrl() {
|
String buildWebSsoRelayStateUrl() {
|
||||||
if (!kIsWeb) return '';
|
if (!kIsWeb) return '';
|
||||||
final origin = Uri.base.origin;
|
final origin = Uri.base.origin;
|
||||||
final frag = Uri.base.fragment;
|
final frag = Uri.base.fragment;
|
||||||
// Hash routing: e.g. `http://host/#/login` → return with payload on `#/login`
|
|
||||||
if (frag.startsWith('/')) {
|
if (frag.startsWith('/')) {
|
||||||
return '$origin/#/login';
|
return '$origin/#/sso-login';
|
||||||
|
}
|
||||||
|
var path = Uri.base.path;
|
||||||
|
if (path.endsWith('/')) {
|
||||||
|
path = path.substring(0, path.length - 1);
|
||||||
|
}
|
||||||
|
if (path.endsWith('/sso-login')) {
|
||||||
|
path = path.substring(0, path.length - '/sso-login'.length);
|
||||||
}
|
}
|
||||||
final href = Environment.baseHref;
|
final href = Environment.baseHref;
|
||||||
if (href.isEmpty || href == '/') {
|
final basePath = (href.isEmpty || href == '/')
|
||||||
return '$origin/login';
|
? (path.isEmpty ? '' : path)
|
||||||
|
: (href.endsWith('/') ? href.substring(0, href.length - 1) : href);
|
||||||
|
if (basePath.isEmpty) {
|
||||||
|
return '$origin/sso-login';
|
||||||
}
|
}
|
||||||
final trimmed =
|
return '$origin$basePath/sso-login';
|
||||||
href.endsWith('/') ? href.substring(0, href.length - 1) : href;
|
}
|
||||||
return '$origin$trimmed/login';
|
|
||||||
|
/// When SAML returns to `/login?payload=`, send users to [SsoLoginReturnPage].
|
||||||
|
String? redirectLoginWithSsoPayload(GoRouterState state) {
|
||||||
|
if (state.matchedLocation != '/login') return null;
|
||||||
|
final payload = readPayloadFromRouterUri(state.uri);
|
||||||
|
if (payload == null || payload.isEmpty) return null;
|
||||||
|
return '/sso-login?payload=${Uri.encodeQueryComponent(payload)}';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decodes the `payload` query value (base64 / base64url) into a JSON map.
|
/// Decodes the `payload` query value (base64 / base64url) into a JSON map.
|
||||||
@ -153,6 +168,52 @@ SsoPayloadJwtDecode decodeJwtClaimsFromSsoPayloadMap(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses SAML entry API JSON such as
|
||||||
|
/// `{"status":"failed","code":404,"message":"SAML not configured for this domain"}`.
|
||||||
|
String? parseSamlApiFailureMessage(String body) {
|
||||||
|
if (body.trim().isEmpty) return null;
|
||||||
|
try {
|
||||||
|
final decoded = json.decode(body);
|
||||||
|
if (decoded is Map && decoded['status']?.toString() == 'failed') {
|
||||||
|
final msg = decoded['message']?.toString();
|
||||||
|
if (msg != null && msg.isNotEmpty) return msg;
|
||||||
|
return 'SSO sign-in was not successful';
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// GETs the SAML entry URL without following redirects. Returns an error
|
||||||
|
/// message when the server responds with a failed JSON body (e.g. unknown domain).
|
||||||
|
Future<String?> fetchSamlEntryFailureMessage(Uri samlEntry) async {
|
||||||
|
final client = http.Client();
|
||||||
|
try {
|
||||||
|
final request = http.Request('GET', samlEntry)..followRedirects = false;
|
||||||
|
final streamed = await client
|
||||||
|
.send(request)
|
||||||
|
.timeout(const Duration(seconds: 30));
|
||||||
|
final response = await http.Response.fromStream(streamed);
|
||||||
|
|
||||||
|
final jsonError = parseSamlApiFailureMessage(response.body);
|
||||||
|
if (jsonError != null) return jsonError;
|
||||||
|
|
||||||
|
if (response.statusCode >= 300 && response.statusCode < 400) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (response.statusCode == 200) return null;
|
||||||
|
|
||||||
|
if (response.statusCode >= 400) {
|
||||||
|
return 'Unable to start SSO sign-in';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
logDebug('SAML entry preflight failed: $e');
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String prettySsoJsonForConsole(Object? value) {
|
String prettySsoJsonForConsole(Object? value) {
|
||||||
if (value == null) return 'null';
|
if (value == null) return 'null';
|
||||||
try {
|
try {
|
||||||
@ -257,14 +318,14 @@ Future<void> completeLoginFromSsoPayloadMap(
|
|||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
|
|
||||||
if (postToken != null && postToken.isNotEmpty) {
|
if (kIsWeb) stripPayloadParamFromBrowserUrl();
|
||||||
|
|
||||||
|
final destination = await TokenService.defaultRouteForStoredTokens();
|
||||||
|
SessionManager().unlockMobileAppSession();
|
||||||
|
if (destination == '/home') {
|
||||||
ToastHelper.showSuccessToast(context, 'Successfully Logged In');
|
ToastHelper.showSuccessToast(context, 'Successfully Logged In');
|
||||||
SessionManager().unlockMobileAppSession();
|
|
||||||
context.go('/home');
|
|
||||||
} else {
|
|
||||||
SessionManager().unlockMobileAppSession();
|
|
||||||
context.go('/empDetails');
|
|
||||||
}
|
}
|
||||||
|
if (context.mounted) context.replace(destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> completeLoginFromSsoPayloadString(
|
Future<void> completeLoginFromSsoPayloadString(
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:webview_flutter/webview_flutter.dart';
|
import 'package:webview_flutter/webview_flutter.dart';
|
||||||
|
|
||||||
|
import '../customAppBar/customAppBar.dart';
|
||||||
|
import '../customAppBar/toastHelper.dart';
|
||||||
import 'login_saml_auth.dart';
|
import 'login_saml_auth.dart';
|
||||||
|
|
||||||
/// Mobile: in-app WebView for SAML entry; when URL contains `payload=`, decodes and pops.
|
/// Mobile: in-app WebView for SAML entry; when URL contains `payload=`, decodes and pops.
|
||||||
@ -27,6 +29,21 @@ class _LoginSamlWebViewScreenState extends State<LoginSamlWebViewScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _tryFinishFromPageBody() async {
|
||||||
|
if (_finished || !mounted) return;
|
||||||
|
try {
|
||||||
|
final raw = await _controller.runJavaScriptReturningResult(
|
||||||
|
'document.body ? document.body.innerText : ""',
|
||||||
|
);
|
||||||
|
final text = raw.toString().trim();
|
||||||
|
final msg = parseSamlApiFailureMessage(text);
|
||||||
|
if (msg == null || !mounted) return;
|
||||||
|
_finished = true;
|
||||||
|
ToastHelper.showErrorToast(context, msg);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
void _tryFinishFromHttpError(HttpResponseError error) {
|
void _tryFinishFromHttpError(HttpResponseError error) {
|
||||||
if (_finished || !mounted) return;
|
if (_finished || !mounted) return;
|
||||||
final status = error.response?.statusCode;
|
final status = error.response?.statusCode;
|
||||||
@ -57,7 +74,10 @@ class _LoginSamlWebViewScreenState extends State<LoginSamlWebViewScreen> {
|
|||||||
return NavigationDecision.navigate;
|
return NavigationDecision.navigate;
|
||||||
},
|
},
|
||||||
onPageStarted: (String url) => _tryFinishWithUrl(url),
|
onPageStarted: (String url) => _tryFinishWithUrl(url),
|
||||||
onPageFinished: (String url) => _tryFinishWithUrl(url),
|
onPageFinished: (String url) async {
|
||||||
|
_tryFinishWithUrl(url);
|
||||||
|
if (!_finished) await _tryFinishFromPageBody();
|
||||||
|
},
|
||||||
onUrlChange: (UrlChange change) {
|
onUrlChange: (UrlChange change) {
|
||||||
final url = change.url;
|
final url = change.url;
|
||||||
if (url != null && url.isNotEmpty) {
|
if (url != null && url.isNotEmpty) {
|
||||||
@ -79,7 +99,8 @@ class _LoginSamlWebViewScreenState extends State<LoginSamlWebViewScreen> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: const Text('Sign in with SSO')),
|
backgroundColor: Colors.white,
|
||||||
|
appBar: const CustomAppBar(logoOnly: true),
|
||||||
body: WebViewWidget(controller: _controller),
|
body: WebViewWidget(controller: _controller),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -270,10 +270,10 @@ class _HomeState extends State<Home> {
|
|||||||
final response = await apiService.getActiveAndInactivePolicyDetails(
|
final response = await apiService.getActiveAndInactivePolicyDetails(
|
||||||
client_id, empCodeString, status, client_branch_id, mobileNo,emailId);
|
client_id, empCodeString, status, client_branch_id, mobileNo,emailId);
|
||||||
logDebug('check 1');
|
logDebug('check 1');
|
||||||
|
setState(() {
|
||||||
|
isLoadingGif = false;
|
||||||
|
});
|
||||||
if (response['status'] == 'success') {
|
if (response['status'] == 'success') {
|
||||||
setState(() {
|
|
||||||
isLoadingGif = false;
|
|
||||||
});
|
|
||||||
emp_name = response['emp_name'];
|
emp_name = response['emp_name'];
|
||||||
policyList = List.from(response['data'] ?? []);
|
policyList = List.from(response['data'] ?? []);
|
||||||
retailPolicyDetails = List.from(response['retail_policy_data'] ?? []);
|
retailPolicyDetails = List.from(response['retail_policy_data'] ?? []);
|
||||||
@ -307,7 +307,6 @@ class _HomeState extends State<Home> {
|
|||||||
} else {
|
} else {
|
||||||
setState(() {
|
setState(() {
|
||||||
policyDataIsEmpty = 0;
|
policyDataIsEmpty = 0;
|
||||||
isLoadingGif = false;
|
|
||||||
});
|
});
|
||||||
logDebug('API request failed with status: ${response['status']}');
|
logDebug('API request failed with status: ${response['status']}');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||||||
import '../../config/environment.dart';
|
import '../../config/environment.dart';
|
||||||
import '../../customAppBar/toastHelper.dart';
|
import '../../customAppBar/toastHelper.dart';
|
||||||
import '../../logger.dart';
|
import '../../logger.dart';
|
||||||
import '../../models/platform_helper_mobile.dart';
|
import '../../router/app_router.dart';
|
||||||
import 'SessionManager.dart';
|
import 'SessionManager.dart';
|
||||||
import 'web_session_storage.dart';
|
import 'web_session_storage.dart';
|
||||||
|
|
||||||
@ -70,6 +70,15 @@ class TokenService {
|
|||||||
(pre != null && pre.isNotEmpty);
|
(pre != null && pre.isNotEmpty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Post token → home; pre-only → enrollment ([empDetails]).
|
||||||
|
static Future<String> defaultRouteForStoredTokens() async {
|
||||||
|
final post = await getPostToken();
|
||||||
|
if (post != null && post.isNotEmpty) return '/home';
|
||||||
|
final pre = await getPreToken();
|
||||||
|
if (pre != null && pre.isNotEmpty) return '/empDetails';
|
||||||
|
return '/login';
|
||||||
|
}
|
||||||
|
|
||||||
/// Clears auth tokens that older web builds stored in SharedPreferences (localStorage).
|
/// Clears auth tokens that older web builds stored in SharedPreferences (localStorage).
|
||||||
/// Call once at startup so they cannot be mistaken for a valid session.
|
/// Call once at startup so they cannot be mistaken for a valid session.
|
||||||
static Future<void> clearLegacyWebAuthFromSharedPreferences() async {
|
static Future<void> clearLegacyWebAuthFromSharedPreferences() async {
|
||||||
@ -131,17 +140,35 @@ class TokenService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _goAfterLogout(BuildContext context, String location) {
|
||||||
|
if (context.mounted && GoRouter.maybeOf(context) != null) {
|
||||||
|
context.go(location);
|
||||||
|
} else {
|
||||||
|
navigateAppTo(location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> logout(BuildContext context) async {
|
Future<void> logout(BuildContext context) async {
|
||||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
|
||||||
String? mobileNumber = prefs.getString('empMobileNo');
|
String? mobileNumber = prefs.getString('empMobileNo');
|
||||||
String? mailID = prefs.getString('empEmailid');
|
String? mailID = prefs.getString('empEmailid');
|
||||||
final isMpinSkipped = await checkLoginPin();
|
|
||||||
logDebug('isMpinSkipped $isMpinSkipped');
|
final postToken = await getPostToken();
|
||||||
|
final hasPostToken = postToken != null && postToken.isNotEmpty;
|
||||||
|
|
||||||
|
String? isMpinSkipped;
|
||||||
|
if (!kIsWeb && hasPostToken) {
|
||||||
|
isMpinSkipped = await checkLoginPin();
|
||||||
|
}
|
||||||
|
logDebug('isMpinSkipped $isMpinSkipped hasPostToken $hasPostToken');
|
||||||
|
|
||||||
await SessionManager().clear();
|
await SessionManager().clear();
|
||||||
|
|
||||||
if (isMobilePlatform()) {
|
if (kIsWeb) {
|
||||||
|
await prefs.clear();
|
||||||
|
_goAfterLogout(context, '/login');
|
||||||
|
} else {
|
||||||
if (mobileNumber != null) {
|
if (mobileNumber != null) {
|
||||||
await prefs.setString('empMobileNo', mobileNumber);
|
await prefs.setString('empMobileNo', mobileNumber);
|
||||||
}
|
}
|
||||||
@ -151,15 +178,11 @@ class TokenService {
|
|||||||
if (isMpinSkipped != null) {
|
if (isMpinSkipped != null) {
|
||||||
await prefs.setString('is_mpin_skipped', isMpinSkipped);
|
await prefs.setString('is_mpin_skipped', isMpinSkipped);
|
||||||
}
|
}
|
||||||
if (isMpinSkipped != null && isMpinSkipped == '0') {
|
if (hasPostToken && isMpinSkipped != null && isMpinSkipped == '0') {
|
||||||
context.go('/pinPage');
|
_goAfterLogout(context, '/pinPage');
|
||||||
} else {
|
} else {
|
||||||
context.go('/login');
|
_goAfterLogout(context, '/login');
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
await prefs.clear();
|
|
||||||
ToastHelper.showSuccessToast(context, 'logout');
|
|
||||||
context.go('/login');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
import '../customAppBar/nhance_loading_screen.dart';
|
||||||
import '../customAppBar/toastHelper.dart';
|
import '../customAppBar/toastHelper.dart';
|
||||||
import '../logger.dart';
|
import '../logger.dart';
|
||||||
import 'helpers/browser_href.dart';
|
import 'helpers/browser_href.dart';
|
||||||
@ -50,13 +51,15 @@ class _SsoLoginReturnPageState extends State<SsoLoginReturnPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logDebug('SSO return: decoded payload (keys)=${decoded.keys.toList()}');
|
logDebug('SSO return: decoded payload (keys)=${decoded.keys.toList()}');
|
||||||
await completeLoginFromSsoPayloadMap(context, decoded);
|
try {
|
||||||
|
await completeLoginFromSsoPayloadMap(context, decoded);
|
||||||
|
} finally {
|
||||||
|
if (kIsWeb) stripPayloadParamFromBrowserUrl();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return const Scaffold(
|
return const NhanceLoadingScreen();
|
||||||
body: Center(child: CircularProgressIndicator()),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
lib/router/app_router.dart
Normal file
17
lib/router/app_router.dart
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
/// Set once in [startApp] so services (e.g. [TokenService.logout]) can navigate
|
||||||
|
/// when [BuildContext] has no [GoRouter] ancestor (e.g. ApiService from MyApp).
|
||||||
|
GoRouter? appGoRouter;
|
||||||
|
|
||||||
|
void registerAppGoRouter(GoRouter router) {
|
||||||
|
appGoRouter = router;
|
||||||
|
}
|
||||||
|
|
||||||
|
void navigateAppTo(String location) {
|
||||||
|
final router = appGoRouter;
|
||||||
|
if (router == null) {
|
||||||
|
throw StateError('GoRouter not registered. Call registerAppGoRouter first.');
|
||||||
|
}
|
||||||
|
router.go(location);
|
||||||
|
}
|
||||||
@ -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.35+41
|
version: 1.0.37+43
|
||||||
#version: 2.0.23+61
|
#version: 2.0.25+63
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.3.3 <4.0.0'
|
sdk: '>=3.3.3 <4.0.0'
|
||||||
|
|||||||
@ -4,22 +4,6 @@
|
|||||||
|
|
||||||
<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">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user