- Updated AppRouter to use AuthProvider instead of direct Supabase calls - Ensures proper navigation after logout through state management - Router now correctly responds to AuthProvider state changes
205 lines
7.3 KiB
Dart
205 lines
7.3 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
|
|
|
import '../../features/authentication/presentation/pages/login_page.dart';
|
|
import '../../features/authentication/presentation/pages/signup_page.dart';
|
|
import '../../features/home/presentation/pages/home_page.dart';
|
|
import '../../features/authentication/presentation/pages/splash_page.dart';
|
|
import '../../features/authentication/presentation/pages/reset_password_page.dart';
|
|
import '../../features/authentication/presentation/pages/reset_password_confirm_page.dart';
|
|
import '../../features/authentication/presentation/pages/update_password_page.dart';
|
|
import '../../providers/auth_provider.dart';
|
|
|
|
/// Application router configuration
|
|
///
|
|
/// Handles navigation with authentication state awareness and protected routes
|
|
/// Supports deep linking for password reset URLs
|
|
/// URL scheme for mobile: sage://reset-password?token=xxx&email=xxx
|
|
/// Web URLs: https://app.sage.com/reset-password?token=xxx&email=xxx
|
|
class AppRouter {
|
|
static final GoRouter _router = GoRouter(
|
|
initialLocation: '/',
|
|
debugLogDiagnostics: true,
|
|
redirect: (context, state) {
|
|
// Use AuthProvider for auth state checking
|
|
final authState = context.read(authStateProvider);
|
|
final currentUser = authState.user;
|
|
|
|
// Allow splash page regardless of auth state
|
|
if (state.uri.toString() == '/splash') {
|
|
return null;
|
|
}
|
|
|
|
// If not authenticated and trying to access protected route, redirect to login
|
|
// Allow password reset routes regardless of auth state for deep linking
|
|
if (currentUser == null &&
|
|
!state.uri.toString().startsWith('/login') &&
|
|
!state.uri.toString().startsWith('/signup') &&
|
|
!state.uri.toString().startsWith('/reset-password') &&
|
|
!state.uri.toString().startsWith('/update-password')) {
|
|
return '/login';
|
|
}
|
|
|
|
// If authenticated and on auth pages (except password reset), redirect to home
|
|
if (currentUser != null &&
|
|
(state.uri.toString().startsWith('/login') ||
|
|
state.uri.toString().startsWith('/signup') ||
|
|
state.uri.toString().startsWith('/reset-password'))) {
|
|
return '/home';
|
|
}
|
|
|
|
return null;
|
|
},
|
|
routes: [
|
|
// Splash route - initial loading screen
|
|
GoRoute(
|
|
path: '/splash',
|
|
builder: (context, state) => const SplashPage(),
|
|
),
|
|
|
|
// Authentication routes (public)
|
|
GoRoute(
|
|
path: '/login',
|
|
builder: (context, state) => const LoginPage(),
|
|
),
|
|
|
|
GoRoute(
|
|
path: '/signup',
|
|
builder: (context, state) => const SignupPage(),
|
|
),
|
|
|
|
// Password reset routes (public for deep linking)
|
|
GoRoute(
|
|
path: '/reset-password',
|
|
builder: (context, state) => const ResetPasswordPage(),
|
|
),
|
|
|
|
GoRoute(
|
|
path: '/reset-password-confirm',
|
|
builder: (context, state) {
|
|
// Extract token from query parameters for deep linking
|
|
final token = state.uri.queryParameters['token'];
|
|
final email = state.uri.queryParameters['email'];
|
|
|
|
// Store token data for password reset flow
|
|
if (token != null && email != null) {
|
|
// TODO: Store token and email securely for password reset flow
|
|
// This could be done through a provider or secure storage
|
|
print('Password reset token received for email: $email');
|
|
}
|
|
|
|
return const ResetPasswordConfirmPage();
|
|
},
|
|
),
|
|
|
|
GoRoute(
|
|
path: '/update-password',
|
|
builder: (context, state) {
|
|
// Extract token from query parameters if present
|
|
final token = state.uri.queryParameters['token'];
|
|
final email = state.uri.queryParameters['email'];
|
|
|
|
// Store token data for password update
|
|
if (token != null && email != null) {
|
|
// TODO: Store token and email securely for password update flow
|
|
print('Password update token received for email: $email');
|
|
}
|
|
|
|
return const UpdatePasswordPage();
|
|
},
|
|
),
|
|
|
|
// Protected routes (require authentication)
|
|
GoRoute(
|
|
path: '/home',
|
|
builder: (context, state) => const HomePage(),
|
|
),
|
|
|
|
// Root route - redirects based on auth state
|
|
GoRoute(
|
|
path: '/',
|
|
redirect: (context, state) {
|
|
final currentUser = Supabase.instance.client.auth.currentUser;
|
|
return currentUser != null ? '/home' : '/splash';
|
|
},
|
|
),
|
|
],
|
|
errorBuilder: (context, state) => Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Error'),
|
|
),
|
|
body: Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Icon(
|
|
Icons.error_outline,
|
|
size: 64,
|
|
color: Colors.red,
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
'Page not found',
|
|
style: Theme.of(context).textTheme.headlineSmall,
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'Could not find: ${state.uri.toString()}',
|
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
color: Colors.grey[600],
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
// Navigate to home or login based on auth state
|
|
final currentUser = Supabase.instance.client.auth.currentUser;
|
|
if (currentUser != null) {
|
|
GoRouter.of(context).go('/home');
|
|
} else {
|
|
GoRouter.of(context).go('/login');
|
|
}
|
|
},
|
|
child: const Text('Go Home'),
|
|
),
|
|
const SizedBox(height: 16),
|
|
// Special handling for malformed password reset URLs
|
|
if (state.uri.toString().contains('reset')) ...[
|
|
TextButton(
|
|
onPressed: () => GoRouter.of(context).go('/reset-password'),
|
|
child: const Text('Request New Reset Link'),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
/// Get the router instance
|
|
static GoRouter get router => _router;
|
|
|
|
/// Extract URL query parameters from current location
|
|
static Map<String, String> extractQueryParameters(BuildContext context) {
|
|
final GoRouter router = GoRouter.of(context);
|
|
return router.routeInformationProvider.value.uri.queryParameters;
|
|
}
|
|
|
|
/// Handle password reset deep link with parameters
|
|
static Map<String, String> handlePasswordResetDeepLink(BuildContext context) {
|
|
final params = extractQueryParameters(context);
|
|
final token = params['token'];
|
|
final email = params['email'];
|
|
|
|
if (token != null && email != null) {
|
|
// Store secure data for password reset flow
|
|
// TODO: Implement secure storage mechanism
|
|
print('Password reset deep link received for: $email');
|
|
return {'token': token, 'email': email};
|
|
}
|
|
|
|
return {};
|
|
}
|
|
} |