Files
retail/lib/features/auth/example_usage.dart
Phuoc Nguyen 04f7042b8d update api
2025-10-10 17:15:40 +07:00

489 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),
// use the service locator directly:
// import 'package:retail/core/di/injection_container.dart';
// import 'package:retail/features/auth/domain/repositories/auth_repository.dart';
// final authRepository = sl<AuthRepository>();
//
// // Check if authenticated
// final isAuthenticated = await authRepository.isAuthenticated();
//
// // Get token
// final token = await authRepository.getAccessToken();
//
// print('Token: $token');
}
// ============================================================================
// 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:
// final response = await sl<DioClient>().get('/api/products');
//
// The above request will automatically include:
// Headers: {
// "Authorization": "Bearer <your-jwt-token>",
// "Content-Type": "application/json",
// "Accept": "application/json"
// }
}