feat: Refactor context reading in emergency contact and FAQs widgets

- Updated the context reading method in `EmergencyContactAddButton` and `EmergencyContactFormItem` to use `ReadContext`.
- Modified the `FaqsWidget` to utilize `ReadContext` for fetching FAQs.
- Adjusted the `PrivacySectionWidget` to read from `PrivacySecurityBloc` using `ReadContext`.

feat: Implement Firebase Auth isolation pattern

- Introduced `FirebaseAuthService` and `FirebaseAuthServiceImpl` to abstract Firebase Auth operations.
- Ensured features do not directly import `firebase_auth`, adhering to architecture rules.

feat: Create repository interfaces for billing and coverage

- Added `BillingRepositoryInterface` for billing-related operations.
- Created `CoverageRepositoryInterface` for coverage data access.

feat: Add use cases for order management

- Implemented use cases for fetching hubs, managers, and roles related to orders.
- Created `GetHubsUseCase`, `GetManagersByHubUseCase`, and `GetRolesByVendorUseCase`.

feat: Develop report use cases for client reports

- Added use cases for fetching various reports including coverage, daily operations, forecast, no-show, performance, and spend reports.
- Implemented `GetCoverageReportUseCase`, `GetDailyOpsReportUseCase`, `GetForecastReportUseCase`, `GetNoShowReportUseCase`, `GetPerformanceReportUseCase`, and `GetSpendReportUseCase`.

feat: Establish profile repository and use cases

- Created `ProfileRepositoryInterface` for staff profile data access.
- Implemented use cases for retrieving staff profile and section statuses: `GetStaffProfileUseCase` and `GetProfileSectionsUseCase`.
- Added `SignOutUseCase` for signing out the current user.
This commit is contained in:
Achintha Isuru
2026-03-19 01:10:27 -04:00
parent a45a3f6af1
commit 843eec5692
123 changed files with 2102 additions and 1087 deletions

View File

