import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:tatlock_ui/core/auth/auth_provider.dart'; import 'package:tatlock_ui/core/config/app_config.dart'; import 'package:tatlock_ui/features/control_room/router.dart'; import 'package:tatlock_ui/features/front_hall/presentation/pages/front_hall_page.dart'; import 'package:tatlock_ui/features/security/router.dart'; import 'package:tatlock_ui/shared/layouts/app_scaffold.dart'; part 'app_router.g.dart'; /// Route paths as constants. abstract class AppRoutes { static const frontHall = '/'; static const parlor = '/parlor'; static const settings = '/settings'; static const login = '/login'; } /// Provides the GoRouter instance. @riverpod GoRouter appRouter(Ref ref) { final authState = ref.watch(authProvider); return GoRouter( initialLocation: AppRoutes.frontHall, debugLogDiagnostics: true, redirect: (context, state) { // No auth required in LAN mode if (!AppConfig.requiresAuth) { return null; } final isAuthenticated = authState.value?.isAuthenticated ?? false; final isLoginRoute = state.matchedLocation == AppRoutes.login; // If not authenticated, redirect to login (except if already on login) if (!isAuthenticated && !isLoginRoute) { return AppRoutes.login; } // If authenticated and on login page, redirect to home if (isAuthenticated && isLoginRoute) { return AppRoutes.frontHall; } return null; }, routes: [ // Login route (outside shell - no app scaffold) GoRoute( path: AppRoutes.login, name: 'login', builder: (context, state) => const _LoginPage(), ), // Main app routes (inside shell with app scaffold) ShellRoute( builder: (context, state, child) => AppScaffold(child: child), routes: [ GoRoute( path: AppRoutes.frontHall, name: 'frontHall', builder: (context, state) => const FrontHallPage(), ), ...controlRoomRoutes(), ...securityRoutes(), GoRoute( path: AppRoutes.parlor, name: 'parlor', builder: (context, state) => const _PlaceholderPage(title: 'Parlor'), ), GoRoute( path: AppRoutes.settings, name: 'settings', builder: (context, state) => const _PlaceholderPage(title: 'Settings'), ), ], ), ], ); } /// Placeholder page for routes not yet implemented. class _PlaceholderPage extends StatelessWidget { const _PlaceholderPage({required this.title}); final String title; @override Widget build(BuildContext context) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.construction, size: 64, color: Theme.of(context).colorScheme.outline, ), const SizedBox(height: 16), Text( title, style: Theme.of(context).textTheme.headlineMedium, ), const SizedBox(height: 8), Text( 'Coming soon', style: Theme.of(context).textTheme.bodyLarge?.copyWith( color: Theme.of(context).colorScheme.outline, ), ), ], ), ); } } /// Login page displayed when user is not authenticated. class _LoginPage extends ConsumerWidget { const _LoginPage(); @override Widget build(BuildContext context, WidgetRef ref) { final authAsync = ref.watch(authProvider); final colorScheme = Theme.of(context).colorScheme; return Scaffold( body: Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 400), child: Card( child: Padding( padding: const EdgeInsets.all(32), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.home_work_outlined, size: 64, color: colorScheme.primary, ), const SizedBox(height: 24), Text( 'Tatlock Estate', style: Theme.of(context).textTheme.headlineMedium?.copyWith( fontWeight: FontWeight.w600, ), ), const SizedBox(height: 8), Text( 'Sign in to access the estate management system', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: colorScheme.onSurfaceVariant, ), textAlign: TextAlign.center, ), const SizedBox(height: 32), authAsync.when( data: (_) => _buildSignInContent(context, ref), loading: () => const Column( children: [ SizedBox( width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2), ), SizedBox(height: 16), Text('Checking authentication...'), ], ), error: (error, _) => _buildErrorContent(context, ref, error), ), ], ), ), ), ), ), ); } Widget _buildSignInContent(BuildContext context, WidgetRef ref) { if (kIsWeb) { // Web: User needs to access via authenticated proxy return Column( children: [ Text( 'Please access Tatlock via the authenticated URL.\n' 'If you see this page, the proxy authentication may not be configured.', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), textAlign: TextAlign.center, ), const SizedBox(height: 16), OutlinedButton.icon( onPressed: () => ref.read(authProvider.notifier).signIn(), icon: const Icon(Icons.refresh), label: const Text('Retry'), style: OutlinedButton.styleFrom( minimumSize: const Size(double.infinity, 48), ), ), ], ); } // Mobile: Show sign in button return FilledButton.icon( onPressed: () => ref.read(authProvider.notifier).signIn(), icon: const Icon(Icons.login), label: const Text('Sign in with Authentik'), style: FilledButton.styleFrom( minimumSize: const Size(double.infinity, 48), ), ); } Widget _buildErrorContent(BuildContext context, WidgetRef ref, Object error) { final colorScheme = Theme.of(context).colorScheme; return Column( children: [ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: colorScheme.errorContainer, borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Icon(Icons.error_outline, color: colorScheme.onErrorContainer), const SizedBox(width: 12), Expanded( child: Text( _formatError(error), style: TextStyle(color: colorScheme.onErrorContainer), ), ), ], ), ), const SizedBox(height: 16), OutlinedButton.icon( onPressed: () => ref.read(authProvider.notifier).signIn(), icon: const Icon(Icons.refresh), label: const Text('Try again'), style: OutlinedButton.styleFrom( minimumSize: const Size(double.infinity, 48), ), ), ], ); } String _formatError(Object error) { final message = error.toString(); if (message.contains('user_cancelled')) { return 'Sign in was cancelled'; } if (message.contains('network')) { return 'Network error. Please check your connection.'; } if (message.contains('authenticated URL')) { return 'Not authenticated - please access via the authenticated URL'; } return 'Authentication failed. Please try again.'; } }