import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:descope/descope.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:flutter_web_plugins/url_strategy.dart'; import 'features/auth/presentation/login_screen.dart'; import 'features/auth/presentation/approve_qr_screen.dart'; import 'features/auth/presentation/qr_scan_screen.dart'; import 'features/dashboard/presentation/dashboard_screen.dart'; import 'features/admin/presentation/user_management_screen.dart'; import 'core/services/auth_proxy_service.dart'; import 'core/services/logger_service.dart'; import 'core/notifiers/auth_notifier.dart'; import 'package:logging/logging.dart'; final _log = Logger('Main'); void main() async { WidgetsFlutterBinding.ensureInitialized(); usePathUrlStrategy(); // 0. Initialize Logger LoggerService.init(); // 1. Global Error Handling FlutterError.onError = (details) { FlutterError.presentError(details); _log.severe("FLUTTER_ERROR", details.exception, details.stack); // Also send to backend if needed AuthProxyService.logError("FLUTTER_ERROR: ${details.exception}\n${details.stack}"); }; PlatformDispatcher.instance.onError = (error, stack) { _log.severe("PLATFORM_ERROR", error, stack); AuthProxyService.logError("PLATFORM_ERROR: $error\n$stack"); return true; }; // Load Env (Handling error if missing for now) try { await dotenv.load(fileName: ".env"); } catch (e) { _log.warning("Warning: .env file not found."); } // Initialize Descope final projectId = dotenv.env['DESCOPE_PROJECT_ID'] ?? 'your-project-id'; Descope.setup(projectId); // Load saved session if any try { // 저장된 세션 불러옴 await Descope.sessionManager.loadSession(); } catch (e) { _log.warning("Failed to load session: $e"); } runApp(const ProviderScope(child: BaronSSOApp())); } // Router Configuration final _routerLogger = Logger('Router'); final _router = GoRouter( initialLocation: '/', debugLogDiagnostics: true, // Enable diagnostic logs refreshListenable: AuthNotifier.instance, routes: [ GoRoute( path: '/', builder: (context, state) { _routerLogger.info("Navigating to root (DashboardScreen)"); return const DashboardScreen(); }, ), GoRoute( path: '/login', builder: (context, state) { _routerLogger.info("Navigating to /login"); return const LoginScreen(); } ), GoRoute( path: '/verify/:token', builder: (context, state) { final token = state.pathParameters['token']; _routerLogger.info("Navigating to /verify with token: $token"); return LoginScreen(verificationToken: token); }, ), GoRoute( path: '/approve', builder: (context, state) { final ref = state.uri.queryParameters['ref']; _routerLogger.info("Navigating to /approve with ref: $ref"); return ApproveQrScreen(pendingRef: ref); }, ), GoRoute( path: '/scan', builder: (context, state) { _routerLogger.info("Navigating to /scan"); return const QRScanScreen(); }, ), GoRoute( path: '/admin/users', builder: (context, state) { _routerLogger.info("Navigating to /admin/users"); return const UserManagementScreen(); }, ), ], redirect: (context, state) { final isLoggedIn = Descope.sessionManager.session?.refreshToken?.isExpired == false; final path = state.uri.path; // Public paths that don't require login final isPublicPath = path == '/login' || path.startsWith('/verify/') || path == '/approve'; _routerLogger.fine("Redirect check - Path: $path, IsLoggedIn: $isLoggedIn"); // 0. ALWAYS allow /verify/ to proceed so it can signal the backend if (path.startsWith('/verify/')) { return null; } // If not logged in and trying to access a protected page, redirect to /login if (!isLoggedIn && !isPublicPath) { _routerLogger.info("Not logged in, redirecting to /login"); return '/login'; } // If logged in and trying to access login page, redirect to root (dashboard) if (isLoggedIn && path == '/login') { _routerLogger.info("Logged in, redirecting to /"); return '/'; } return null; }, ); class BaronSSOApp extends StatelessWidget { const BaronSSOApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp.router( title: 'Baron SSO', theme: ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: const Color(0xFF1A1F2C), // Dark Navy/Black base brightness: Brightness.light, ), useMaterial3: true, textTheme: GoogleFonts.interTextTheme(), ), routerConfig: _router, ); } }