update api

This commit is contained in:
Phuoc Nguyen
2025-10-10 17:15:40 +07:00
parent b94c158004
commit 04f7042b8d
24 changed files with 3322 additions and 8 deletions

472
lib/features/auth/README.md Normal file
View File

@@ -0,0 +1,472 @@
# Authentication Feature
Complete JWT-based authentication system for the Retail POS app.
## Overview
This feature implements a production-ready authentication system with:
- User login with email/password
- User registration
- JWT token management with secure storage
- Automatic token injection in API requests
- Profile management
- Token refresh capability
- Proper error handling
## Architecture
The authentication feature follows Clean Architecture principles:
```
auth/
├── domain/ # Business logic layer
│ ├── entities/ # Core business objects
│ │ ├── user.dart
│ │ └── auth_response.dart
│ └── repositories/ # Repository interfaces
│ └── auth_repository.dart
├── data/ # Data layer
│ ├── models/ # Data transfer objects and models
│ │ ├── login_dto.dart
│ │ ├── register_dto.dart
│ │ ├── user_model.dart
│ │ └── auth_response_model.dart
│ ├── datasources/ # Remote data sources
│ │ └── auth_remote_datasource.dart
│ └── repositories/ # Repository implementations
│ └── auth_repository_impl.dart
└── presentation/ # UI layer
├── providers/ # Riverpod state management
│ └── auth_provider.dart
├── pages/ # UI screens
│ ├── login_page.dart
│ └── register_page.dart
└── widgets/ # Reusable UI components
```
## API Endpoints
Base URL: `http://localhost:3000/api`
### 1. Login
```
POST /auth/login
Content-Type: application/json
Request:
{
"email": "user@example.com",
"password": "Password123!"
}
Response (200):
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "uuid",
"name": "John Doe",
"email": "user@example.com",
"roles": ["user"],
"isActive": true,
"createdAt": "2025-01-01T00:00:00.000Z",
"updatedAt": "2025-01-01T00:00:00.000Z"
}
}
```
### 2. Register
```
POST /auth/register
Content-Type: application/json
Request:
{
"name": "John Doe",
"email": "user@example.com",
"password": "Password123!",
"roles": ["user"]
}
Response (201):
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": { ... }
}
```
### 3. Get Profile
```
GET /auth/profile
Authorization: Bearer {token}
Response (200):
{
"id": "uuid",
"name": "John Doe",
"email": "user@example.com",
"roles": ["user"],
"isActive": true,
"createdAt": "2025-01-01T00:00:00.000Z",
"updatedAt": "2025-01-01T00:00:00.000Z"
}
```
### 4. Refresh Token
```
POST /auth/refresh
Authorization: Bearer {token}
Response (200):
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": { ... }
}
```
## Usage
### 1. Setup (Already configured in injection_container.dart)
The authentication system is registered in the dependency injection container:
```dart
// In lib/core/di/injection_container.dart
Future<void> initDependencies() async {
// Secure Storage
sl.registerLazySingleton<SecureStorage>(() => SecureStorage());
// Auth Remote Data Source
sl.registerLazySingleton<AuthRemoteDataSource>(
() => AuthRemoteDataSourceImpl(dioClient: sl()),
);
// Auth Repository
sl.registerLazySingleton<AuthRepository>(
() => AuthRepositoryImpl(
remoteDataSource: sl(),
secureStorage: sl(),
dioClient: sl(),
),
);
}
```
### 2. Login Flow
```dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:retail/features/auth/presentation/providers/auth_provider.dart';
// In a widget
class MyWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
return ElevatedButton(
onPressed: () async {
final success = await ref.read(authProvider.notifier).login(
email: 'user@example.com',
password: 'Password123!',
);
if (success) {
// Navigate to home
} else {
// Show error
final error = ref.read(authProvider).errorMessage;
print('Login failed: $error');
}
},
child: Text('Login'),
);
}
}
```
### 3. Check Authentication Status
```dart
// Check if user is authenticated
final isAuthenticated = ref.watch(isAuthenticatedProvider);
// Get current user
final user = ref.watch(currentUserProvider);
if (user != null) {
print('Welcome ${user.name}!');
print('Roles: ${user.roles}');
print('Is Admin: ${user.isAdmin}');
}
```
### 4. Logout
```dart
await ref.read(authProvider.notifier).logout();
```
### 5. Register New User
```dart
final success = await ref.read(authProvider.notifier).register(
name: 'John Doe',
email: 'john@example.com',
password: 'Password123!',
roles: ['user'], // Optional, defaults to ['user']
);
```
### 6. Refresh Token
```dart
final success = await ref.read(authProvider.notifier).refreshToken();
if (!success) {
// Token refresh failed, user logged out automatically
}
```
## Bearer Token Injection
The authentication system automatically injects the JWT bearer token into all API requests:
### How it Works
1. **On Login/Register**: Token is saved to secure storage and set in DioClient
2. **Automatic Injection**: DioClient interceptor adds `Authorization: Bearer {token}` header to all requests
3. **On Logout**: Token is cleared from secure storage and DioClient
4. **On App Start**: Token is loaded from secure storage and set in DioClient if valid
### Implementation Details
```dart
// In lib/core/network/dio_client.dart
class DioClient {
String? _authToken;
DioClient() {
_dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) {
if (_authToken != null) {
options.headers['Authorization'] = 'Bearer $_authToken';
}
return handler.next(options);
},
),
);
}
void setAuthToken(String token) {
_authToken = token;
}
void clearAuthToken() {
_authToken = null;
}
}
```
### Manual Token Management (Advanced)
If you need to manually manage tokens:
```dart
// Get token from storage
final token = await sl<SecureStorage>().getAccessToken();
// Set token in DioClient
sl<DioClient>().setAuthToken(token!);
// Clear token
sl<DioClient>().clearAuthToken();
await sl<SecureStorage>().deleteAllTokens();
```
## Error Handling
The authentication system handles various error scenarios:
### Error Types
1. **InvalidCredentialsFailure**: Wrong email or password
2. **ConflictFailure**: Email already exists (registration)
3. **UnauthorizedFailure**: Invalid or expired token
4. **NetworkFailure**: No internet connection
5. **ServerFailure**: Server errors (500, 503, etc.)
6. **ValidationFailure**: Invalid input data
### Error Handling Example
```dart
final success = await ref.read(authProvider.notifier).login(
email: email,
password: password,
);
if (!success) {
final error = ref.read(authProvider).errorMessage;
// Show user-friendly error message
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(error ?? 'Login failed'),
backgroundColor: Colors.red,
),
);
}
```
## Secure Storage
JWT tokens are stored securely using `flutter_secure_storage`:
- **iOS**: Keychain
- **Android**: EncryptedSharedPreferences
- **Web**: Web Crypto API
- **Desktop**: Platform-specific secure storage
```dart
// Access secure storage
final secureStorage = sl<SecureStorage>();
// Save token
await secureStorage.saveAccessToken(token);
// Get token
final token = await secureStorage.getAccessToken();
// Check if token exists
final hasToken = await secureStorage.hasAccessToken();
// Delete token
await secureStorage.deleteAllTokens();
```
## State Management
The authentication state is managed using Riverpod 3.0:
### AuthState
```dart
class AuthState {
final User? user; // Current authenticated user
final bool isAuthenticated; // Authentication status
final bool isLoading; // Loading state
final String? errorMessage; // Error message (if any)
}
```
### Watching Auth State
```dart
// Watch entire auth state
final authState = ref.watch(authProvider);
// Access specific properties
final user = authState.user;
final isLoading = authState.isLoading;
final error = authState.errorMessage;
// Or use convenience providers
final isAuthenticated = ref.watch(isAuthenticatedProvider);
final currentUser = ref.watch(currentUserProvider);
```
## Protected Routes
Implement route guards to protect authenticated routes:
```dart
class AuthGuard extends ConsumerWidget {
final Widget child;
const AuthGuard({required this.child});
@override
Widget build(BuildContext context, WidgetRef ref) {
final isAuthenticated = ref.watch(isAuthenticatedProvider);
final isLoading = ref.watch(authProvider.select((s) => s.isLoading));
if (isLoading) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
if (!isAuthenticated) {
return const LoginPage();
}
return child;
}
}
// Usage
MaterialApp(
home: AuthGuard(
child: HomePage(),
),
);
```
## Testing
### Unit Tests
```dart
test('login should return success with valid credentials', () async {
// Arrange
final repository = MockAuthRepository();
when(repository.login(email: any, password: any))
.thenAnswer((_) async => Right(mockAuthResponse));
// Act
final result = await repository.login(
email: 'test@example.com',
password: 'Password123!',
);
// Assert
expect(result.isRight(), true);
});
```
### Integration Tests
Test the complete authentication flow from UI to API.
## Code Generation
Run code generation for Riverpod providers:
```bash
# Generate auth_provider.g.dart
flutter pub run build_runner build --delete-conflicting-outputs
# Watch for changes
flutter pub run build_runner watch
```
## Production Considerations
1. **HTTPS**: Always use HTTPS in production
2. **Token Expiry**: Implement automatic token refresh on 401 errors
3. **Biometric Auth**: Add fingerprint/face ID support
4. **Password Strength**: Enforce strong password requirements
5. **Rate Limiting**: Handle rate limiting (429 errors)
6. **Secure Storage**: Tokens are already stored securely
7. **Session Management**: Clear tokens on app uninstall
## Future Enhancements
- [ ] Biometric authentication (fingerprint, face ID)
- [ ] Remember me functionality
- [ ] Password reset flow
- [ ] Email verification
- [ ] Social login (Google, Apple, Facebook)
- [ ] Multi-factor authentication (MFA)
- [ ] Session timeout warning
- [ ] Device management

View File

@@ -0,0 +1,159 @@
import 'package:dio/dio.dart';
import '../../../../core/constants/api_constants.dart';
import '../../../../core/errors/exceptions.dart';
import '../../../../core/network/dio_client.dart';
import '../models/auth_response_model.dart';
import '../models/login_dto.dart';
import '../models/register_dto.dart';
import '../models/user_model.dart';
/// Remote data source for authentication operations
abstract class AuthRemoteDataSource {
/// Login user with email and password
Future<AuthResponseModel> login(LoginDto loginDto);
/// Register new user
Future<AuthResponseModel> register(RegisterDto registerDto);
/// Get current user profile
Future<UserModel> getProfile();
/// Refresh access token
Future<AuthResponseModel> refreshToken();
}
/// Implementation of AuthRemoteDataSource
class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
final DioClient dioClient;
AuthRemoteDataSourceImpl({required this.dioClient});
@override
Future<AuthResponseModel> login(LoginDto loginDto) async {
try {
final response = await dioClient.post(
ApiConstants.login,
data: loginDto.toJson(),
);
if (response.statusCode == ApiConstants.statusOk) {
return AuthResponseModel.fromJson(response.data);
} else {
throw ServerException('Login failed with status: ${response.statusCode}');
}
} on DioException catch (e) {
throw _handleDioError(e);
} catch (e) {
throw ServerException('Unexpected error during login: $e');
}
}
@override
Future<AuthResponseModel> register(RegisterDto registerDto) async {
try {
final response = await dioClient.post(
ApiConstants.register,
data: registerDto.toJson(),
);
if (response.statusCode == ApiConstants.statusCreated) {
return AuthResponseModel.fromJson(response.data);
} else {
throw ServerException('Registration failed with status: ${response.statusCode}');
}
} on DioException catch (e) {
throw _handleDioError(e);
} catch (e) {
throw ServerException('Unexpected error during registration: $e');
}
}
@override
Future<UserModel> getProfile() async {
try {
final response = await dioClient.get(ApiConstants.profile);
if (response.statusCode == ApiConstants.statusOk) {
return UserModel.fromJson(response.data);
} else {
throw ServerException('Get profile failed with status: ${response.statusCode}');
}
} on DioException catch (e) {
throw _handleDioError(e);
} catch (e) {
throw ServerException('Unexpected error getting profile: $e');
}
}
@override
Future<AuthResponseModel> refreshToken() async {
try {
final response = await dioClient.post(ApiConstants.refreshToken);
if (response.statusCode == ApiConstants.statusOk) {
return AuthResponseModel.fromJson(response.data);
} else {
throw ServerException('Token refresh failed with status: ${response.statusCode}');
}
} on DioException catch (e) {
throw _handleDioError(e);
} catch (e) {
throw ServerException('Unexpected error refreshing token: $e');
}
}
/// Handle Dio errors and convert to custom exceptions
Exception _handleDioError(DioException error) {
switch (error.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
return NetworkException('Connection timeout. Please check your internet connection.');
case DioExceptionType.badResponse:
final statusCode = error.response?.statusCode;
final message = error.response?.data?['message'] ?? error.message;
switch (statusCode) {
case ApiConstants.statusUnauthorized:
return InvalidCredentialsException(message ?? 'Invalid email or password');
case ApiConstants.statusForbidden:
return UnauthorizedException(message ?? 'Access forbidden');
case ApiConstants.statusNotFound:
return NotFoundException(message ?? 'Resource not found');
case ApiConstants.statusUnprocessableEntity:
return ValidationException(message ?? 'Validation failed');
case 409: // Conflict
return ConflictException(message ?? 'Email already exists');
case ApiConstants.statusTooManyRequests:
return ServerException('Too many requests. Please try again later.');
case ApiConstants.statusInternalServerError:
case ApiConstants.statusBadGateway:
case ApiConstants.statusServiceUnavailable:
case ApiConstants.statusGatewayTimeout:
return ServerException(message ?? 'Server error. Please try again later.');
default:
return ServerException(message ?? 'Unknown error occurred');
}
case DioExceptionType.connectionError:
return NetworkException('No internet connection. Please check your network.');
case DioExceptionType.badCertificate:
return NetworkException('SSL certificate error');
case DioExceptionType.cancel:
return NetworkException('Request was cancelled');
default:
return ServerException(error.message ?? 'Unknown error occurred');
}
}
}

View File

@@ -0,0 +1,42 @@
import '../../domain/entities/auth_response.dart';
import 'user_model.dart';
/// AuthResponse model for data layer (extends AuthResponse entity)
class AuthResponseModel extends AuthResponse {
const AuthResponseModel({
required super.accessToken,
required super.user,
});
/// Create AuthResponseModel from JSON
factory AuthResponseModel.fromJson(Map<String, dynamic> json) {
return AuthResponseModel(
accessToken: json['access_token'] as String,
user: UserModel.fromJson(json['user'] as Map<String, dynamic>),
);
}
/// Convert AuthResponseModel to JSON
Map<String, dynamic> toJson() {
return {
'access_token': accessToken,
'user': (user as UserModel).toJson(),
};
}
/// Create AuthResponseModel from AuthResponse entity
factory AuthResponseModel.fromEntity(AuthResponse authResponse) {
return AuthResponseModel(
accessToken: authResponse.accessToken,
user: authResponse.user,
);
}
/// Convert to AuthResponse entity
AuthResponse toEntity() {
return AuthResponse(
accessToken: accessToken,
user: user,
);
}
}

View File

@@ -0,0 +1,18 @@
/// Login request Data Transfer Object
class LoginDto {
final String email;
final String password;
const LoginDto({
required this.email,
required this.password,
});
/// Convert to JSON for API request
Map<String, dynamic> toJson() {
return {
'email': email,
'password': password,
};
}
}

View File

@@ -0,0 +1,24 @@
/// Register request Data Transfer Object
class RegisterDto {
final String name;
final String email;
final String password;
final List<String> roles;
const RegisterDto({
required this.name,
required this.email,
required this.password,
this.roles = const ['user'],
});
/// Convert to JSON for API request
Map<String, dynamic> toJson() {
return {
'name': name,
'email': email,
'password': password,
'roles': roles,
};
}
}

View File

@@ -0,0 +1,66 @@
import '../../domain/entities/user.dart';
/// User model for data layer (extends User entity)
class UserModel extends User {
const UserModel({
required super.id,
required super.name,
required super.email,
required super.roles,
required super.isActive,
required super.createdAt,
required super.updatedAt,
});
/// Create UserModel from JSON
factory UserModel.fromJson(Map<String, dynamic> json) {
return UserModel(
id: json['id'] as String,
name: json['name'] as String,
email: json['email'] as String,
roles: (json['roles'] as List<dynamic>).cast<String>(),
isActive: json['isActive'] as bool? ?? true,
createdAt: DateTime.parse(json['createdAt'] as String),
updatedAt: DateTime.parse(json['updatedAt'] as String),
);
}
/// Convert UserModel to JSON
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'email': email,
'roles': roles,
'isActive': isActive,
'createdAt': createdAt.toIso8601String(),
'updatedAt': updatedAt.toIso8601String(),
};
}
/// Create UserModel from User entity
factory UserModel.fromEntity(User user) {
return UserModel(
id: user.id,
name: user.name,
email: user.email,
roles: user.roles,
isActive: user.isActive,
createdAt: user.createdAt,
updatedAt: user.updatedAt,
);
}
/// Convert to User entity
User toEntity() {
return User(
id: id,
name: name,
email: email,
roles: roles,
isActive: isActive,
createdAt: createdAt,
updatedAt: updatedAt,
);
}
}

View File

@@ -0,0 +1,175 @@
import 'package:dartz/dartz.dart';
import '../../../../core/errors/exceptions.dart';
import '../../../../core/errors/failures.dart';
import '../../../../core/network/dio_client.dart';
import '../../../../core/storage/secure_storage.dart';
import '../../domain/entities/auth_response.dart';
import '../../domain/entities/user.dart';
import '../../domain/repositories/auth_repository.dart';
import '../datasources/auth_remote_datasource.dart';
import '../models/login_dto.dart';
import '../models/register_dto.dart';
/// Implementation of AuthRepository
class AuthRepositoryImpl implements AuthRepository {
final AuthRemoteDataSource remoteDataSource;
final SecureStorage secureStorage;
final DioClient dioClient;
AuthRepositoryImpl({
required this.remoteDataSource,
required this.secureStorage,
required this.dioClient,
});
@override
Future<Either<Failure, AuthResponse>> login({
required String email,
required String password,
}) async {
try {
final loginDto = LoginDto(email: email, password: password);
final authResponse = await remoteDataSource.login(loginDto);
// Save token to secure storage
await secureStorage.saveAccessToken(authResponse.accessToken);
// Set token in Dio client for subsequent requests
dioClient.setAuthToken(authResponse.accessToken);
return Right(authResponse);
} on InvalidCredentialsException catch (e) {
return Left(InvalidCredentialsFailure(e.message));
} on UnauthorizedException catch (e) {
return Left(UnauthorizedFailure(e.message));
} on ValidationException catch (e) {
return Left(ValidationFailure(e.message));
} on NetworkException catch (e) {
return Left(NetworkFailure(e.message));
} on ServerException catch (e) {
return Left(ServerFailure(e.message));
} catch (e) {
return Left(ServerFailure('Unexpected error: $e'));
}
}
@override
Future<Either<Failure, AuthResponse>> register({
required String name,
required String email,
required String password,
List<String> roles = const ['user'],
}) async {
try {
final registerDto = RegisterDto(
name: name,
email: email,
password: password,
roles: roles,
);
final authResponse = await remoteDataSource.register(registerDto);
// Save token to secure storage
await secureStorage.saveAccessToken(authResponse.accessToken);
// Set token in Dio client for subsequent requests
dioClient.setAuthToken(authResponse.accessToken);
return Right(authResponse);
} on ConflictException catch (e) {
return Left(ConflictFailure(e.message));
} on ValidationException catch (e) {
return Left(ValidationFailure(e.message));
} on NetworkException catch (e) {
return Left(NetworkFailure(e.message));
} on ServerException catch (e) {
return Left(ServerFailure(e.message));
} catch (e) {
return Left(ServerFailure('Unexpected error: $e'));
}
}
@override
Future<Either<Failure, User>> getProfile() async {
try {
final user = await remoteDataSource.getProfile();
return Right(user);
} on UnauthorizedException catch (e) {
return Left(UnauthorizedFailure(e.message));
} on TokenExpiredException catch (e) {
return Left(TokenExpiredFailure(e.message));
} on NetworkException catch (e) {
return Left(NetworkFailure(e.message));
} on ServerException catch (e) {
return Left(ServerFailure(e.message));
} catch (e) {
return Left(ServerFailure('Unexpected error: $e'));
}
}
@override
Future<Either<Failure, AuthResponse>> refreshToken() async {
try {
final authResponse = await remoteDataSource.refreshToken();
// Update token in secure storage
await secureStorage.saveAccessToken(authResponse.accessToken);
// Update token in Dio client
dioClient.setAuthToken(authResponse.accessToken);
return Right(authResponse);
} on UnauthorizedException catch (e) {
return Left(UnauthorizedFailure(e.message));
} on TokenExpiredException catch (e) {
return Left(TokenExpiredFailure(e.message));
} on NetworkException catch (e) {
return Left(NetworkFailure(e.message));
} on ServerException catch (e) {
return Left(ServerFailure(e.message));
} catch (e) {
return Left(ServerFailure('Unexpected error: $e'));
}
}
@override
Future<Either<Failure, void>> logout() async {
try {
// Clear token from secure storage
await secureStorage.deleteAllTokens();
// Clear token from Dio client
dioClient.clearAuthToken();
return const Right(null);
} catch (e) {
return Left(CacheFailure('Failed to logout: $e'));
}
}
@override
Future<bool> isAuthenticated() async {
try {
final hasToken = await secureStorage.hasAccessToken();
if (hasToken) {
final token = await secureStorage.getAccessToken();
if (token != null) {
dioClient.setAuthToken(token);
return true;
}
}
return false;
} catch (e) {
return false;
}
}
@override
Future<String?> getAccessToken() async {
try {
return await secureStorage.getAccessToken();
} catch (e) {
return null;
}
}
}

View File

@@ -0,0 +1,16 @@
import 'package:equatable/equatable.dart';
import 'user.dart';
/// Authentication response entity
class AuthResponse extends Equatable {
final String accessToken;
final User user;
const AuthResponse({
required this.accessToken,
required this.user,
});
@override
List<Object?> get props => [accessToken, user];
}

View File

@@ -0,0 +1,45 @@
import 'package:equatable/equatable.dart';
/// User entity representing a user in the system
class User extends Equatable {
final String id;
final String name;
final String email;
final List<String> roles;
final bool isActive;
final DateTime createdAt;
final DateTime updatedAt;
const User({
required this.id,
required this.name,
required this.email,
required this.roles,
required this.isActive,
required this.createdAt,
required this.updatedAt,
});
@override
List<Object?> get props => [
id,
name,
email,
roles,
isActive,
createdAt,
updatedAt,
];
/// Check if user has a specific role
bool hasRole(String role) => roles.contains(role);
/// Check if user is admin
bool get isAdmin => hasRole('admin');
/// Check if user is manager
bool get isManager => hasRole('manager');
/// Check if user is cashier
bool get isCashier => hasRole('cashier');
}

View File

@@ -0,0 +1,36 @@
import 'package:dartz/dartz.dart';
import '../../../../core/errors/failures.dart';
import '../entities/auth_response.dart';
import '../entities/user.dart';
/// Abstract repository for authentication operations
abstract class AuthRepository {
/// Login user with email and password
Future<Either<Failure, AuthResponse>> login({
required String email,
required String password,
});
/// Register new user
Future<Either<Failure, AuthResponse>> register({
required String name,
required String email,
required String password,
List<String> roles = const ['user'],
});
/// Get current user profile
Future<Either<Failure, User>> getProfile();
/// Refresh access token
Future<Either<Failure, AuthResponse>> refreshToken();
/// Logout user (clear local token)
Future<Either<Failure, void>> logout();
/// Check if user is authenticated
Future<bool> isAuthenticated();
/// Get stored access token
Future<String?> getAccessToken();
}

View File

@@ -0,0 +1,488 @@
// 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"
// }
}

View File

@@ -0,0 +1,171 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/auth_provider.dart';
/// Login page for user authentication
class LoginPage extends ConsumerStatefulWidget {
const LoginPage({super.key});
@override
ConsumerState<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends ConsumerState<LoginPage> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
bool _obscurePassword = true;
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
Future<void> _handleLogin() async {
if (!_formKey.currentState!.validate()) return;
final success = await ref.read(authProvider.notifier).login(
email: _emailController.text.trim(),
password: _passwordController.text,
);
if (!mounted) return;
if (success) {
// Navigate to home or show success
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Login successful!')),
);
// TODO: Navigate to home page
} else {
final errorMessage = ref.read(authProvider).errorMessage;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(errorMessage ?? 'Login failed'),
backgroundColor: Colors.red,
),
);
}
}
@override
Widget build(BuildContext context) {
final authState = ref.watch(authProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Login'),
),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Logo or app name
Icon(
Icons.shopping_cart,
size: 80,
color: Theme.of(context).primaryColor,
),
const SizedBox(height: 16),
Text(
'Retail POS',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 48),
// Email field
TextFormField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'Email',
prefixIcon: Icon(Icons.email),
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your email';
}
if (!value.contains('@')) {
return 'Please enter a valid email';
}
return null;
},
),
const SizedBox(height: 16),
// Password field
TextFormField(
controller: _passwordController,
obscureText: _obscurePassword,
decoration: InputDecoration(
labelText: 'Password',
prefixIcon: const Icon(Icons.lock),
border: const OutlineInputBorder(),
suffixIcon: IconButton(
icon: Icon(
_obscurePassword
? Icons.visibility
: Icons.visibility_off,
),
onPressed: () {
setState(() {
_obscurePassword = !_obscurePassword;
});
},
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your password';
}
if (value.length < 8) {
return 'Password must be at least 8 characters';
}
return null;
},
),
const SizedBox(height: 24),
// Login button
FilledButton(
onPressed: authState.isLoading ? null : _handleLogin,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: authState.isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
: const Text('Login'),
),
),
const SizedBox(height: 16),
// Register link
TextButton(
onPressed: () {
// TODO: Navigate to register page
// Navigator.push(context, MaterialPageRoute(builder: (_) => const RegisterPage()));
},
child: const Text('Don\'t have an account? Register'),
),
],
),
),
),
),
);
}
}

View File

@@ -0,0 +1,234 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/auth_provider.dart';
/// Register page for new user registration
class RegisterPage extends ConsumerStatefulWidget {
const RegisterPage({super.key});
@override
ConsumerState<RegisterPage> createState() => _RegisterPageState();
}
class _RegisterPageState extends ConsumerState<RegisterPage> {
final _formKey = GlobalKey<FormState>();
final _nameController = TextEditingController();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
final _confirmPasswordController = TextEditingController();
bool _obscurePassword = true;
bool _obscureConfirmPassword = true;
@override
void dispose() {
_nameController.dispose();
_emailController.dispose();
_passwordController.dispose();
_confirmPasswordController.dispose();
super.dispose();
}
Future<void> _handleRegister() async {
if (!_formKey.currentState!.validate()) return;
final success = await ref.read(authProvider.notifier).register(
name: _nameController.text.trim(),
email: _emailController.text.trim(),
password: _passwordController.text,
);
if (!mounted) return;
if (success) {
// Navigate to home or show success
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Registration successful!')),
);
// TODO: Navigate to home page
} else {
final errorMessage = ref.read(authProvider).errorMessage;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(errorMessage ?? 'Registration failed'),
backgroundColor: Colors.red,
),
);
}
}
@override
Widget build(BuildContext context) {
final authState = ref.watch(authProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Register'),
),
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 24),
// Logo or app name
Icon(
Icons.shopping_cart,
size: 80,
color: Theme.of(context).primaryColor,
),
const SizedBox(height: 16),
Text(
'Create Account',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 48),
// Name field
TextFormField(
controller: _nameController,
decoration: const InputDecoration(
labelText: 'Full Name',
prefixIcon: Icon(Icons.person),
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your name';
}
if (value.length < 2) {
return 'Name must be at least 2 characters';
}
return null;
},
),
const SizedBox(height: 16),
// Email field
TextFormField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'Email',
prefixIcon: Icon(Icons.email),
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your email';
}
if (!value.contains('@')) {
return 'Please enter a valid email';
}
return null;
},
),
const SizedBox(height: 16),
// Password field
TextFormField(
controller: _passwordController,
obscureText: _obscurePassword,
decoration: InputDecoration(
labelText: 'Password',
prefixIcon: const Icon(Icons.lock),
border: const OutlineInputBorder(),
suffixIcon: IconButton(
icon: Icon(
_obscurePassword
? Icons.visibility
: Icons.visibility_off,
),
onPressed: () {
setState(() {
_obscurePassword = !_obscurePassword;
});
},
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your password';
}
if (value.length < 8) {
return 'Password must be at least 8 characters';
}
// Check for uppercase, lowercase, and number
if (!RegExp(r'(?=.*[a-z])(?=.*[A-Z])(?=.*\d)').hasMatch(value)) {
return 'Password must contain uppercase, lowercase, and number';
}
return null;
},
),
const SizedBox(height: 16),
// Confirm password field
TextFormField(
controller: _confirmPasswordController,
obscureText: _obscureConfirmPassword,
decoration: InputDecoration(
labelText: 'Confirm Password',
prefixIcon: const Icon(Icons.lock_outline),
border: const OutlineInputBorder(),
suffixIcon: IconButton(
icon: Icon(
_obscureConfirmPassword
? Icons.visibility
: Icons.visibility_off,
),
onPressed: () {
setState(() {
_obscureConfirmPassword = !_obscureConfirmPassword;
});
},
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please confirm your password';
}
if (value != _passwordController.text) {
return 'Passwords do not match';
}
return null;
},
),
const SizedBox(height: 24),
// Register button
FilledButton(
onPressed: authState.isLoading ? null : _handleRegister,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: authState.isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
: const Text('Register'),
),
),
const SizedBox(height: 16),
// Login link
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Already have an account? Login'),
),
],
),
),
),
),
);
}
}

View File

@@ -0,0 +1,215 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../../../../core/di/injection_container.dart';
import '../../domain/entities/user.dart';
import '../../domain/repositories/auth_repository.dart';
part 'auth_provider.g.dart';
/// Provider for AuthRepository
@riverpod
AuthRepository authRepository(Ref ref) {
return sl<AuthRepository>();
}
/// Auth state class
class AuthState {
final User? user;
final bool isAuthenticated;
final bool isLoading;
final String? errorMessage;
const AuthState({
this.user,
this.isAuthenticated = false,
this.isLoading = false,
this.errorMessage,
});
AuthState copyWith({
User? user,
bool? isAuthenticated,
bool? isLoading,
String? errorMessage,
}) {
return AuthState(
user: user ?? this.user,
isAuthenticated: isAuthenticated ?? this.isAuthenticated,
isLoading: isLoading ?? this.isLoading,
errorMessage: errorMessage ?? this.errorMessage,
);
}
}
/// Auth state notifier provider
@riverpod
class Auth extends _$Auth {
@override
AuthState build() {
_checkAuthStatus();
return const AuthState();
}
AuthRepository get _repository => ref.read(authRepositoryProvider);
/// Check if user is authenticated on app start
Future<void> _checkAuthStatus() async {
state = state.copyWith(isLoading: true);
final isAuthenticated = await _repository.isAuthenticated();
if (isAuthenticated) {
// Get user profile
final result = await _repository.getProfile();
result.fold(
(failure) {
state = const AuthState(
isAuthenticated: false,
isLoading: false,
);
},
(user) {
state = AuthState(
user: user,
isAuthenticated: true,
isLoading: false,
);
},
);
} else {
state = const AuthState(
isAuthenticated: false,
isLoading: false,
);
}
}
/// Login user
Future<bool> login({
required String email,
required String password,
}) async {
state = state.copyWith(isLoading: true, errorMessage: null);
final result = await _repository.login(email: email, password: password);
return result.fold(
(failure) {
state = state.copyWith(
isLoading: false,
errorMessage: failure.message,
);
return false;
},
(authResponse) {
state = AuthState(
user: authResponse.user,
isAuthenticated: true,
isLoading: false,
errorMessage: null,
);
return true;
},
);
}
/// Register new user
Future<bool> register({
required String name,
required String email,
required String password,
List<String> roles = const ['user'],
}) async {
state = state.copyWith(isLoading: true, errorMessage: null);
final result = await _repository.register(
name: name,
email: email,
password: password,
roles: roles,
);
return result.fold(
(failure) {
state = state.copyWith(
isLoading: false,
errorMessage: failure.message,
);
return false;
},
(authResponse) {
state = AuthState(
user: authResponse.user,
isAuthenticated: true,
isLoading: false,
errorMessage: null,
);
return true;
},
);
}
/// Get user profile (refresh user data)
Future<void> getProfile() async {
state = state.copyWith(isLoading: true, errorMessage: null);
final result = await _repository.getProfile();
result.fold(
(failure) {
state = state.copyWith(
isLoading: false,
errorMessage: failure.message,
);
},
(user) {
state = state.copyWith(
user: user,
isLoading: false,
);
},
);
}
/// Refresh access token
Future<bool> refreshToken() async {
final result = await _repository.refreshToken();
return result.fold(
(failure) {
// If token refresh fails, logout user
logout();
return false;
},
(authResponse) {
state = state.copyWith(user: authResponse.user);
return true;
},
);
}
/// Logout user
Future<void> logout() async {
state = state.copyWith(isLoading: true);
await _repository.logout();
state = const AuthState(
isAuthenticated: false,
isLoading: false,
);
}
}
/// Current authenticated user provider
@riverpod
User? currentUser(Ref ref) {
final authState = ref.watch(authProvider);
return authState.user;
}
/// Is authenticated provider
@riverpod
bool isAuthenticated(Ref ref) {
final authState = ref.watch(authProvider);
return authState.isAuthenticated;
}

View File

@@ -0,0 +1,204 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'auth_provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
/// Provider for AuthRepository
@ProviderFor(authRepository)
const authRepositoryProvider = AuthRepositoryProvider._();
/// Provider for AuthRepository
final class AuthRepositoryProvider
extends $FunctionalProvider<AuthRepository, AuthRepository, AuthRepository>
with $Provider<AuthRepository> {
/// Provider for AuthRepository
const AuthRepositoryProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'authRepositoryProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$authRepositoryHash();
@$internal
@override
$ProviderElement<AuthRepository> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
AuthRepository create(Ref ref) {
return authRepository(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(AuthRepository value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<AuthRepository>(value),
);
}
}
String _$authRepositoryHash() => r'0483b13ac95333b56a1a82f6c9fdb64ae46f287d';
/// Auth state notifier provider
@ProviderFor(Auth)
const authProvider = AuthProvider._();
/// Auth state notifier provider
final class AuthProvider extends $NotifierProvider<Auth, AuthState> {
/// Auth state notifier provider
const AuthProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'authProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$authHash();
@$internal
@override
Auth create() => Auth();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(AuthState value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<AuthState>(value),
);
}
}
String _$authHash() => r'c88e150224fa855ed0ddfba30bef9e2b289f329d';
/// Auth state notifier provider
abstract class _$Auth extends $Notifier<AuthState> {
AuthState build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<AuthState, AuthState>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<AuthState, AuthState>,
AuthState,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}
/// Current authenticated user provider
@ProviderFor(currentUser)
const currentUserProvider = CurrentUserProvider._();
/// Current authenticated user provider
final class CurrentUserProvider extends $FunctionalProvider<User?, User?, User?>
with $Provider<User?> {
/// Current authenticated user provider
const CurrentUserProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'currentUserProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$currentUserHash();
@$internal
@override
$ProviderElement<User?> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
User? create(Ref ref) {
return currentUser(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(User? value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<User?>(value),
);
}
}
String _$currentUserHash() => r'4c8cb60cef35a4fd001291434558037d6c85faf5';
/// Is authenticated provider
@ProviderFor(isAuthenticated)
const isAuthenticatedProvider = IsAuthenticatedProvider._();
/// Is authenticated provider
final class IsAuthenticatedProvider
extends $FunctionalProvider<bool, bool, bool>
with $Provider<bool> {
/// Is authenticated provider
const IsAuthenticatedProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'isAuthenticatedProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$isAuthenticatedHash();
@$internal
@override
$ProviderElement<bool> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
bool create(Ref ref) {
return isAuthenticated(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(bool value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<bool>(value),
);
}
}
String _$isAuthenticatedHash() => r'003f7e85bfa5ae774792659ce771b5b59ebf04f8';