@@ -33,7 +33,10 @@ class ClientAuthenticationModule extends Module {
void binds(Injector i) {
// Repositories
i.addLazySingleton<AuthRepositoryInterface>(
() => AuthRepositoryImpl(apiService: i.get<BaseApiService>()),
() => AuthRepositoryImpl(
apiService: i.get<BaseApiService>(),
firebaseAuthService: i.get<FirebaseAuthService>(),
),
);
// UseCases

View File

@@ -1,7 +1,6 @@
import 'dart:developer' as developer;
import 'package:client_authentication/src/domain/repositories/auth_repository_interface.dart';
import 'package:firebase_auth/firebase_auth.dart' as firebase;
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart'
show
@@ -20,20 +19,23 @@ import 'package:krow_domain/krow_domain.dart'
/// Production implementation of the [AuthRepositoryInterface] for the client app.
///
/// Uses Firebase Auth client-side for sign-in (to maintain local auth state for
/// the [AuthInterceptor]), then calls V2 `GET /auth/session` to retrieve
/// business context. Sign-up provisioning (tenant, business, memberships) is
/// handled entirely server-side by the V2 API.
/// Uses [FirebaseAuthService] from core for local Firebase sign-in (to maintain
/// local auth state for the [AuthInterceptor]), then calls V2 `GET /auth/session`
/// to retrieve business context. Sign-up provisioning (tenant, business,
/// memberships) is handled entirely server-side by the V2 API.
class AuthRepositoryImpl implements AuthRepositoryInterface {
/// Creates an [AuthRepositoryImpl] with the given [BaseApiService].
AuthRepositoryImpl({required BaseApiService apiService})
: _apiService = apiService;
/// Creates an [AuthRepositoryImpl] with the given dependencies.
AuthRepositoryImpl({
required BaseApiService apiService,
required FirebaseAuthService firebaseAuthService,
}) : _apiService = apiService,
_firebaseAuthService = firebaseAuthService;
/// The V2 API service for backend calls.
final BaseApiService _apiService;
/// Firebase Auth instance for client-side sign-in/sign-up.
firebase.FirebaseAuth get _auth => firebase.FirebaseAuth.instance;
/// Core Firebase Auth service abstraction.
final FirebaseAuthService _firebaseAuthService;
@override
Future<User> signInWithEmail({
@@ -41,7 +43,7 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
required String password,
}) async {
try {
// Step 1: Call V2 sign-in endpoint server handles Firebase Auth
// Step 1: Call V2 sign-in endpoint -- server handles Firebase Auth
// via Identity Toolkit and returns a full auth envelope.
final ApiResponse response = await _apiService.post(
AuthEndpoints.clientSignIn,
@@ -53,19 +55,14 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
// Step 2: Sign in locally so AuthInterceptor can attach Bearer tokens
// to subsequent requests. The V2 API already validated credentials, so
// email/password sign-in establishes the local Firebase Auth state.
final firebase.UserCredential credential = await _auth
.signInWithEmailAndPassword(email: email, password: password);
final firebase.User? firebaseUser = credential.user;
if (firebaseUser == null) {
throw const SignInFailedException(
technicalMessage: 'Local Firebase sign-in failed after V2 sign-in',
);
}
await _firebaseAuthService.signInWithEmailAndPassword(
email: email,
password: password,
);
// Step 3: Populate session store from the V2 auth envelope directly
// (no need for a separate GET /auth/session call).
return _populateStoreFromAuthEnvelope(body, firebaseUser, email);
return _populateStoreFromAuthEnvelope(body, email);
} on AppException {
rethrow;
} catch (e) {
@@ -98,35 +95,34 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
// Step 2: Sign in locally to Firebase Auth so AuthInterceptor works
// for subsequent requests. The V2 API already created the Firebase
// account, so this should succeed.
final firebase.UserCredential credential = await _auth
.signInWithEmailAndPassword(email: email, password: password);
final firebase.User? firebaseUser = credential.user;
if (firebaseUser == null) {
try {
await _firebaseAuthService.signInWithEmailAndPassword(
email: email,
password: password,
);
} on SignInFailedException {
throw const SignUpFailedException(
technicalMessage: 'Local Firebase sign-in failed after V2 sign-up',
);
}
// Step 3: Populate store from the sign-up response envelope.
return _populateStoreFromAuthEnvelope(body, firebaseUser, email);
} on firebase.FirebaseAuthException catch (e) {
if (e.code == 'email-already-in-use') {
throw AccountExistsException(
technicalMessage: 'Firebase: ${e.message}',
);
} else if (e.code == 'weak-password') {
throw WeakPasswordException(technicalMessage: 'Firebase: ${e.message}');
} else if (e.code == 'network-request-failed') {
throw NetworkException(technicalMessage: 'Firebase: ${e.message}');
} else {
throw SignUpFailedException(
technicalMessage: 'Firebase auth error: ${e.message}',
);
}
return _populateStoreFromAuthEnvelope(body, email);
} on AppException {
rethrow;
} catch (e) {
// Map common Firebase-originated errors from the V2 API response
// to domain exceptions.
final String errorMessage = e.toString();
if (errorMessage.contains('EMAIL_EXISTS') ||
errorMessage.contains('email-already-in-use')) {
throw AccountExistsException(technicalMessage: errorMessage);
} else if (errorMessage.contains('WEAK_PASSWORD') ||
errorMessage.contains('weak-password')) {
throw WeakPasswordException(technicalMessage: errorMessage);
} else if (errorMessage.contains('network-request-failed')) {
throw NetworkException(technicalMessage: errorMessage);
}
throw SignUpFailedException(technicalMessage: 'Unexpected error: $e');
}
}
@@ -149,8 +145,8 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
}
try {
// Step 2: Sign out from local Firebase Auth.
await _auth.signOut();
// Step 2: Sign out from local Firebase Auth via core service.
await _firebaseAuthService.signOut();
} catch (e) {
throw Exception('Error signing out locally: $e');
}
@@ -167,7 +163,6 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
/// returns a domain [User].
User _populateStoreFromAuthEnvelope(
Map<String, dynamic> envelope,
firebase.User firebaseUser,
String fallbackEmail,
) {
final Map<String, dynamic>? userJson =
@@ -194,7 +189,8 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
ClientSessionStore.instance.setSession(clientSession);
}
final String userId = userJson?['id'] as String? ?? firebaseUser.uid;
final String userId = userJson?['id'] as String? ??
(_firebaseAuthService.currentUserUid ?? '');
final String email = userJson?['email'] as String? ?? fallbackEmail;
return User(

View File

@@ -14,7 +14,6 @@ dependencies:
flutter_bloc: ^8.1.0
flutter_modular: ^6.3.0
equatable: ^2.0.5
firebase_auth: ^6.1.2
# Architecture Packages
design_system: