491 lines
15 KiB
Dart
491 lines
15 KiB
Dart
// ignore_for_file: unused_local_variable, avoid_print
|
|
|
|
/// Example usage of the authentication system
|
|
///
|
|
/// This file demonstrates how to use the authentication feature
|
|
/// in your Flutter app. Copy the patterns shown here into your
|
|
/// actual implementation.
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'presentation/providers/auth_provider.dart';
|
|
|
|
// ============================================================================
|
|
// EXAMPLE 1: Login Flow
|
|
// ============================================================================
|
|
|
|
class LoginExample extends ConsumerWidget {
|
|
final TextEditingController emailController;
|
|
final TextEditingController passwordController;
|
|
|
|
const LoginExample({
|
|
super.key,
|
|
required this.emailController,
|
|
required this.passwordController,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
// Watch auth state
|
|
final authState = ref.watch(authProvider);
|
|
|
|
return Column(
|
|
children: [
|
|
// Show loading indicator
|
|
if (authState.isLoading) const CircularProgressIndicator(),
|
|
|
|
// Login button
|
|
ElevatedButton(
|
|
onPressed: authState.isLoading
|
|
? null
|
|
: () async {
|
|
// Call login
|
|
final success = await ref.read(authProvider.notifier).login(
|
|
email: emailController.text.trim(),
|
|
password: passwordController.text,
|
|
);
|
|
|
|
// Handle result
|
|
if (success) {
|
|
// Login successful - navigate to home
|
|
if (context.mounted) {
|
|
Navigator.pushReplacementNamed(context, '/home');
|
|
}
|
|
} else {
|
|
// Login failed - show error
|
|
final error = ref.read(authProvider).errorMessage;
|
|
if (context.mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(error ?? 'Login failed'),
|
|
backgroundColor: Colors.red,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
},
|
|
child: const Text('Login'),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// EXAMPLE 2: Register Flow
|
|
// ============================================================================
|
|
|
|
class RegisterExample extends ConsumerStatefulWidget {
|
|
const RegisterExample({super.key});
|
|
|
|
@override
|
|
ConsumerState<RegisterExample> createState() => _RegisterExampleState();
|
|
}
|
|
|
|
class _RegisterExampleState extends ConsumerState<RegisterExample> {
|
|
final _nameController = TextEditingController();
|
|
final _emailController = TextEditingController();
|
|
final _passwordController = TextEditingController();
|
|
|
|
@override
|
|
void dispose() {
|
|
_nameController.dispose();
|
|
_emailController.dispose();
|
|
_passwordController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _handleRegister() async {
|
|
final success = await ref.read(authProvider.notifier).register(
|
|
name: _nameController.text.trim(),
|
|
email: _emailController.text.trim(),
|
|
password: _passwordController.text,
|
|
roles: ['user'], // Optional, defaults to ['user']
|
|
);
|
|
|
|
if (!mounted) return;
|
|
|
|
if (success) {
|
|
// Registration successful
|
|
Navigator.pushReplacementNamed(context, '/home');
|
|
} else {
|
|
// Registration failed
|
|
final error = ref.read(authProvider).errorMessage;
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(error ?? 'Registration failed'),
|
|
backgroundColor: Colors.red,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final authState = ref.watch(authProvider);
|
|
|
|
return ElevatedButton(
|
|
onPressed: authState.isLoading ? null : _handleRegister,
|
|
child: const Text('Register'),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// EXAMPLE 3: Check Authentication Status
|
|
// ============================================================================
|
|
|
|
class AuthStatusExample extends ConsumerWidget {
|
|
const AuthStatusExample({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
// Method 1: Watch entire auth state
|
|
final authState = ref.watch(authProvider);
|
|
|
|
// Method 2: Use convenience providers
|
|
final isAuthenticated = ref.watch(isAuthenticatedProvider);
|
|
final currentUser = ref.watch(currentUserProvider);
|
|
|
|
return Column(
|
|
children: [
|
|
// Check if authenticated
|
|
if (authState.isAuthenticated) ...[
|
|
Text('Welcome ${authState.user?.name}!'),
|
|
Text('Email: ${authState.user?.email}'),
|
|
Text('Roles: ${authState.user?.roles.join(", ")}'),
|
|
|
|
// Check user roles
|
|
if (currentUser?.isAdmin ?? false)
|
|
const Text('You are an admin'),
|
|
if (currentUser?.isManager ?? false)
|
|
const Text('You are a manager'),
|
|
if (currentUser?.isCashier ?? false)
|
|
const Text('You are a cashier'),
|
|
] else ...[
|
|
const Text('Not authenticated'),
|
|
],
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// EXAMPLE 4: Protected Route Guard
|
|
// ============================================================================
|
|
|
|
class AuthGuard extends ConsumerWidget {
|
|
final Widget child;
|
|
|
|
const AuthGuard({super.key, required this.child});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final isAuthenticated = ref.watch(isAuthenticatedProvider);
|
|
final isLoading = ref.watch(authProvider.select((s) => s.isLoading));
|
|
|
|
// Show loading while checking auth status
|
|
if (isLoading) {
|
|
return const Scaffold(
|
|
body: Center(child: CircularProgressIndicator()),
|
|
);
|
|
}
|
|
|
|
// If not authenticated, show login page
|
|
if (!isAuthenticated) {
|
|
return const Scaffold(
|
|
body: Center(
|
|
child: Text('Please login to continue'),
|
|
),
|
|
);
|
|
// In real app: return const LoginPage();
|
|
}
|
|
|
|
// User is authenticated, show protected content
|
|
return child;
|
|
}
|
|
}
|
|
|
|
// Usage in main app:
|
|
// MaterialApp(
|
|
// home: AuthGuard(
|
|
// child: HomePage(),
|
|
// ),
|
|
// );
|
|
|
|
// ============================================================================
|
|
// EXAMPLE 5: Logout
|
|
// ============================================================================
|
|
|
|
class LogoutExample extends ConsumerWidget {
|
|
const LogoutExample({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
return ElevatedButton(
|
|
onPressed: () async {
|
|
// Show confirmation dialog
|
|
final confirmed = await showDialog<bool>(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: const Text('Logout'),
|
|
content: const Text('Are you sure you want to logout?'),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context, false),
|
|
child: const Text('Cancel'),
|
|
),
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context, true),
|
|
child: const Text('Logout'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
if (confirmed == true) {
|
|
// Perform logout
|
|
await ref.read(authProvider.notifier).logout();
|
|
|
|
// Navigate to login
|
|
if (context.mounted) {
|
|
Navigator.pushReplacementNamed(context, '/login');
|
|
}
|
|
}
|
|
},
|
|
child: const Text('Logout'),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// EXAMPLE 6: Get Profile (Refresh User Data)
|
|
// ============================================================================
|
|
|
|
class ProfileExample extends ConsumerWidget {
|
|
const ProfileExample({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final currentUser = ref.watch(currentUserProvider);
|
|
|
|
return Column(
|
|
children: [
|
|
if (currentUser != null) ...[
|
|
Text('Name: ${currentUser.name}'),
|
|
Text('Email: ${currentUser.email}'),
|
|
Text('Roles: ${currentUser.roles.join(", ")}'),
|
|
],
|
|
|
|
// Refresh profile button
|
|
ElevatedButton(
|
|
onPressed: () async {
|
|
await ref.read(authProvider.notifier).getProfile();
|
|
|
|
if (context.mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('Profile refreshed')),
|
|
);
|
|
}
|
|
},
|
|
child: const Text('Refresh Profile'),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// EXAMPLE 7: Refresh Token
|
|
// ============================================================================
|
|
|
|
class RefreshTokenExample extends ConsumerWidget {
|
|
const RefreshTokenExample({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
return ElevatedButton(
|
|
onPressed: () async {
|
|
final success = await ref.read(authProvider.notifier).refreshToken();
|
|
|
|
if (!context.mounted) return;
|
|
|
|
if (success) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('Token refreshed successfully')),
|
|
);
|
|
} else {
|
|
// Token refresh failed - user was logged out
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text('Session expired. Please login again.'),
|
|
backgroundColor: Colors.red,
|
|
),
|
|
);
|
|
Navigator.pushReplacementNamed(context, '/login');
|
|
}
|
|
},
|
|
child: const Text('Refresh Token'),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// EXAMPLE 8: Role-Based UI
|
|
// ============================================================================
|
|
|
|
class RoleBasedUIExample extends ConsumerWidget {
|
|
const RoleBasedUIExample({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final currentUser = ref.watch(currentUserProvider);
|
|
|
|
return Column(
|
|
children: [
|
|
// Show to all authenticated users
|
|
const Text('Dashboard'),
|
|
|
|
// Show only to admins
|
|
if (currentUser?.isAdmin ?? false) ...[
|
|
const Text('Admin Panel'),
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
// Navigate to admin panel
|
|
},
|
|
child: const Text('Manage Users'),
|
|
),
|
|
],
|
|
|
|
// Show only to managers
|
|
if (currentUser?.isManager ?? false) ...[
|
|
const Text('Manager Tools'),
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
// Navigate to manager tools
|
|
},
|
|
child: const Text('View Reports'),
|
|
),
|
|
],
|
|
|
|
// Show only to cashiers
|
|
if (currentUser?.isCashier ?? false) ...[
|
|
const Text('POS Terminal'),
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
// Navigate to POS
|
|
},
|
|
child: const Text('Start Transaction'),
|
|
),
|
|
],
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// EXAMPLE 9: Error Handling
|
|
// ============================================================================
|
|
|
|
class ErrorHandlingExample extends ConsumerWidget {
|
|
const ErrorHandlingExample({super.key});
|
|
|
|
Future<void> _handleLogin(BuildContext context, WidgetRef ref) async {
|
|
final success = await ref.read(authProvider.notifier).login(
|
|
email: 'test@example.com',
|
|
password: 'password',
|
|
);
|
|
|
|
if (!context.mounted) return;
|
|
|
|
if (!success) {
|
|
final error = ref.read(authProvider).errorMessage;
|
|
|
|
// Different error messages result in different UI feedback
|
|
String userMessage;
|
|
Color backgroundColor;
|
|
|
|
if (error?.contains('Invalid email or password') ?? false) {
|
|
userMessage = 'Incorrect email or password. Please try again.';
|
|
backgroundColor = Colors.red;
|
|
} else if (error?.contains('Network') ?? false) {
|
|
userMessage = 'No internet connection. Please check your network.';
|
|
backgroundColor = Colors.orange;
|
|
} else if (error?.contains('Server') ?? false) {
|
|
userMessage = 'Server error. Please try again later.';
|
|
backgroundColor = Colors.red[700]!;
|
|
} else {
|
|
userMessage = error ?? 'Login failed. Please try again.';
|
|
backgroundColor = Colors.red;
|
|
}
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(userMessage),
|
|
backgroundColor: backgroundColor,
|
|
action: SnackBarAction(
|
|
label: 'Retry',
|
|
textColor: Colors.white,
|
|
onPressed: () => _handleLogin(context, ref),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
return ElevatedButton(
|
|
onPressed: () => _handleLogin(context, ref),
|
|
child: const Text('Login with Error Handling'),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// EXAMPLE 10: Using Auth in Non-Widget Code
|
|
// ============================================================================
|
|
|
|
void nonWidgetExample() {
|
|
// If you need to access auth outside widgets (e.g., in services),
|
|
// you can pass WidgetRef as a parameter or use ProviderContainer:
|
|
|
|
// Method 1: Pass WidgetRef as parameter
|
|
// Future<void> myService(WidgetRef ref) async {
|
|
// final authRepository = ref.read(authRepositoryProvider);
|
|
// final isAuthenticated = await authRepository.isAuthenticated();
|
|
// print('Is authenticated: $isAuthenticated');
|
|
// }
|
|
|
|
// Method 2: Use ProviderContainer (for non-Flutter code)
|
|
// final container = ProviderContainer();
|
|
// final authRepository = container.read(authRepositoryProvider);
|
|
// final isAuthenticated = await authRepository.isAuthenticated();
|
|
// container.dispose(); // Don't forget to dispose!
|
|
}
|
|
|
|
// ============================================================================
|
|
// EXAMPLE 11: Automatic Token Injection Test
|
|
// ============================================================================
|
|
|
|
void tokenInjectionExample() {
|
|
// Once logged in, all API requests automatically include the JWT token:
|
|
//
|
|
// The DioClient interceptor adds this header to all requests:
|
|
// Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|
//
|
|
// You don't need to manually add the token - it's automatic!
|
|
|
|
// Example of making an API call after login:
|
|
// Using Riverpod:
|
|
// final dioClient = ref.read(dioClientProvider);
|
|
// final response = await dioClient.get('/api/products');
|
|
//
|
|
// The above request will automatically include:
|
|
// Headers: {
|
|
// "Authorization": "Bearer <your-jwt-token>",
|
|
// "Content-Type": "application/json",
|
|
// "Accept": "application/json"
|
|
// }
|
|
}
|