From 51d53f658beceedd61daa53947439205ae263186 Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Mon, 16 Feb 2026 14:49:13 -0500 Subject: [PATCH 01/15] feat(data-error-handler): Replace print with debugPrint for unhandled exceptions logging --- .../data_connect/lib/src/mixins/data_error_handler.dart | 3 ++- apps/mobile/packages/data_connect/pubspec.yaml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/mobile/packages/data_connect/lib/src/mixins/data_error_handler.dart b/apps/mobile/packages/data_connect/lib/src/mixins/data_error_handler.dart index 27ca4624..aec89758 100644 --- a/apps/mobile/packages/data_connect/lib/src/mixins/data_error_handler.dart +++ b/apps/mobile/packages/data_connect/lib/src/mixins/data_error_handler.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/material.dart'; import 'package:krow_domain/krow_domain.dart'; /// Mixin to handle Data Layer errors and map them to Domain Failures. @@ -62,7 +63,7 @@ mixin DataErrorHandler { if (e is AppException) rethrow; // Debugging: Log unexpected errors - print('DataErrorHandler: Unhandled exception caught: $e'); + debugPrint('DataErrorHandler: Unhandled exception caught: $e'); throw UnknownException(technicalMessage: e.toString()); } diff --git a/apps/mobile/packages/data_connect/pubspec.yaml b/apps/mobile/packages/data_connect/pubspec.yaml index 9795fcb7..d2b83d6a 100644 --- a/apps/mobile/packages/data_connect/pubspec.yaml +++ b/apps/mobile/packages/data_connect/pubspec.yaml @@ -15,3 +15,4 @@ dependencies: path: ../domain flutter_modular: ^6.3.0 firebase_data_connect: ^0.2.2+2 + firebase_core: ^4.4.0 From c3abb819c915de40cbed35fa0aafce69d61e92c0 Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Mon, 16 Feb 2026 14:57:47 -0500 Subject: [PATCH 02/15] feat(data-connect): Implement DataConnectService for centralized data operations and refactor ShiftsRepositoryImpl to utilize the new service --- .../data_connect/lib/krow_data_connect.dart | 1 + .../src/services/data_connect_service.dart | 104 ++++++++++ .../mobile/packages/data_connect/pubspec.yaml | 2 + .../shifts_repository_impl.dart | 186 ++++++------------ 4 files changed, 164 insertions(+), 129 deletions(-) create mode 100644 apps/mobile/packages/data_connect/lib/src/services/data_connect_service.dart diff --git a/apps/mobile/packages/data_connect/lib/krow_data_connect.dart b/apps/mobile/packages/data_connect/lib/krow_data_connect.dart index 277ad737..d512a29c 100644 --- a/apps/mobile/packages/data_connect/lib/krow_data_connect.dart +++ b/apps/mobile/packages/data_connect/lib/krow_data_connect.dart @@ -13,6 +13,7 @@ export 'src/session/client_session_store.dart'; // Export the generated Data Connect SDK export 'src/dataconnect_generated/generated.dart'; +export 'src/services/data_connect_service.dart'; export 'src/session/staff_session_store.dart'; export 'src/mixins/data_error_handler.dart'; diff --git a/apps/mobile/packages/data_connect/lib/src/services/data_connect_service.dart b/apps/mobile/packages/data_connect/lib/src/services/data_connect_service.dart new file mode 100644 index 00000000..cdfe2813 --- /dev/null +++ b/apps/mobile/packages/data_connect/lib/src/services/data_connect_service.dart @@ -0,0 +1,104 @@ +import 'dart:async'; + +import 'package:firebase_auth/firebase_auth.dart' as firebase_auth; +import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc; +import 'package:krow_core/core.dart'; + +import '../../krow_data_connect.dart' as dc; +import '../mixins/data_error_handler.dart'; + +/// A centralized service for interacting with Firebase Data Connect. +/// +/// This service provides common utilities and context management for all repositories. +class DataConnectService with DataErrorHandler { + DataConnectService._(); + + /// The singleton instance of the [DataConnectService]. + static final DataConnectService instance = DataConnectService._(); + + /// The Data Connect connector used for data operations. + final dc.ExampleConnector connector = dc.ExampleConnector.instance; + + /// The Firebase Auth instance. + final firebase_auth.FirebaseAuth _auth = firebase_auth.FirebaseAuth.instance; + + /// Cache for the current staff ID to avoid redundant lookups. + String? _cachedStaffId; + + /// Gets the current staff ID from session store or persistent storage. + Future getStaffId() async { + // 1. Check Session Store + final dc.StaffSession? session = dc.StaffSessionStore.instance.session; + if (session?.staff?.id != null) { + return session!.staff!.id; + } + + // 2. Check Cache + if (_cachedStaffId != null) return _cachedStaffId!; + + // 3. Fetch from Data Connect using Firebase UID + final firebase_auth.User? user = _auth.currentUser; + if (user == null) { + throw Exception('User is not authenticated'); + } + + try { + final fdc.QueryResult< + dc.GetStaffByUserIdData, + dc.GetStaffByUserIdVariables + > + response = await executeProtected( + () => connector.getStaffByUserId(userId: user.uid).execute(), + ); + + if (response.data.staffs.isNotEmpty) { + _cachedStaffId = response.data.staffs.first.id; + return _cachedStaffId!; + } + } catch (e) { + throw Exception('Failed to fetch staff ID from Data Connect: $e'); + } + + // 4. Fallback (should ideally not happen if DB is seeded) + return user.uid; + } + + /// Converts a Data Connect timestamp/string/json to a [DateTime]. + DateTime? toDateTime(dynamic t) { + if (t == null) return null; + DateTime? dt; + if (t is fdc.Timestamp) { + dt = t.toDateTime(); + } else if (t is String) { + dt = DateTime.tryParse(t); + } else { + try { + dt = DateTime.tryParse(t.toJson() as String); + } catch (_) { + try { + dt = DateTime.tryParse(t.toString()); + } catch (e) { + dt = null; + } + } + } + + if (dt != null) { + return DateTimeUtils.toDeviceTime(dt); + } + return null; + } + + /// Converts a [DateTime] to a Firebase Data Connect [Timestamp]. + fdc.Timestamp toTimestamp(DateTime dateTime) { + final DateTime utc = dateTime.toUtc(); + final int seconds = utc.millisecondsSinceEpoch ~/ 1000; + final int nanoseconds = (utc.microsecondsSinceEpoch % 1000000) * 1000; + return fdc.Timestamp(nanoseconds, seconds); + } + + /// Clears the internal cache (e.g., on logout). + void clearCache() { + _cachedStaffId = null; + } +} diff --git a/apps/mobile/packages/data_connect/pubspec.yaml b/apps/mobile/packages/data_connect/pubspec.yaml index d2b83d6a..48d0039b 100644 --- a/apps/mobile/packages/data_connect/pubspec.yaml +++ b/apps/mobile/packages/data_connect/pubspec.yaml @@ -16,3 +16,5 @@ dependencies: flutter_modular: ^6.3.0 firebase_data_connect: ^0.2.2+2 firebase_core: ^4.4.0 + firebase_auth: ^6.1.4 + krow_core: ^0.0.1 diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/data/repositories_impl/shifts_repository_impl.dart b/apps/mobile/packages/features/staff/shifts/lib/src/data/repositories_impl/shifts_repository_impl.dart index d500819a..64a112ca 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/data/repositories_impl/shifts_repository_impl.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/data/repositories_impl/shifts_repository_impl.dart @@ -1,85 +1,20 @@ import 'package:krow_data_connect/krow_data_connect.dart' as dc; import 'package:krow_domain/krow_domain.dart'; import 'package:intl/intl.dart'; -import 'package:firebase_auth/firebase_auth.dart' as firebase_auth; import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc; -import 'package:krow_core/core.dart'; import '../../domain/repositories/shifts_repository_interface.dart'; class ShiftsRepositoryImpl - with dc.DataErrorHandler implements ShiftsRepositoryInterface { - final dc.ExampleConnector _dataConnect; - final firebase_auth.FirebaseAuth _auth = firebase_auth.FirebaseAuth.instance; + final dc.DataConnectService _service; - ShiftsRepositoryImpl() : _dataConnect = dc.ExampleConnector.instance; + ShiftsRepositoryImpl() : _service = dc.DataConnectService.instance; // Cache: ShiftID -> ApplicationID (For Accept/Decline) final Map _shiftToAppIdMap = {}; // Cache: ApplicationID -> RoleID (For Accept/Decline w/ Update mutation) final Map _appToRoleIdMap = {}; - String? _cachedStaffId; - - Future _getStaffId() async { - // 1. Check Session Store - final dc.StaffSession? session = dc.StaffSessionStore.instance.session; - if (session?.staff?.id != null) { - return session!.staff!.id; - } - - // 2. Check Cache - if (_cachedStaffId != null) return _cachedStaffId!; - - // 3. Fetch from Data Connect using Firebase UID - final firebase_auth.User? user = _auth.currentUser; - if (user == null) { - throw Exception('User is not authenticated'); - } - - try { - final fdc.QueryResult response = await executeProtected(() => _dataConnect - .getStaffByUserId(userId: user.uid) - .execute()); - if (response.data.staffs.isNotEmpty) { - _cachedStaffId = response.data.staffs.first.id; - return _cachedStaffId!; - } - } catch (e) { - // Log or handle error - } - - // 4. Fallback (should ideally not happen if DB is seeded) - return user.uid; - } - - DateTime? _toDateTime(dynamic t) { - if (t == null) return null; - DateTime? dt; - if (t is fdc.Timestamp) { - dt = t.toDateTime(); - } else if (t is String) { - dt = DateTime.tryParse(t); - } else { - try { - dt = DateTime.tryParse(t.toJson() as String); - } catch (_) { - try { - dt = DateTime.tryParse(t.toString()); - } catch (e) { - dt = null; - } - } - } - - if (dt != null) { - final local = DateTimeUtils.toDeviceTime(dt); - - return local; - } - return null; - } - @override Future> getMyShifts({ required DateTime start, @@ -100,8 +35,8 @@ class ShiftsRepositoryImpl @override Future> getHistoryShifts() async { - final staffId = await _getStaffId(); - final fdc.QueryResult response = await executeProtected(() => _dataConnect + final staffId = await _service.getStaffId(); + final fdc.QueryResult response = await _service.executeProtected(() => _service.connector .listCompletedApplicationsByStaffId(staffId: staffId) .execute()); final List shifts = []; @@ -116,10 +51,10 @@ class ShiftsRepositoryImpl ? app.shift.order.eventName! : app.shift.order.business.businessName; final String title = '$roleName - $orderName'; - final DateTime? shiftDate = _toDateTime(app.shift.date); - final DateTime? startDt = _toDateTime(app.shiftRole.startTime); - final DateTime? endDt = _toDateTime(app.shiftRole.endTime); - final DateTime? createdDt = _toDateTime(app.createdAt); + final DateTime? shiftDate = _service.toDateTime(app.shift.date); + final DateTime? startDt = _service.toDateTime(app.shiftRole.startTime); + final DateTime? endDt = _service.toDateTime(app.shiftRole.endTime); + final DateTime? createdDt = _service.toDateTime(app.createdAt); shifts.add( Shift( @@ -157,12 +92,12 @@ class ShiftsRepositoryImpl DateTime? start, DateTime? end, }) async { - final staffId = await _getStaffId(); - var query = _dataConnect.getApplicationsByStaffId(staffId: staffId); + final staffId = await _service.getStaffId(); + var query = _service.connector.getApplicationsByStaffId(staffId: staffId); if (start != null && end != null) { - query = query.dayStart(_toTimestamp(start)).dayEnd(_toTimestamp(end)); + query = query.dayStart(_service.toTimestamp(start)).dayEnd(_service.toTimestamp(end)); } - final fdc.QueryResult response = await executeProtected(() => query.execute()); + final fdc.QueryResult response = await _service.executeProtected(() => query.execute()); final apps = response.data.applications; final List shifts = []; @@ -177,10 +112,10 @@ class ShiftsRepositoryImpl ? app.shift.order.eventName! : app.shift.order.business.businessName; final String title = '$roleName - $orderName'; - final DateTime? shiftDate = _toDateTime(app.shift.date); - final DateTime? startDt = _toDateTime(app.shiftRole.startTime); - final DateTime? endDt = _toDateTime(app.shiftRole.endTime); - final DateTime? createdDt = _toDateTime(app.createdAt); + final DateTime? shiftDate = _service.toDateTime(app.shift.date); + final DateTime? startDt = _service.toDateTime(app.shiftRole.startTime); + final DateTime? endDt = _service.toDateTime(app.shiftRole.endTime); + final DateTime? createdDt = _service.toDateTime(app.createdAt); // Override status to reflect the application state (e.g., CHECKED_OUT, CONFIRMED) final bool hasCheckIn = app.checkInTime != null; @@ -226,13 +161,6 @@ class ShiftsRepositoryImpl return shifts; } - fdc.Timestamp _toTimestamp(DateTime dateTime) { - final DateTime utc = dateTime.toUtc(); - final int seconds = utc.millisecondsSinceEpoch ~/ 1000; - final int nanoseconds = (utc.microsecondsSinceEpoch % 1000000) * 1000; - return fdc.Timestamp(nanoseconds, seconds); - } - String _mapStatus(dc.ApplicationStatus status) { switch (status) { case dc.ApplicationStatus.CONFIRMED: @@ -255,7 +183,7 @@ class ShiftsRepositoryImpl return []; } - final fdc.QueryResult result = await executeProtected(() => _dataConnect + final fdc.QueryResult result = await _service.executeProtected(() => _service.connector .listShiftRolesByVendorId(vendorId: vendorId) .execute()); final allShiftRoles = result.data.shiftRoles; @@ -263,10 +191,10 @@ class ShiftsRepositoryImpl final List mappedShifts = []; for (final sr in allShiftRoles) { - final DateTime? shiftDate = _toDateTime(sr.shift.date); - final startDt = _toDateTime(sr.startTime); - final endDt = _toDateTime(sr.endTime); - final createdDt = _toDateTime(sr.createdAt); + final DateTime? shiftDate = _service.toDateTime(sr.shift.date); + final startDt = _service.toDateTime(sr.startTime); + final endDt = _service.toDateTime(sr.endTime); + final createdDt = _service.toDateTime(sr.createdAt); mappedShifts.add( Shift( @@ -319,21 +247,21 @@ class ShiftsRepositoryImpl Future _getShiftDetails(String shiftId, {String? roleId}) async { if (roleId != null && roleId.isNotEmpty) { - final roleResult = await executeProtected(() => _dataConnect + final roleResult = await _service.executeProtected(() => _service.connector .getShiftRoleById(shiftId: shiftId, roleId: roleId) .execute()); final sr = roleResult.data.shiftRole; if (sr == null) return null; - final DateTime? startDt = _toDateTime(sr.startTime); - final DateTime? endDt = _toDateTime(sr.endTime); - final DateTime? createdDt = _toDateTime(sr.createdAt); + final DateTime? startDt = _service.toDateTime(sr.startTime); + final DateTime? endDt = _service.toDateTime(sr.endTime); + final DateTime? createdDt = _service.toDateTime(sr.createdAt); - final String staffId = await _getStaffId(); + final String staffId = await _service.getStaffId(); bool hasApplied = false; String status = 'open'; - final apps = await executeProtected(() => - _dataConnect.getApplicationsByStaffId(staffId: staffId).execute()); + final apps = await _service.executeProtected(() => + _service.connector.getApplicationsByStaffId(staffId: staffId).execute()); final app = apps.data.applications .where( (a) => a.shiftId == shiftId && a.shiftRole.roleId == roleId, @@ -378,7 +306,7 @@ class ShiftsRepositoryImpl } final fdc.QueryResult result = - await executeProtected(() => _dataConnect.getShiftById(id: shiftId).execute()); + await _service.executeProtected(() => _service.connector.getShiftById(id: shiftId).execute()); final s = result.data.shift; if (s == null) return null; @@ -386,8 +314,8 @@ class ShiftsRepositoryImpl int? filled; Break? breakInfo; try { - final rolesRes = await executeProtected(() => - _dataConnect.listShiftRolesByShiftId(shiftId: shiftId).execute()); + final rolesRes = await _service.executeProtected(() => + _service.connector.listShiftRolesByShiftId(shiftId: shiftId).execute()); if (rolesRes.data.shiftRoles.isNotEmpty) { required = 0; filled = 0; @@ -404,9 +332,9 @@ class ShiftsRepositoryImpl } } catch (_) {} - final startDt = _toDateTime(s.startTime); - final endDt = _toDateTime(s.endTime); - final createdDt = _toDateTime(s.createdAt); + final startDt = _service.toDateTime(s.startTime); + final endDt = _service.toDateTime(s.endTime); + final createdDt = _service.toDateTime(s.createdAt); return Shift( id: s.id, @@ -437,14 +365,14 @@ class ShiftsRepositoryImpl bool isInstantBook = false, String? roleId, }) async { - final staffId = await _getStaffId(); + final staffId = await _service.getStaffId(); String targetRoleId = roleId ?? ''; if (targetRoleId.isEmpty) { throw Exception('Missing role id.'); } - final roleResult = await executeProtected(() => _dataConnect + final roleResult = await _service.executeProtected(() => _service.connector .getShiftRoleById(shiftId: shiftId, roleId: targetRoleId) .execute()); final role = roleResult.data.shiftRole; @@ -452,12 +380,12 @@ class ShiftsRepositoryImpl throw Exception('Shift role not found'); } final shiftResult = - await executeProtected(() => _dataConnect.getShiftById(id: shiftId).execute()); + await _service.executeProtected(() => _service.connector.getShiftById(id: shiftId).execute()); final shift = shiftResult.data.shift; if (shift == null) { throw Exception('Shift not found'); } - final DateTime? shiftDate = _toDateTime(shift.date); + final DateTime? shiftDate = _service.toDateTime(shift.date); if (shiftDate != null) { final DateTime dayStartUtc = DateTime.utc( shiftDate.year, @@ -475,16 +403,16 @@ class ShiftsRepositoryImpl 999, ); - final dayApplications = await executeProtected(() => _dataConnect + final dayApplications = await _service.executeProtected(() => _service.connector .vaidateDayStaffApplication(staffId: staffId) - .dayStart(_toTimestamp(dayStartUtc)) - .dayEnd(_toTimestamp(dayEndUtc)) + .dayStart(_service.toTimestamp(dayStartUtc)) + .dayEnd(_service.toTimestamp(dayEndUtc)) .execute()); if (dayApplications.data.applications.isNotEmpty) { throw Exception('The user already has a shift that day.'); } } - final existingApplicationResult = await executeProtected(() => _dataConnect + final existingApplicationResult = await _service.executeProtected(() => _service.connector .getApplicationByStaffShiftAndRole( staffId: staffId, shiftId: shiftId, @@ -505,7 +433,7 @@ class ShiftsRepositoryImpl bool updatedRole = false; bool updatedShift = false; try { - final appResult = await executeProtected(() => _dataConnect + final appResult = await _service.executeProtected(() => _service.connector .createApplication( shiftId: shiftId, staffId: staffId, @@ -517,24 +445,24 @@ class ShiftsRepositoryImpl .execute()); appId = appResult.data.application_insert.id; - await executeProtected(() => _dataConnect + await _service.executeProtected(() => _service.connector .updateShiftRole(shiftId: shiftId, roleId: targetRoleId) .assigned(assigned + 1) .execute()); updatedRole = true; - await executeProtected( - () => _dataConnect.updateShift(id: shiftId).filled(filled + 1).execute()); + await _service.executeProtected( + () => _service.connector.updateShift(id: shiftId).filled(filled + 1).execute()); updatedShift = true; } catch (e) { if (updatedShift) { try { - await _dataConnect.updateShift(id: shiftId).filled(filled).execute(); + await _service.connector.updateShift(id: shiftId).filled(filled).execute(); } catch (_) {} } if (updatedRole) { try { - await _dataConnect + await _service.connector .updateShiftRole(shiftId: shiftId, roleId: targetRoleId) .assigned(assigned) .execute(); @@ -542,7 +470,7 @@ class ShiftsRepositoryImpl } if (appId != null) { try { - await _dataConnect.deleteApplication(id: appId).execute(); + await _service.connector.deleteApplication(id: appId).execute(); } catch (_) {} } rethrow; @@ -576,9 +504,9 @@ class ShiftsRepositoryImpl roleId = _appToRoleIdMap[appId]; } else { // Fallback fetch - final staffId = await _getStaffId(); - final apps = await executeProtected(() => - _dataConnect.getApplicationsByStaffId(staffId: staffId).execute()); + final staffId = await _service.getStaffId(); + final apps = await _service.executeProtected(() => + _service.connector.getApplicationsByStaffId(staffId: staffId).execute()); final app = apps.data.applications .where((a) => a.shiftId == shiftId) .firstOrNull; @@ -591,12 +519,12 @@ class ShiftsRepositoryImpl if (appId == null || roleId == null) { // If we are rejecting and can't find an application, create one as rejected (declining an available shift) if (newStatus == dc.ApplicationStatus.REJECTED) { - final rolesResult = await executeProtected(() => - _dataConnect.listShiftRolesByShiftId(shiftId: shiftId).execute()); + final rolesResult = await _service.executeProtected(() => + _service.connector.listShiftRolesByShiftId(shiftId: shiftId).execute()); if (rolesResult.data.shiftRoles.isNotEmpty) { final role = rolesResult.data.shiftRoles.first; - final staffId = await _getStaffId(); - await executeProtected(() => _dataConnect + final staffId = await _service.getStaffId(); + await _service.executeProtected(() => _service.connector .createApplication( shiftId: shiftId, staffId: staffId, @@ -611,7 +539,7 @@ class ShiftsRepositoryImpl throw Exception("Application not found for shift $shiftId"); } - await executeProtected(() => _dataConnect + await _service.executeProtected(() => _service.connector .updateApplicationStatus(id: appId!) .status(newStatus) .execute()); From 3245c957f6cb465973696bbae4e66ebce5ea3d9d Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Mon, 16 Feb 2026 15:39:40 -0500 Subject: [PATCH 03/15] feat(data-connect): Add run method for centralized error handling and authentication checks --- .../lib/src/services/data_connect_service.dart | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/apps/mobile/packages/data_connect/lib/src/services/data_connect_service.dart b/apps/mobile/packages/data_connect/lib/src/services/data_connect_service.dart index cdfe2813..c91c34d1 100644 --- a/apps/mobile/packages/data_connect/lib/src/services/data_connect_service.dart +++ b/apps/mobile/packages/data_connect/lib/src/services/data_connect_service.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:firebase_auth/firebase_auth.dart' as firebase_auth; import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc; import 'package:krow_core/core.dart'; +import 'package:krow_domain/krow_domain.dart'; import '../../krow_data_connect.dart' as dc; import '../mixins/data_error_handler.dart'; @@ -20,6 +21,7 @@ class DataConnectService with DataErrorHandler { final dc.ExampleConnector connector = dc.ExampleConnector.instance; /// The Firebase Auth instance. + firebase_auth.FirebaseAuth get auth => _auth; final firebase_auth.FirebaseAuth _auth = firebase_auth.FirebaseAuth.instance; /// Cache for the current staff ID to avoid redundant lookups. @@ -97,6 +99,20 @@ class DataConnectService with DataErrorHandler { return fdc.Timestamp(nanoseconds, seconds); } + // --- 3. Unified Execution --- + // Repositories call this to benefit from centralized error handling/logging + Future run( + Future Function() action, { + bool requiresAuthentication = true, + }) { + if (requiresAuthentication && auth.currentUser == null) { + throw const NotAuthenticatedException( + technicalMessage: 'User must be authenticated to perform this action', + ); + } + return executeProtected(action); + } + /// Clears the internal cache (e.g., on logout). void clearCache() { _cachedStaffId = null; From d0585d12abfbecc87578f8d5f1a392697464e99c Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Mon, 16 Feb 2026 15:47:01 -0500 Subject: [PATCH 04/15] feat(auth): Refactor AuthRepositoryImpl and ProfileSetupRepositoryImpl to use DataConnectService for authentication and data operations --- .../auth_repository_impl.dart | 103 ++++++++++-------- .../profile_setup_repository_impl.dart | 32 +++--- .../lib/src/staff_authentication_module.dart | 15 +-- 3 files changed, 76 insertions(+), 74 deletions(-) diff --git a/apps/mobile/packages/features/staff/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart b/apps/mobile/packages/features/staff/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart index 0d4bca6b..b247880e 100644 --- a/apps/mobile/packages/features/staff/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart +++ b/apps/mobile/packages/features/staff/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart @@ -10,20 +10,14 @@ import '../../domain/ui_entities/auth_mode.dart'; import '../../domain/repositories/auth_repository_interface.dart'; /// Implementation of [AuthRepositoryInterface]. -class AuthRepositoryImpl - with DataErrorHandler - implements AuthRepositoryInterface { - AuthRepositoryImpl({ - required this.firebaseAuth, - required this.dataConnect, - }); +class AuthRepositoryImpl implements AuthRepositoryInterface { + AuthRepositoryImpl() : _service = DataConnectService.instance; - final FirebaseAuth firebaseAuth; - final ExampleConnector dataConnect; + final DataConnectService _service; Completer? _pendingVerification; @override - Stream get currentUser => firebaseAuth + Stream get currentUser => _service.auth .authStateChanges() .map((User? firebaseUser) { if (firebaseUser == null) { @@ -44,7 +38,7 @@ class AuthRepositoryImpl final Completer completer = Completer(); _pendingVerification = completer; - await firebaseAuth.verifyPhoneNumber( + await _service.auth.verifyPhoneNumber( phoneNumber: phoneNumber, verificationCompleted: (PhoneAuthCredential credential) { // Skip auto-verification for test numbers to allow manual code entry @@ -101,7 +95,8 @@ class AuthRepositoryImpl @override Future signOut() { StaffSessionStore.instance.clear(); - return firebaseAuth.signOut(); + _service.clearCache(); + return _service.auth.signOut(); } /// Verifies an OTP code and returns the authenticated user. @@ -115,10 +110,10 @@ class AuthRepositoryImpl verificationId: verificationId, smsCode: smsCode, ); - final UserCredential userCredential = await executeProtected( + final UserCredential userCredential = await _service.run( () async { try { - return await firebaseAuth.signInWithCredential(credential); + return await _service.auth.signInWithCredential(credential); } on FirebaseAuthException catch (e) { if (e.code == 'invalid-verification-code') { throw const domain.InvalidCredentialsException( @@ -128,45 +123,56 @@ class AuthRepositoryImpl rethrow; } }, + requiresAuthentication: false, ); final User? firebaseUser = userCredential.user; if (firebaseUser == null) { throw const domain.SignInFailedException( - technicalMessage: 'Phone verification failed, no Firebase user received.', + technicalMessage: + 'Phone verification failed, no Firebase user received.', ); } final QueryResult response = - await executeProtected(() => dataConnect - .getUserById( - id: firebaseUser.uid, - ) - .execute()); + await _service.run( + () => _service.connector + .getUserById( + id: firebaseUser.uid, + ) + .execute(), + requiresAuthentication: false, + ); final GetUserByIdUser? user = response.data.user; GetStaffByUserIdStaffs? staffRecord; if (mode == AuthMode.signup) { if (user == null) { - await executeProtected(() => dataConnect - .createUser( - id: firebaseUser.uid, - role: UserBaseRole.USER, - ) - .userRole('STAFF') - .execute()); + await _service.run( + () => _service.connector + .createUser( + id: firebaseUser.uid, + role: UserBaseRole.USER, + ) + .userRole('STAFF') + .execute(), + requiresAuthentication: false, + ); } else { // User exists in PostgreSQL. Check if they have a STAFF profile. final QueryResult - staffResponse = await executeProtected(() => dataConnect - .getStaffByUserId( - userId: firebaseUser.uid, - ) - .execute()); + staffResponse = await _service.run( + () => _service.connector + .getStaffByUserId( + userId: firebaseUser.uid, + ) + .execute(), + requiresAuthentication: false, + ); if (staffResponse.data.staffs.isNotEmpty) { // If profile exists, they should use Login mode. - await firebaseAuth.signOut(); + await _service.auth.signOut(); throw const domain.AccountExistsException( technicalMessage: 'This user already has a staff profile. Please log in.', @@ -177,35 +183,44 @@ class AuthRepositoryImpl // they are allowed to "Sign Up" for Staff. // We update their userRole to 'BOTH'. if (user.userRole == 'BUSINESS') { - await executeProtected(() => - dataConnect.updateUser(id: firebaseUser.uid).userRole('BOTH').execute()); + await _service.run( + () => _service.connector + .updateUser(id: firebaseUser.uid) + .userRole('BOTH') + .execute(), + requiresAuthentication: false, + ); } } } else { if (user == null) { - await firebaseAuth.signOut(); + await _service.auth.signOut(); throw const domain.UserNotFoundException( technicalMessage: 'Authenticated user profile not found in database.', ); } // Allow STAFF or BOTH roles to log in to the Staff App if (user.userRole != 'STAFF' && user.userRole != 'BOTH') { - await firebaseAuth.signOut(); + await _service.auth.signOut(); throw const domain.UnauthorizedAppException( technicalMessage: 'User is not authorized for this app.', ); } final QueryResult - staffResponse = await executeProtected(() => dataConnect - .getStaffByUserId( - userId: firebaseUser.uid, - ) - .execute()); + staffResponse = await _service.run( + () => _service.connector + .getStaffByUserId( + userId: firebaseUser.uid, + ) + .execute(), + requiresAuthentication: false, + ); if (staffResponse.data.staffs.isEmpty) { - await firebaseAuth.signOut(); + await _service.auth.signOut(); throw const domain.UserNotFoundException( - technicalMessage: 'Your account is not registered yet. Please register first.', + technicalMessage: + 'Your account is not registered yet. Please register first.', ); } staffRecord = staffResponse.data.staffs.first; diff --git a/apps/mobile/packages/features/staff/authentication/lib/src/data/repositories_impl/profile_setup_repository_impl.dart b/apps/mobile/packages/features/staff/authentication/lib/src/data/repositories_impl/profile_setup_repository_impl.dart index 1aeabdc2..fe25eea3 100644 --- a/apps/mobile/packages/features/staff/authentication/lib/src/data/repositories_impl/profile_setup_repository_impl.dart +++ b/apps/mobile/packages/features/staff/authentication/lib/src/data/repositories_impl/profile_setup_repository_impl.dart @@ -1,18 +1,13 @@ -import 'package:firebase_auth/firebase_auth.dart' as auth; import 'package:krow_data_connect/krow_data_connect.dart'; import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc; import 'package:krow_domain/krow_domain.dart'; +import 'package:firebase_auth/firebase_auth.dart' as auth; import '../../domain/repositories/profile_setup_repository.dart'; class ProfileSetupRepositoryImpl implements ProfileSetupRepository { - final auth.FirebaseAuth _firebaseAuth; - final ExampleConnector _dataConnect; + final DataConnectService _service; - ProfileSetupRepositoryImpl({ - required auth.FirebaseAuth firebaseAuth, - required ExampleConnector dataConnect, - }) : _firebaseAuth = firebaseAuth, - _dataConnect = dataConnect; + ProfileSetupRepositoryImpl() : _service = DataConnectService.instance; @override Future submitProfile({ @@ -23,17 +18,19 @@ class ProfileSetupRepositoryImpl implements ProfileSetupRepository { required List industries, required List skills, }) async { - final auth.User? firebaseUser = _firebaseAuth.currentUser; - if (firebaseUser == null) { - throw Exception('User not authenticated.'); - } + return _service.run(() async { + final auth.User? firebaseUser = _service.auth.currentUser; + if (firebaseUser == null) { + throw const NotAuthenticatedException( + technicalMessage: 'User not authenticated.'); + } - final StaffSession? session = StaffSessionStore.instance.session; - final String email = session?.user.email ?? ''; - final String? phone = firebaseUser.phoneNumber; + final StaffSession? session = StaffSessionStore.instance.session; + final String email = session?.user.email ?? ''; + final String? phone = firebaseUser.phoneNumber; - final fdc.OperationResult - result = await _dataConnect + final fdc.OperationResult result = + await _service.connector .createStaff( userId: firebaseUser.uid, fullName: fullName, @@ -63,5 +60,6 @@ class ProfileSetupRepositoryImpl implements ProfileSetupRepository { StaffSession(user: session.user, staff: staff, ownerId: session.ownerId), ); } + }); } } diff --git a/apps/mobile/packages/features/staff/authentication/lib/src/staff_authentication_module.dart b/apps/mobile/packages/features/staff/authentication/lib/src/staff_authentication_module.dart index ef1f34da..c5380d68 100644 --- a/apps/mobile/packages/features/staff/authentication/lib/src/staff_authentication_module.dart +++ b/apps/mobile/packages/features/staff/authentication/lib/src/staff_authentication_module.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_modular/flutter_modular.dart'; import 'package:krow_core/core.dart'; import 'package:krow_data_connect/krow_data_connect.dart'; -import 'package:firebase_auth/firebase_auth.dart' as firebase; import 'package:staff_authentication/src/data/repositories_impl/auth_repository_impl.dart'; import 'package:staff_authentication/src/domain/repositories/auth_repository_interface.dart'; import 'package:staff_authentication/src/domain/usecases/sign_in_with_phone_usecase.dart'; @@ -28,18 +27,8 @@ class StaffAuthenticationModule extends Module { @override void binds(Injector i) { // Repositories - i.addLazySingleton( - () => AuthRepositoryImpl( - firebaseAuth: firebase.FirebaseAuth.instance, - dataConnect: ExampleConnector.instance, - ), - ); - i.addLazySingleton( - () => ProfileSetupRepositoryImpl( - firebaseAuth: firebase.FirebaseAuth.instance, - dataConnect: ExampleConnector.instance, - ), - ); + i.addLazySingleton(AuthRepositoryImpl.new); + i.addLazySingleton(ProfileSetupRepositoryImpl.new); i.addLazySingleton(PlaceRepositoryImpl.new); // UseCases From 1f7134799b5396abb053f0c12d364e048f89f007 Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Mon, 16 Feb 2026 15:48:45 -0500 Subject: [PATCH 05/15] feat(availability): Refactor AvailabilityRepositoryImpl to use DataConnectService and simplify dependency injection --- .../availability_repository_impl.dart | 50 +++++-------------- .../lib/src/staff_availability_module.dart | 7 +-- 2 files changed, 13 insertions(+), 44 deletions(-) diff --git a/apps/mobile/packages/features/staff/availability/lib/src/data/repositories_impl/availability_repository_impl.dart b/apps/mobile/packages/features/staff/availability/lib/src/data/repositories_impl/availability_repository_impl.dart index c68ae129..4c7a1afe 100644 --- a/apps/mobile/packages/features/staff/availability/lib/src/data/repositories_impl/availability_repository_impl.dart +++ b/apps/mobile/packages/features/staff/availability/lib/src/data/repositories_impl/availability_repository_impl.dart @@ -1,4 +1,3 @@ -import 'package:firebase_auth/firebase_auth.dart' as firebase; import 'package:firebase_data_connect/firebase_data_connect.dart'; import 'package:krow_data_connect/krow_data_connect.dart' as dc; import 'package:krow_domain/krow_domain.dart'; @@ -10,44 +9,19 @@ import '../../domain/repositories/availability_repository.dart'; /// not specific date availability. Therefore, updating availability for a specific /// date will update the availability for that Day of Week globally (Recurring). class AvailabilityRepositoryImpl - with dc.DataErrorHandler implements AvailabilityRepository { - final dc.ExampleConnector _dataConnect; - final firebase.FirebaseAuth _firebaseAuth; - String? _cachedStaffId; + final dc.DataConnectService _service; - AvailabilityRepositoryImpl({ - required dc.ExampleConnector dataConnect, - required firebase.FirebaseAuth firebaseAuth, - }) : _dataConnect = dataConnect, - _firebaseAuth = firebaseAuth; - - Future _getStaffId() async { - if (_cachedStaffId != null) return _cachedStaffId!; - - final firebase.User? user = _firebaseAuth.currentUser; - if (user == null) { - throw NotAuthenticatedException( - technicalMessage: 'User not authenticated'); - } - - final QueryResult result = - await _dataConnect.getStaffByUserId(userId: user.uid).execute(); - if (result.data.staffs.isEmpty) { - throw const ServerException(technicalMessage: 'Staff profile not found'); - } - _cachedStaffId = result.data.staffs.first.id; - return _cachedStaffId!; - } + AvailabilityRepositoryImpl() : _service = dc.DataConnectService.instance; @override Future> getAvailability(DateTime start, DateTime end) async { - return executeProtected(() async { - final String staffId = await _getStaffId(); + return _service.run(() async { + final String staffId = await _service.getStaffId(); // 1. Fetch Weekly recurring availability final QueryResult result = - await _dataConnect.listStaffAvailabilitiesByStaffId(staffId: staffId).limit(100).execute(); + await _service.connector.listStaffAvailabilitiesByStaffId(staffId: staffId).limit(100).execute(); final List items = result.data.staffAvailabilities; @@ -124,8 +98,8 @@ class AvailabilityRepositoryImpl @override Future updateDayAvailability(DayAvailability availability) async { - return executeProtected(() async { - final String staffId = await _getStaffId(); + return _service.run(() async { + final String staffId = await _service.getStaffId(); final dc.DayOfWeek dow = _toBackendDay(availability.date.weekday); // Update each slot in the backend. @@ -143,8 +117,8 @@ class AvailabilityRepositoryImpl @override Future> applyQuickSet(DateTime start, DateTime end, String type) async { - return executeProtected(() async { - final String staffId = await _getStaffId(); + return _service.run(() async { + final String staffId = await _service.getStaffId(); // QuickSet updates the Recurring schedule for all days involved. // However, if the user selects a range that covers e.g. Mon-Fri, we update Mon-Fri. @@ -204,7 +178,7 @@ class AvailabilityRepositoryImpl Future _upsertSlot(String staffId, dc.DayOfWeek day, dc.AvailabilitySlot slot, dc.AvailabilityStatus status) async { // Check if exists - final result = await _dataConnect.getStaffAvailabilityByKey( + final result = await _service.connector.getStaffAvailabilityByKey( staffId: staffId, day: day, slot: slot, @@ -212,14 +186,14 @@ class AvailabilityRepositoryImpl if (result.data.staffAvailability != null) { // Update - await _dataConnect.updateStaffAvailability( + await _service.connector.updateStaffAvailability( staffId: staffId, day: day, slot: slot, ).status(status).execute(); } else { // Create - await _dataConnect.createStaffAvailability( + await _service.connector.createStaffAvailability( staffId: staffId, day: day, slot: slot, diff --git a/apps/mobile/packages/features/staff/availability/lib/src/staff_availability_module.dart b/apps/mobile/packages/features/staff/availability/lib/src/staff_availability_module.dart index 88458885..98937517 100644 --- a/apps/mobile/packages/features/staff/availability/lib/src/staff_availability_module.dart +++ b/apps/mobile/packages/features/staff/availability/lib/src/staff_availability_module.dart @@ -18,12 +18,7 @@ class StaffAvailabilityModule extends Module { @override void binds(Injector i) { // Repository - i.add( - () => AvailabilityRepositoryImpl( - dataConnect: ExampleConnector.instance, - firebaseAuth: FirebaseAuth.instance, - ), - ); + i.add(AvailabilityRepositoryImpl.new); // UseCases i.add(GetWeeklyAvailabilityUseCase.new); From 66859e4241ca804a5a2e766389c34d5baee94a7a Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Mon, 16 Feb 2026 16:00:27 -0500 Subject: [PATCH 06/15] feat(clock-in): Refactor ClockInRepositoryImpl to utilize DataConnectService and simplify dependency injection --- .../clock_in_repository_impl.dart | 308 ++++++++---------- .../lib/src/staff_clock_in_module.dart | 5 +- 2 files changed, 135 insertions(+), 178 deletions(-) diff --git a/apps/mobile/packages/features/staff/clock_in/lib/src/data/repositories_impl/clock_in_repository_impl.dart b/apps/mobile/packages/features/staff/clock_in/lib/src/data/repositories_impl/clock_in_repository_impl.dart index 6caf9a50..ea0e990f 100644 --- a/apps/mobile/packages/features/staff/clock_in/lib/src/data/repositories_impl/clock_in_repository_impl.dart +++ b/apps/mobile/packages/features/staff/clock_in/lib/src/data/repositories_impl/clock_in_repository_impl.dart @@ -1,69 +1,17 @@ import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc; import 'package:krow_data_connect/krow_data_connect.dart' as dc; import 'package:krow_domain/krow_domain.dart'; -import 'package:krow_core/core.dart'; import '../../domain/repositories/clock_in_repository_interface.dart'; /// Implementation of [ClockInRepositoryInterface] using Firebase Data Connect. -class ClockInRepositoryImpl - with dc.DataErrorHandler - implements ClockInRepositoryInterface { +class ClockInRepositoryImpl implements ClockInRepositoryInterface { + ClockInRepositoryImpl() : _service = dc.DataConnectService.instance; - ClockInRepositoryImpl({ - required dc.ExampleConnector dataConnect, - }) : _dataConnect = dataConnect; - final dc.ExampleConnector _dataConnect; + final dc.DataConnectService _service; final Map _shiftToApplicationId = {}; String? _activeApplicationId; - Future _getStaffId() async { - final dc.StaffSession? session = dc.StaffSessionStore.instance.session; - final String? staffId = session?.staff?.id; - if (staffId != null && staffId.isNotEmpty) { - return staffId; - } - throw Exception('Staff session not found'); - } - - /// Helper to convert Data Connect fdc.Timestamp to DateTime - DateTime? _toDateTime(dynamic t) { - if (t == null) return null; - DateTime? dt; - if (t is DateTime) { - dt = t; - } else if (t is String) { - dt = DateTime.tryParse(t); - } else { - try { - if (t is fdc.Timestamp) { - dt = t.toDateTime(); - } - } catch (_) {} - - try { - if (dt == null && t.runtimeType.toString().contains('Timestamp')) { - dt = (t as dynamic).toDate(); - } - } catch (_) {} - - try { - dt ??= DateTime.tryParse(t.toString()); - } catch (_) {} - } - - if (dt != null) { - return DateTimeUtils.toDeviceTime(dt); - } - return null; - } - - /// Helper to create fdc.Timestamp from DateTime - fdc.Timestamp _fromDateTime(DateTime d) { - // Assuming fdc.Timestamp.fromJson takes an ISO string - return fdc.Timestamp.fromJson(d.toUtc().toIso8601String()); - } - ({fdc.Timestamp start, fdc.Timestamp end}) _utcDayRange(DateTime localDay) { final DateTime dayStartUtc = DateTime.utc( localDay.year, @@ -81,8 +29,8 @@ class ClockInRepositoryImpl 999, ); return ( - start: _fromDateTime(dayStartUtc), - end: _fromDateTime(dayEndUtc), + start: _service.toTimestamp(dayStartUtc), + end: _service.toTimestamp(dayEndUtc), ); } @@ -93,26 +41,29 @@ class ClockInRepositoryImpl final DateTime now = DateTime.now(); final ({fdc.Timestamp start, fdc.Timestamp end}) range = _utcDayRange(now); final fdc.QueryResult result = await executeProtected( - () => _dataConnect + dc.GetApplicationsByStaffIdVariables> result = await _service.run( + () => _service.connector .getApplicationsByStaffId(staffId: staffId) .dayStart(range.start) .dayEnd(range.end) .execute(), ); - final List apps = result.data.applications; + final List apps = + result.data.applications; if (apps.isEmpty) return const []; _shiftToApplicationId ..clear() - ..addEntries(apps.map((dc.GetApplicationsByStaffIdApplications app) => MapEntry(app.shiftId, app.id))); + ..addEntries(apps.map((dc.GetApplicationsByStaffIdApplications app) => + MapEntry(app.shiftId, app.id))); - apps.sort((dc.GetApplicationsByStaffIdApplications a, dc.GetApplicationsByStaffIdApplications b) { + apps.sort((dc.GetApplicationsByStaffIdApplications a, + dc.GetApplicationsByStaffIdApplications b) { final DateTime? aTime = - _toDateTime(a.shift.startTime) ?? _toDateTime(a.shift.date); + _service.toDateTime(a.shift.startTime) ?? _service.toDateTime(a.shift.date); final DateTime? bTime = - _toDateTime(b.shift.startTime) ?? _toDateTime(b.shift.date); + _service.toDateTime(b.shift.startTime) ?? _service.toDateTime(b.shift.date); if (aTime == null && bTime == null) return 0; if (aTime == null) return -1; if (bTime == null) return 1; @@ -124,118 +75,124 @@ class ClockInRepositoryImpl return apps; } - - @override Future> getTodaysShifts() async { - final String staffId = await _getStaffId(); - final List apps = - await _getTodaysApplications(staffId); - if (apps.isEmpty) return const []; + return _service.run(() async { + final String staffId = await _service.getStaffId(); + final List apps = + await _getTodaysApplications(staffId); + if (apps.isEmpty) return const []; - final List shifts = []; - for (final dc.GetApplicationsByStaffIdApplications app in apps) { - final dc.GetApplicationsByStaffIdApplicationsShift shift = app.shift; - final DateTime? startDt = _toDateTime(app.shiftRole.startTime); - final DateTime? endDt = _toDateTime(app.shiftRole.endTime); - final DateTime? createdDt = _toDateTime(app.createdAt); + final List shifts = []; + for (final dc.GetApplicationsByStaffIdApplications app in apps) { + final dc.GetApplicationsByStaffIdApplicationsShift shift = app.shift; + final DateTime? startDt = _service.toDateTime(app.shiftRole.startTime); + final DateTime? endDt = _service.toDateTime(app.shiftRole.endTime); + final DateTime? createdDt = _service.toDateTime(app.createdAt); - final String roleName = app.shiftRole.role.name; - final String orderName = - (shift.order.eventName ?? '').trim().isNotEmpty - ? shift.order.eventName! - : shift.order.business.businessName; - final String title = '$roleName - $orderName'; - shifts.add( - Shift( - id: shift.id, - title: title, - clientName: shift.order.business.businessName, - logoUrl: shift.order.business.companyLogoUrl ?? '', - hourlyRate: app.shiftRole.role.costPerHour, - location: shift.location ?? '', - locationAddress: shift.order.teamHub.hubName, - date: startDt?.toIso8601String() ?? '', - startTime: startDt?.toIso8601String() ?? '', - endTime: endDt?.toIso8601String() ?? '', - createdDate: createdDt?.toIso8601String() ?? '', - status: shift.status?.stringValue, - description: shift.description, - latitude: shift.latitude, - longitude: shift.longitude, - ), - ); - } + final String roleName = app.shiftRole.role.name; + final String orderName = + (shift.order.eventName ?? '').trim().isNotEmpty + ? shift.order.eventName! + : shift.order.business.businessName; + final String title = '$roleName - $orderName'; + shifts.add( + Shift( + id: shift.id, + title: title, + clientName: shift.order.business.businessName, + logoUrl: shift.order.business.companyLogoUrl ?? '', + hourlyRate: app.shiftRole.role.costPerHour, + location: shift.location ?? '', + locationAddress: shift.order.teamHub.hubName, + date: startDt?.toIso8601String() ?? '', + startTime: startDt?.toIso8601String() ?? '', + endTime: endDt?.toIso8601String() ?? '', + createdDate: createdDt?.toIso8601String() ?? '', + status: shift.status?.stringValue, + description: shift.description, + latitude: shift.latitude, + longitude: shift.longitude, + ), + ); + } - return shifts; + return shifts; + }); } @override Future getAttendanceStatus() async { - final String staffId = await _getStaffId(); - final List apps = - await _getTodaysApplications(staffId); - if (apps.isEmpty) { - return const AttendanceStatus(isCheckedIn: false); - } + return _service.run(() async { + final String staffId = await _service.getStaffId(); + final List apps = + await _getTodaysApplications(staffId); + if (apps.isEmpty) { + return const AttendanceStatus(isCheckedIn: false); + } - dc.GetApplicationsByStaffIdApplications? activeApp; - for (final dc.GetApplicationsByStaffIdApplications app in apps) { - if (app.checkInTime != null && app.checkOutTime == null) { - if (activeApp == null) { - activeApp = app; - } else { - final DateTime? current = _toDateTime(activeApp.checkInTime); - final DateTime? next = _toDateTime(app.checkInTime); - if (current == null || (next != null && next.isAfter(current))) { + dc.GetApplicationsByStaffIdApplications? activeApp; + for (final dc.GetApplicationsByStaffIdApplications app in apps) { + if (app.checkInTime != null && app.checkOutTime == null) { + if (activeApp == null) { activeApp = app; + } else { + final DateTime? current = _service.toDateTime(activeApp.checkInTime); + final DateTime? next = _service.toDateTime(app.checkInTime); + if (current == null || (next != null && next.isAfter(current))) { + activeApp = app; + } } } } - } - if (activeApp == null) { - _activeApplicationId = null; - return const AttendanceStatus(isCheckedIn: false); - } + if (activeApp == null) { + _activeApplicationId = null; + return const AttendanceStatus(isCheckedIn: false); + } - _activeApplicationId = activeApp.id; + _activeApplicationId = activeApp.id; - return AttendanceStatus( - isCheckedIn: true, - checkInTime: _toDateTime(activeApp.checkInTime), - checkOutTime: _toDateTime(activeApp.checkOutTime), - activeShiftId: activeApp.shiftId, - activeApplicationId: activeApp.id, - ); + return AttendanceStatus( + isCheckedIn: true, + checkInTime: _service.toDateTime(activeApp.checkInTime), + checkOutTime: _service.toDateTime(activeApp.checkOutTime), + activeShiftId: activeApp.shiftId, + activeApplicationId: activeApp.id, + ); + }); } @override Future clockIn({required String shiftId, String? notes}) async { - final String staffId = await _getStaffId(); + return _service.run(() async { + final String staffId = await _service.getStaffId(); - final String? cachedAppId = _shiftToApplicationId[shiftId]; - dc.GetApplicationsByStaffIdApplications? app; - if (cachedAppId != null) { - try { - final List apps = await _getTodaysApplications(staffId); - app = apps.firstWhere((dc.GetApplicationsByStaffIdApplications a) => a.id == cachedAppId); - } catch (_) {} - } - app ??= (await _getTodaysApplications(staffId)) - .firstWhere((dc.GetApplicationsByStaffIdApplications a) => a.shiftId == shiftId); + final String? cachedAppId = _shiftToApplicationId[shiftId]; + dc.GetApplicationsByStaffIdApplications? app; + if (cachedAppId != null) { + try { + final List apps = + await _getTodaysApplications(staffId); + app = apps.firstWhere( + (dc.GetApplicationsByStaffIdApplications a) => a.id == cachedAppId); + } catch (_) {} + } + app ??= (await _getTodaysApplications(staffId)).firstWhere( + (dc.GetApplicationsByStaffIdApplications a) => a.shiftId == shiftId); - final fdc.Timestamp checkInTs = _fromDateTime(DateTime.now()); + final fdc.Timestamp checkInTs = _service.toTimestamp(DateTime.now()); - await executeProtected(() => _dataConnect - .updateApplicationStatus( - id: app!.id, - ) - .checkInTime(checkInTs) - .execute()); - _activeApplicationId = app.id; + await _service.run(() => _service.connector + .updateApplicationStatus( + id: app!.id, + ) + .checkInTime(checkInTs) + .execute()); + _activeApplicationId = app.id; - return getAttendanceStatus(); + return getAttendanceStatus(); + }); } @override @@ -244,32 +201,35 @@ class ClockInRepositoryImpl int? breakTimeMinutes, String? applicationId, }) async { - await _getStaffId(); // Validate session - + return _service.run(() async { + await _service.getStaffId(); // Validate session - final String? targetAppId = applicationId ?? _activeApplicationId; - if (targetAppId == null || targetAppId.isEmpty) { - throw Exception('No active application id for checkout'); - } - final fdc.QueryResult appResult = await executeProtected(() => _dataConnect - .getApplicationById(id: targetAppId) - .execute()); - final dc.GetApplicationByIdApplication? app = appResult.data.application; + final String? targetAppId = applicationId ?? _activeApplicationId; + if (targetAppId == null || targetAppId.isEmpty) { + throw Exception('No active application id for checkout'); + } + final fdc.QueryResult appResult = + await _service.run(() => _service.connector + .getApplicationById(id: targetAppId) + .execute()); + final dc.GetApplicationByIdApplication? app = appResult.data.application; - if (app == null) { - throw Exception('Application not found for checkout'); - } - if (app.checkInTime == null || app.checkOutTime != null) { - throw Exception('No active shift found to clock out'); - } + if (app == null) { + throw Exception('Application not found for checkout'); + } + if (app.checkInTime == null || app.checkOutTime != null) { + throw Exception('No active shift found to clock out'); + } - await executeProtected(() => _dataConnect - .updateApplicationStatus( - id: targetAppId, - ) - .checkOutTime(_fromDateTime(DateTime.now())) - .execute()); + await _service.run(() => _service.connector + .updateApplicationStatus( + id: targetAppId, + ) + .checkOutTime(_service.toTimestamp(DateTime.now())) + .execute()); - return getAttendanceStatus(); + return getAttendanceStatus(); + }); } } diff --git a/apps/mobile/packages/features/staff/clock_in/lib/src/staff_clock_in_module.dart b/apps/mobile/packages/features/staff/clock_in/lib/src/staff_clock_in_module.dart index 37164a81..ffd19c01 100644 --- a/apps/mobile/packages/features/staff/clock_in/lib/src/staff_clock_in_module.dart +++ b/apps/mobile/packages/features/staff/clock_in/lib/src/staff_clock_in_module.dart @@ -1,7 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter_modular/flutter_modular.dart'; import 'package:krow_core/core.dart'; -import 'package:krow_data_connect/krow_data_connect.dart'; import 'data/repositories_impl/clock_in_repository_impl.dart'; import 'domain/repositories/clock_in_repository_interface.dart'; @@ -16,9 +15,7 @@ class StaffClockInModule extends Module { @override void binds(Injector i) { // Repositories - i.add( - () => ClockInRepositoryImpl(dataConnect: ExampleConnector.instance), - ); + i.add(ClockInRepositoryImpl.new); // Use Cases i.add(GetTodaysShiftUseCase.new); From dcb76db1f8e02cecc245ac87aac8778104bc1714 Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Mon, 16 Feb 2026 16:05:38 -0500 Subject: [PATCH 07/15] feat(home-repository): Refactor HomeRepositoryImpl to utilize DataConnectService for data operations and simplify shift retrieval logic --- .../repositories/home_repository_impl.dart | 113 ++++++++---------- 1 file changed, 50 insertions(+), 63 deletions(-) diff --git a/apps/mobile/packages/features/staff/home/lib/src/data/repositories/home_repository_impl.dart b/apps/mobile/packages/features/staff/home/lib/src/data/repositories/home_repository_impl.dart index 8783e938..8f8bf8a8 100644 --- a/apps/mobile/packages/features/staff/home/lib/src/data/repositories/home_repository_impl.dart +++ b/apps/mobile/packages/features/staff/home/lib/src/data/repositories/home_repository_impl.dart @@ -1,26 +1,13 @@ -import 'package:firebase_data_connect/firebase_data_connect.dart'; import 'package:intl/intl.dart'; import 'package:krow_data_connect/krow_data_connect.dart'; import 'package:krow_domain/krow_domain.dart'; -import 'package:krow_core/core.dart'; import 'package:staff_home/src/domain/repositories/home_repository.dart'; -extension TimestampExt on Timestamp { - DateTime toDate() { - return DateTimeUtils.toDeviceTime(toDateTime()); - } -} - class HomeRepositoryImpl - with DataErrorHandler implements HomeRepository { - HomeRepositoryImpl(); + HomeRepositoryImpl() : _service = DataConnectService.instance; - String get _currentStaffId { - final session = StaffSessionStore.instance.session; - if (session?.staff?.id == null) throw Exception('User not logged in'); - return session!.staff!.id; - } + final DataConnectService _service; @override Future> getTodayShifts() async { @@ -33,59 +20,57 @@ class HomeRepositoryImpl } Future> _getShiftsForDate(DateTime date) async { - final staffId = _currentStaffId; + return _service.run(() async { + final staffId = await _service.getStaffId(); - // Create start and end timestamps for the target date - final DateTime start = DateTime(date.year, date.month, date.day); - final DateTime end = - DateTime(date.year, date.month, date.day, 23, 59, 59, 999); + // Create start and end timestamps for the target date + final DateTime start = DateTime(date.year, date.month, date.day); + final DateTime end = + DateTime(date.year, date.month, date.day, 23, 59, 59, 999); - final response = await executeProtected(() => ExampleConnector.instance - .getApplicationsByStaffId(staffId: staffId) - .dayStart(_toTimestamp(start)) - .dayEnd(_toTimestamp(end)) - .execute()); + final response = await _service.run(() => _service.connector + .getApplicationsByStaffId(staffId: staffId) + .dayStart(_service.toTimestamp(start)) + .dayEnd(_service.toTimestamp(end)) + .execute()); - // Filter for CONFIRMED applications (same logic as shifts_repository_impl) - final apps = response.data.applications.where((app) => - (app.status is Known && - (app.status as Known).value == ApplicationStatus.CONFIRMED)); + // Filter for CONFIRMED applications (same logic as shifts_repository_impl) + final apps = response.data.applications.where((app) => + (app.status is Known && + (app.status as Known).value == ApplicationStatus.CONFIRMED)); - final List shifts = []; - for (final app in apps) { - shifts.add(_mapApplicationToShift(app)); - } + final List shifts = []; + for (final app in apps) { + shifts.add(_mapApplicationToShift(app)); + } - return shifts; - } - - Timestamp _toTimestamp(DateTime dateTime) { - final DateTime utc = dateTime.toUtc(); - final int seconds = utc.millisecondsSinceEpoch ~/ 1000; - final int nanoseconds = (utc.microsecondsSinceEpoch % 1000000) * 1000; - return Timestamp(nanoseconds, seconds); + return shifts; + }); } @override Future> getRecommendedShifts() async { // Logic: List ALL open shifts (simple recommendation engine) // Limitation: listShifts might return ALL shifts. We should ideally filter by status=PUBLISHED. - final response = await executeProtected(() => ExampleConnector.instance.listShifts().execute()); + return _service.run(() async { + final response = + await _service.run(() => _service.connector.listShifts().execute()); - return response.data.shifts - .where((s) { - final isOpen = - s.status is Known && (s.status as Known).value == ShiftStatus.OPEN; - if (!isOpen) return false; + return response.data.shifts + .where((s) { + final isOpen = s.status is Known && + (s.status as Known).value == ShiftStatus.OPEN; + if (!isOpen) return false; - final start = s.startTime?.toDate(); - if (start == null) return false; + final start = _service.toDateTime(s.startTime); + if (start == null) return false; - return start.isAfter(DateTime.now()); - }) - .take(10) - .map((s) => _mapConnectorShiftToDomain(s)) - .toList(); + return start.isAfter(DateTime.now()); + }) + .take(10) + .map((s) => _mapConnectorShiftToDomain(s)) + .toList(); + }); } @override @@ -100,7 +85,7 @@ class HomeRepositoryImpl Shift _mapApplicationToShift(GetApplicationsByStaffIdApplications app) { final s = app.shift; final r = app.shiftRole; - + return ShiftAdapter.fromApplicationData( shiftId: s.id, roleId: r.roleId, @@ -110,10 +95,10 @@ class HomeRepositoryImpl costPerHour: r.role.costPerHour, shiftLocation: s.location, teamHubName: s.order.teamHub.hubName, - shiftDate: s.date?.toDate(), - startTime: r.startTime?.toDate(), - endTime: r.endTime?.toDate(), - createdAt: app.createdAt?.toDate(), + shiftDate: _service.toDateTime(s.date), + startTime: _service.toDateTime(r.startTime), + endTime: _service.toDateTime(r.endTime), + createdAt: _service.toDateTime(app.createdAt), status: 'confirmed', description: s.description, durationDays: s.durationDays, @@ -132,10 +117,12 @@ class HomeRepositoryImpl hourlyRate: s.cost ?? 0.0, location: s.location ?? 'Unknown', locationAddress: s.locationAddress ?? '', - date: s.date?.toDate().toIso8601String() ?? '', - startTime: DateFormat('HH:mm').format(s.startTime?.toDate() ?? DateTime.now()), - endTime: DateFormat('HH:mm').format(s.endTime?.toDate() ?? DateTime.now()), - createdDate: s.createdAt?.toDate().toIso8601String() ?? '', + date: _service.toDateTime(s.date)?.toIso8601String() ?? '', + startTime: DateFormat('HH:mm') + .format(_service.toDateTime(s.startTime) ?? DateTime.now()), + endTime: DateFormat('HH:mm') + .format(_service.toDateTime(s.endTime) ?? DateTime.now()), + createdDate: _service.toDateTime(s.createdAt)?.toIso8601String() ?? '', tipsAvailable: false, mealProvided: false, managers: [], From a10617f17d196663011736babc190eb71adb5090 Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Mon, 16 Feb 2026 16:10:47 -0500 Subject: [PATCH 08/15] feat(payments-repository): Refactor PaymentsRepositoryImpl to utilize DataConnectService for payment operations and simplify staff ID retrieval --- .../payments_repository_impl.dart | 98 +++---------------- 1 file changed, 13 insertions(+), 85 deletions(-) diff --git a/apps/mobile/packages/features/staff/payments/lib/src/data/repositories/payments_repository_impl.dart b/apps/mobile/packages/features/staff/payments/lib/src/data/repositories/payments_repository_impl.dart index a0791ab5..42cdb1af 100644 --- a/apps/mobile/packages/features/staff/payments/lib/src/data/repositories/payments_repository_impl.dart +++ b/apps/mobile/packages/features/staff/payments/lib/src/data/repositories/payments_repository_impl.dart @@ -1,97 +1,26 @@ -import 'package:firebase_data_connect/firebase_data_connect.dart'; import 'package:krow_data_connect/krow_data_connect.dart' as dc; - +import 'package:krow_data_connect/krow_data_connect.dart'; import 'package:krow_domain/krow_domain.dart'; -import 'package:firebase_auth/firebase_auth.dart' as firebase_auth; -import 'package:krow_core/core.dart'; + import '../../domain/repositories/payments_repository.dart'; class PaymentsRepositoryImpl - with dc.DataErrorHandler implements PaymentsRepository { - PaymentsRepositoryImpl() : _dataConnect = dc.ExampleConnector.instance; - final dc.ExampleConnector _dataConnect; - final firebase_auth.FirebaseAuth _auth = firebase_auth.FirebaseAuth.instance; - - String? _cachedStaffId; - - Future _getStaffId() async { - // 1. Check Session Store - final dc.StaffSession? session = dc.StaffSessionStore.instance.session; - if (session?.staff?.id != null) { - return session!.staff!.id; - } - - // 2. Check Cache - if (_cachedStaffId != null) return _cachedStaffId!; - - // 3. Fetch from Data Connect using Firebase UID - final firebase_auth.User? user = _auth.currentUser; - if (user == null) { - throw const NotAuthenticatedException( - technicalMessage: 'User is not authenticated', - ); - } - - // This call is protected by parent execution context if called within executeProtected, - // otherwise we might need to wrap it if called standalone. - // For now we assume it's called from public methods which are protected. - final QueryResult response = await _dataConnect.getStaffByUserId(userId: user.uid).execute(); - if (response.data.staffs.isNotEmpty) { - _cachedStaffId = response.data.staffs.first.id; - return _cachedStaffId!; - } - - // 4. Fallback - return user.uid; - } - - /// Helper to convert Data Connect Timestamp to DateTime - DateTime? _toDateTime(dynamic t) { - if (t == null) return null; - DateTime? dt; - if (t is DateTime) { - dt = t; - } else if (t is String) { - dt = DateTime.tryParse(t); - } else { - try { - if (t is Timestamp) { - dt = t.toDateTime(); - } - } catch (_) {} - - try { - if (dt == null && t.runtimeType.toString().contains('Timestamp')) { - dt = (t as dynamic).toDate(); - } - } catch (_) {} - - try { - dt ??= DateTime.tryParse(t.toString()); - } catch (_) {} - } - - if (dt != null) { - return DateTimeUtils.toDeviceTime(dt); - } - return null; - } + PaymentsRepositoryImpl() : _service = DataConnectService.instance; + final DataConnectService _service; @override Future getPaymentSummary() async { - return executeProtected(() async { - final String currentStaffId = await _getStaffId(); + return _service.run(() async { + final String currentStaffId = await _service.getStaffId(); // Fetch recent payments with a limit - // Note: limit is chained on the query builder - final QueryResult result = - await _dataConnect.listRecentPaymentsByStaffId( + final response = await _service.connector.listRecentPaymentsByStaffId( staffId: currentStaffId, ).limit(100).execute(); - final List payments = result.data.recentPayments; + final List payments = response.data.recentPayments; double weekly = 0; double monthly = 0; @@ -103,7 +32,7 @@ class PaymentsRepositoryImpl final DateTime startOfMonth = DateTime(now.year, now.month, 1); for (final dc.ListRecentPaymentsByStaffIdRecentPayments p in payments) { - final DateTime? date = _toDateTime(p.invoice.issueDate) ?? _toDateTime(p.createdAt); + final DateTime? date = _service.toDateTime(p.invoice.issueDate) ?? _service.toDateTime(p.createdAt); final double amount = p.invoice.amount; final String? status = p.status?.stringValue; @@ -129,11 +58,10 @@ class PaymentsRepositoryImpl @override Future> getPaymentHistory(String period) async { - return executeProtected(() async { - final String currentStaffId = await _getStaffId(); + return _service.run(() async { + final String currentStaffId = await _service.getStaffId(); - final QueryResult response = - await _dataConnect + final response = await _service.connector .listRecentPaymentsByStaffId(staffId: currentStaffId) .execute(); @@ -144,7 +72,7 @@ class PaymentsRepositoryImpl assignmentId: payment.applicationId, amount: payment.invoice.amount, status: PaymentAdapter.toPaymentStatus(payment.status?.stringValue ?? 'UNKNOWN'), - paidAt: _toDateTime(payment.invoice.issueDate), + paidAt: _service.toDateTime(payment.invoice.issueDate), ); }).toList(); }); From 0fc317e1da0e66ee2e09d76ef85eddf36b73b499 Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Mon, 16 Feb 2026 16:19:27 -0500 Subject: [PATCH 09/15] feat(profile-repository): Refactor ProfileRepositoryImpl to utilize DataConnectService and simplify authentication handling --- .../repositories/profile_repository_impl.dart | 33 +++++-------------- .../profile/lib/src/staff_profile_module.dart | 7 +--- 2 files changed, 10 insertions(+), 30 deletions(-) diff --git a/apps/mobile/packages/features/staff/profile/lib/src/data/repositories/profile_repository_impl.dart b/apps/mobile/packages/features/staff/profile/lib/src/data/repositories/profile_repository_impl.dart index 32bbcec5..42aa3a17 100644 --- a/apps/mobile/packages/features/staff/profile/lib/src/data/repositories/profile_repository_impl.dart +++ b/apps/mobile/packages/features/staff/profile/lib/src/data/repositories/profile_repository_impl.dart @@ -1,4 +1,3 @@ -import 'package:firebase_auth/firebase_auth.dart'; import 'package:krow_data_connect/krow_data_connect.dart'; import 'package:krow_domain/krow_domain.dart'; @@ -15,38 +14,23 @@ import '../../domain/repositories/profile_repository.dart'; /// Currently uses [ProfileRepositoryMock] from data_connect. /// When Firebase Data Connect is ready, this will be swapped with a real implementation. class ProfileRepositoryImpl - with DataErrorHandler implements ProfileRepositoryInterface { /// Creates a [ProfileRepositoryImpl]. - /// - /// Requires a [ExampleConnector] from the data_connect package and [FirebaseAuth]. - const ProfileRepositoryImpl({ - required this.connector, - required this.firebaseAuth, - }); + ProfileRepositoryImpl() : _service = DataConnectService.instance; - /// The Data Connect connector used for data operations. - final ExampleConnector connector; - - /// The Firebase Auth instance. - final FirebaseAuth firebaseAuth; + final DataConnectService _service; @override Future getStaffProfile() async { - return executeProtected(() async { - final user = firebaseAuth.currentUser; - if (user == null) { - throw NotAuthenticatedException( - technicalMessage: 'User not authenticated'); - } - - final response = await connector.getStaffByUserId(userId: user.uid).execute(); + return _service.run(() async { + final staffId = await _service.getStaffId(); + final response = await _service.connector.getStaffById(id: staffId).execute(); - if (response.data.staffs.isEmpty) { + if (response.data.staff == null) { throw const ServerException(technicalMessage: 'Staff not found'); } - final GetStaffByUserIdStaffs rawStaff = response.data.staffs.first; + final GetStaffByIdStaff rawStaff = response.data.staff!; // Map the raw data connect object to the Domain Entity return Staff( @@ -71,7 +55,8 @@ class ProfileRepositoryImpl @override Future signOut() async { try { - await firebaseAuth.signOut(); + await _service.auth.signOut(); + _service.clearCache(); } catch (e) { throw Exception('Error signing out: ${e.toString()}'); } diff --git a/apps/mobile/packages/features/staff/profile/lib/src/staff_profile_module.dart b/apps/mobile/packages/features/staff/profile/lib/src/staff_profile_module.dart index 992c80f1..88f56cc5 100644 --- a/apps/mobile/packages/features/staff/profile/lib/src/staff_profile_module.dart +++ b/apps/mobile/packages/features/staff/profile/lib/src/staff_profile_module.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_modular/flutter_modular.dart'; import 'package:krow_core/core.dart'; -import 'package:krow_data_connect/krow_data_connect.dart'; -import 'package:firebase_auth/firebase_auth.dart'; import 'data/repositories/profile_repository_impl.dart'; import 'domain/repositories/profile_repository.dart'; @@ -25,10 +23,7 @@ class StaffProfileModule extends Module { void binds(Injector i) { // Repository implementation - delegates to data_connect i.addLazySingleton( - () => ProfileRepositoryImpl( - connector: ExampleConnector.instance, - firebaseAuth: FirebaseAuth.instance, - ), + ProfileRepositoryImpl.new, ); // Use cases - depend on repository interface From 572ade95b9b94cbe50bc6a70fce2f3bd9570cc0e Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Mon, 16 Feb 2026 16:22:14 -0500 Subject: [PATCH 10/15] feat(tax-forms-repository): Refactor TaxFormsRepositoryImpl to simplify initialization and utilize DataConnectService for data operations --- .../tax_forms_repository_impl.dart | 64 ++++++------------- .../lib/src/staff_tax_forms_module.dart | 9 +-- 2 files changed, 19 insertions(+), 54 deletions(-) diff --git a/apps/mobile/packages/features/staff/profile_sections/compliance/tax_forms/lib/src/data/repositories/tax_forms_repository_impl.dart b/apps/mobile/packages/features/staff/profile_sections/compliance/tax_forms/lib/src/data/repositories/tax_forms_repository_impl.dart index be9a1cc0..c834f02f 100644 --- a/apps/mobile/packages/features/staff/profile_sections/compliance/tax_forms/lib/src/data/repositories/tax_forms_repository_impl.dart +++ b/apps/mobile/packages/features/staff/profile_sections/compliance/tax_forms/lib/src/data/repositories/tax_forms_repository_impl.dart @@ -1,7 +1,5 @@ import 'dart:async'; -import 'package:firebase_auth/firebase_auth.dart' as auth; -import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_data_connect/firebase_data_connect.dart'; import 'package:krow_data_connect/krow_data_connect.dart' as dc; import 'package:krow_domain/krow_domain.dart'; @@ -10,45 +8,21 @@ import '../../domain/repositories/tax_forms_repository.dart'; import '../mappers/tax_form_mapper.dart'; class TaxFormsRepositoryImpl - with dc.DataErrorHandler implements TaxFormsRepository { - TaxFormsRepositoryImpl({ - required this.firebaseAuth, - required this.dataConnect, - }); + TaxFormsRepositoryImpl() : _service = dc.DataConnectService.instance; - final auth.FirebaseAuth firebaseAuth; - final dc.ExampleConnector dataConnect; - - /// Helper to get the logged-in staff ID. - String _getStaffId() { - final auth.User? user = firebaseAuth.currentUser; - if (user == null) { - throw const NotAuthenticatedException( - technicalMessage: 'Firebase User is null', - ); - } - - final String? staffId = dc.StaffSessionStore.instance.session?.staff?.id; - if (staffId == null || staffId.isEmpty) { - throw const StaffProfileNotFoundException( - technicalMessage: 'Staff ID missing in SessionStore', - ); - } - return staffId; - } + final dc.DataConnectService _service; @override Future> getTaxForms() async { - return executeProtected(() async { - final String staffId = _getStaffId(); - final QueryResult - result = await dataConnect + return _service.run(() async { + final String staffId = await _service.getStaffId(); + final response = await _service.connector .getTaxFormsByStaffId(staffId: staffId) .execute(); final List forms = - result.data.taxForms.map(TaxFormMapper.fromDataConnect).toList(); + response.data.taxForms.map(TaxFormMapper.fromDataConnect).toList(); // Check if required forms exist, create if not. final Set typesPresent = @@ -65,11 +39,9 @@ class TaxFormsRepositoryImpl } if (createdNew) { - final QueryResult< - dc.GetTaxFormsByStaffIdData, - dc.GetTaxFormsByStaffIdVariables> result2 = - await dataConnect.getTaxFormsByStaffId(staffId: staffId).execute(); - return result2.data.taxForms + final response2 = + await _service.connector.getTaxFormsByStaffId(staffId: staffId).execute(); + return response2.data.taxForms .map(TaxFormMapper.fromDataConnect) .toList(); } @@ -79,7 +51,7 @@ class TaxFormsRepositoryImpl } Future _createInitialForm(String staffId, TaxFormType type) async { - await dataConnect + await _service.connector .createTaxForm( staffId: staffId, formType: @@ -95,10 +67,10 @@ class TaxFormsRepositoryImpl @override Future updateI9Form(I9TaxForm form) async { - return executeProtected(() async { + return _service.run(() async { final Map data = form.formData; final dc.UpdateTaxFormVariablesBuilder builder = - dataConnect.updateTaxForm(id: form.id); + _service.connector.updateTaxForm(id: form.id); _mapCommonFields(builder, data); _mapI9Fields(builder, data); await builder.execute(); @@ -107,10 +79,10 @@ class TaxFormsRepositoryImpl @override Future submitI9Form(I9TaxForm form) async { - return executeProtected(() async { + return _service.run(() async { final Map data = form.formData; final dc.UpdateTaxFormVariablesBuilder builder = - dataConnect.updateTaxForm(id: form.id); + _service.connector.updateTaxForm(id: form.id); _mapCommonFields(builder, data); _mapI9Fields(builder, data); await builder.status(dc.TaxFormStatus.SUBMITTED).execute(); @@ -119,10 +91,10 @@ class TaxFormsRepositoryImpl @override Future updateW4Form(W4TaxForm form) async { - return executeProtected(() async { + return _service.run(() async { final Map data = form.formData; final dc.UpdateTaxFormVariablesBuilder builder = - dataConnect.updateTaxForm(id: form.id); + _service.connector.updateTaxForm(id: form.id); _mapCommonFields(builder, data); _mapW4Fields(builder, data); await builder.execute(); @@ -131,10 +103,10 @@ class TaxFormsRepositoryImpl @override Future submitW4Form(W4TaxForm form) async { - return executeProtected(() async { + return _service.run(() async { final Map data = form.formData; final dc.UpdateTaxFormVariablesBuilder builder = - dataConnect.updateTaxForm(id: form.id); + _service.connector.updateTaxForm(id: form.id); _mapCommonFields(builder, data); _mapW4Fields(builder, data); await builder.status(dc.TaxFormStatus.SUBMITTED).execute(); diff --git a/apps/mobile/packages/features/staff/profile_sections/compliance/tax_forms/lib/src/staff_tax_forms_module.dart b/apps/mobile/packages/features/staff/profile_sections/compliance/tax_forms/lib/src/staff_tax_forms_module.dart index 95fdd71e..c26c007f 100644 --- a/apps/mobile/packages/features/staff/profile_sections/compliance/tax_forms/lib/src/staff_tax_forms_module.dart +++ b/apps/mobile/packages/features/staff/profile_sections/compliance/tax_forms/lib/src/staff_tax_forms_module.dart @@ -1,7 +1,5 @@ -import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter_modular/flutter_modular.dart'; import 'package:krow_core/core.dart'; -import 'package:krow_data_connect/krow_data_connect.dart'; import 'package:krow_domain/krow_domain.dart'; import 'data/repositories/tax_forms_repository_impl.dart'; import 'domain/repositories/tax_forms_repository.dart'; @@ -18,12 +16,7 @@ import 'presentation/pages/tax_forms_page.dart'; class StaffTaxFormsModule extends Module { @override void binds(Injector i) { - i.addLazySingleton( - () => TaxFormsRepositoryImpl( - firebaseAuth: FirebaseAuth.instance, - dataConnect: ExampleConnector.instance, - ), - ); + i.addLazySingleton(TaxFormsRepositoryImpl.new); // Use Cases i.addLazySingleton(GetTaxFormsUseCase.new); From 24a13488dabe919d5fd490c0e045d02878c99add Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Mon, 16 Feb 2026 16:27:09 -0500 Subject: [PATCH 11/15] feat(certificates-documents-repositories): Refactor Certificates and Documents repositories to utilize DataConnectService and simplify dependency management --- .../certificates_repository_impl.dart | 35 +++++-------------- .../lib/src/staff_certificates_module.dart | 9 +---- .../documents_repository_impl.dart | 26 ++++---------- .../lib/src/staff_documents_module.dart | 9 +---- 4 files changed, 18 insertions(+), 61 deletions(-) diff --git a/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/data/repositories_impl/certificates_repository_impl.dart b/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/data/repositories_impl/certificates_repository_impl.dart index 411ce9b5..f643a65d 100644 --- a/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/data/repositories_impl/certificates_repository_impl.dart +++ b/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/data/repositories_impl/certificates_repository_impl.dart @@ -1,8 +1,6 @@ -import 'package:firebase_auth/firebase_auth.dart'; -import 'package:firebase_data_connect/firebase_data_connect.dart'; +import 'package:krow_core/core.dart'; import 'package:krow_data_connect/krow_data_connect.dart'; import 'package:krow_domain/krow_domain.dart' as domain; -import 'package:krow_core/core.dart'; import '../../domain/repositories/certificates_repository.dart'; @@ -11,37 +9,22 @@ import '../../domain/repositories/certificates_repository.dart'; /// This class handles the communication with the backend via [ExampleConnector]. /// It maps raw generated data types to clean [domain.StaffDocument] entities. class CertificatesRepositoryImpl - with DataErrorHandler implements CertificatesRepository { - /// The generated Data Connect SDK client. - final ExampleConnector _dataConnect; - - /// The Firebase Authentication instance. - final FirebaseAuth _firebaseAuth; + /// The Data Connect service instance. + final DataConnectService _service; /// Creates a [CertificatesRepositoryImpl]. - /// - /// Requires [ExampleConnector] for data access and [FirebaseAuth] for user context. - CertificatesRepositoryImpl({ - required ExampleConnector dataConnect, - required FirebaseAuth firebaseAuth, - }) : _dataConnect = dataConnect, - _firebaseAuth = firebaseAuth; + CertificatesRepositoryImpl() : _service = DataConnectService.instance; @override Future> getCertificates() async { - return executeProtected(() async { - final User? currentUser = _firebaseAuth.currentUser; - if (currentUser == null) { - throw domain.NotAuthenticatedException( - technicalMessage: 'User not authenticated'); - } + return _service.run(() async { + final String staffId = await _service.getStaffId(); // Execute the query via DataConnect generated SDK - final QueryResult result = - await _dataConnect - .listStaffDocumentsByStaffId(staffId: currentUser.uid) + final result = + await _service.connector + .listStaffDocumentsByStaffId(staffId: staffId) .execute(); // Map the generated SDK types to pure Domain entities diff --git a/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/staff_certificates_module.dart b/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/staff_certificates_module.dart index d9d39a6b..1d444c0b 100644 --- a/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/staff_certificates_module.dart +++ b/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/staff_certificates_module.dart @@ -1,7 +1,5 @@ -import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter_modular/flutter_modular.dart'; import 'package:krow_core/core.dart'; -import 'package:krow_data_connect/krow_data_connect.dart'; import 'data/repositories_impl/certificates_repository_impl.dart'; import 'domain/repositories/certificates_repository.dart'; @@ -12,12 +10,7 @@ import 'presentation/pages/certificates_page.dart'; class StaffCertificatesModule extends Module { @override void binds(Injector i) { - i.addLazySingleton( - () => CertificatesRepositoryImpl( - dataConnect: i.get(), // Assuming ExampleConnector is provided by parent module - firebaseAuth: FirebaseAuth.instance, - ), - ); + i.addLazySingleton(CertificatesRepositoryImpl.new); i.addLazySingleton(GetCertificatesUseCase.new); i.addLazySingleton(CertificatesCubit.new); } diff --git a/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/data/repositories_impl/documents_repository_impl.dart b/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/data/repositories_impl/documents_repository_impl.dart index 2a82c255..b72458e7 100644 --- a/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/data/repositories_impl/documents_repository_impl.dart +++ b/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/data/repositories_impl/documents_repository_impl.dart @@ -1,39 +1,27 @@ -import 'package:firebase_auth/firebase_auth.dart'; -import 'package:firebase_data_connect/firebase_data_connect.dart'; +import 'package:krow_core/core.dart'; import 'package:krow_data_connect/krow_data_connect.dart'; import 'package:krow_domain/krow_domain.dart' as domain; -import 'package:krow_core/core.dart'; import '../../domain/repositories/documents_repository.dart'; /// Implementation of [DocumentsRepository] using Data Connect. class DocumentsRepositoryImpl - with DataErrorHandler implements DocumentsRepository { - final ExampleConnector _dataConnect; - final FirebaseAuth _firebaseAuth; + final DataConnectService _service; - DocumentsRepositoryImpl({ - required ExampleConnector dataConnect, - required FirebaseAuth firebaseAuth, - }) : _dataConnect = dataConnect, - _firebaseAuth = firebaseAuth; + DocumentsRepositoryImpl() : _service = DataConnectService.instance; @override Future> getDocuments() async { - return executeProtected(() async { - final User? currentUser = _firebaseAuth.currentUser; - if (currentUser == null) { - throw domain.NotAuthenticatedException( - technicalMessage: 'User not authenticated'); - } + return _service.run(() async { + final String? staffId = await _service.getStaffId(); /// MOCK IMPLEMENTATION /// To be replaced with real data connect query when available return [ domain.StaffDocument( id: 'doc1', - staffId: currentUser.uid, + staffId: staffId!, documentId: 'd1', name: 'Work Permit', description: 'Valid work permit document', @@ -43,7 +31,7 @@ class DocumentsRepositoryImpl ), domain.StaffDocument( id: 'doc2', - staffId: currentUser.uid, + staffId: staffId!, documentId: 'd2', name: 'Health and Safety Training', description: 'Certificate of completion for health and safety training', diff --git a/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/staff_documents_module.dart b/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/staff_documents_module.dart index b0d63374..d1fcd11a 100644 --- a/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/staff_documents_module.dart +++ b/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/staff_documents_module.dart @@ -1,7 +1,5 @@ -import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter_modular/flutter_modular.dart'; import 'package:krow_core/core.dart'; -import 'package:krow_data_connect/krow_data_connect.dart'; import 'data/repositories_impl/documents_repository_impl.dart'; import 'domain/repositories/documents_repository.dart'; import 'domain/usecases/get_documents_usecase.dart'; @@ -11,12 +9,7 @@ import 'presentation/pages/documents_page.dart'; class StaffDocumentsModule extends Module { @override void binds(Injector i) { - i.addLazySingleton( - () => DocumentsRepositoryImpl( - dataConnect: ExampleConnector.instance, - firebaseAuth: FirebaseAuth.instance, - ), - ); + i.addLazySingleton(DocumentsRepositoryImpl.new); i.addLazySingleton(GetDocumentsUseCase.new); i.addLazySingleton(DocumentsCubit.new); } From 8889b8876e08255b87ffd68bad6fce4b81067634 Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Mon, 16 Feb 2026 16:35:26 -0500 Subject: [PATCH 12/15] feat(time-card-repository): Refactor TimeCardRepositoryImpl to utilize DataConnectService and simplify authentication handling --- .../time_card_repository_impl.dart | 66 +++++++------------ .../lib/src/staff_time_card_module.dart | 11 +--- 2 files changed, 28 insertions(+), 49 deletions(-) diff --git a/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/data/repositories_impl/time_card_repository_impl.dart b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/data/repositories_impl/time_card_repository_impl.dart index 15823f5b..eee89873 100644 --- a/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/data/repositories_impl/time_card_repository_impl.dart +++ b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/data/repositories_impl/time_card_repository_impl.dart @@ -1,63 +1,46 @@ -import 'package:firebase_auth/firebase_auth.dart' as firebase; import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc; import 'package:intl/intl.dart'; import 'package:krow_data_connect/krow_data_connect.dart' as dc; import 'package:krow_domain/krow_domain.dart'; // ignore: implementation_imports import 'package:krow_domain/src/adapters/financial/time_card_adapter.dart'; -import 'package:krow_core/core.dart'; import '../../domain/repositories/time_card_repository.dart'; /// Implementation of [TimeCardRepository] using Firebase Data Connect. -class TimeCardRepositoryImpl - with dc.DataErrorHandler - implements TimeCardRepository { - final dc.ExampleConnector _dataConnect; - final firebase.FirebaseAuth _firebaseAuth; +class TimeCardRepositoryImpl implements TimeCardRepository { + final dc.DataConnectService _service; /// Creates a [TimeCardRepositoryImpl]. - TimeCardRepositoryImpl({ - required dc.ExampleConnector dataConnect, - required firebase.FirebaseAuth firebaseAuth, - }) : _dataConnect = dataConnect, - _firebaseAuth = firebaseAuth; - - Future _getStaffId() async { - final firebase.User? user = _firebaseAuth.currentUser; - if (user == null) { - throw const NotAuthenticatedException( - technicalMessage: 'User not authenticated'); - } - - final fdc.QueryResult result = - await _dataConnect.getStaffByUserId(userId: user.uid).execute(); - if (result.data.staffs.isEmpty) { - throw const ServerException(technicalMessage: 'Staff profile not found'); - } - return result.data.staffs.first.id; - } + TimeCardRepositoryImpl({dc.DataConnectService? service}) + : _service = service ?? dc.DataConnectService.instance; @override Future> getTimeCards(DateTime month) async { - return executeProtected(() async { - final String staffId = await _getStaffId(); + return _service.run(() async { + final String staffId = await _service.getStaffId(); // Fetch applications. Limit can be adjusted, assuming 100 is safe for now. - final fdc.QueryResult result = - await _dataConnect.getApplicationsByStaffId(staffId: staffId).limit(100).execute(); + final fdc.QueryResult result = + await _service.connector + .getApplicationsByStaffId(staffId: staffId) + .limit(100) + .execute(); return result.data.applications .where((dc.GetApplicationsByStaffIdApplications app) { - final DateTime? shiftDate = app.shift.date == null - ? null - : DateTimeUtils.toDeviceTime(app.shift.date!.toDateTime()); + final DateTime? shiftDate = _service.toDateTime(app.shift.date); if (shiftDate == null) return false; - return shiftDate.year == month.year && shiftDate.month == month.month; + return shiftDate.year == month.year && + shiftDate.month == month.month; }) .map((dc.GetApplicationsByStaffIdApplications app) { - final DateTime shiftDate = - DateTimeUtils.toDeviceTime(app.shift.date!.toDateTime()); - final String startTime = _formatTime(app.checkInTime) ?? _formatTime(app.shift.startTime) ?? ''; - final String endTime = _formatTime(app.checkOutTime) ?? _formatTime(app.shift.endTime) ?? ''; + final DateTime shiftDate = _service.toDateTime(app.shift.date)!; + final String startTime = _formatTime(app.checkInTime) ?? + _formatTime(app.shift.startTime) ?? + ''; + final String endTime = _formatTime(app.checkOutTime) ?? + _formatTime(app.shift.endTime) ?? + ''; // Prefer shiftRole values for pay/hours final double hours = app.shiftRole.hours ?? 0.0; @@ -84,7 +67,8 @@ class TimeCardRepositoryImpl String? _formatTime(fdc.Timestamp? timestamp) { if (timestamp == null) return null; - return DateFormat('HH:mm') - .format(DateTimeUtils.toDeviceTime(timestamp.toDateTime())); + final DateTime? dt = _service.toDateTime(timestamp); + if (dt == null) return null; + return DateFormat('HH:mm').format(dt); } } diff --git a/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/staff_time_card_module.dart b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/staff_time_card_module.dart index b47632a6..59ff493b 100644 --- a/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/staff_time_card_module.dart +++ b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/staff_time_card_module.dart @@ -1,6 +1,6 @@ library staff_time_card; -import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_modular/flutter_modular.dart'; import 'package:krow_core/core.dart'; import 'package:krow_data_connect/krow_data_connect.dart'; @@ -24,12 +24,7 @@ class StaffTimeCardModule extends Module { @override void binds(Injector i) { // Repositories - i.add( - () => TimeCardRepositoryImpl( - dataConnect: ExampleConnector.instance, - firebaseAuth: FirebaseAuth.instance, - ), - ); + i.addLazySingleton(TimeCardRepositoryImpl.new); // UseCases i.add(GetTimeCardsUseCase.new); @@ -42,7 +37,7 @@ class StaffTimeCardModule extends Module { void routes(RouteManager r) { r.child( StaffPaths.childRoute(StaffPaths.timeCard, StaffPaths.timeCard), - child: (context) => const TimeCardPage(), + child: (BuildContext context) => const TimeCardPage(), ); } } From 3c5987bde4ab0a9a0fa6d6040bc95345a9324f08 Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Mon, 16 Feb 2026 16:43:00 -0500 Subject: [PATCH 13/15] feat(bank-account-repository): Refactor BankAccountRepositoryImpl to utilize DataConnectService and remove FirebaseAuth dependency --- .../bank_account_repository_impl.dart | 78 ++++++++----------- .../lib/src/staff_bank_account_module.dart | 11 +-- 2 files changed, 35 insertions(+), 54 deletions(-) diff --git a/apps/mobile/packages/features/staff/profile_sections/finances/staff_bank_account/lib/src/data/repositories/bank_account_repository_impl.dart b/apps/mobile/packages/features/staff/profile_sections/finances/staff_bank_account/lib/src/data/repositories/bank_account_repository_impl.dart index e2957389..14614b66 100644 --- a/apps/mobile/packages/features/staff/profile_sections/finances/staff_bank_account/lib/src/data/repositories/bank_account_repository_impl.dart +++ b/apps/mobile/packages/features/staff/profile_sections/finances/staff_bank_account/lib/src/data/repositories/bank_account_repository_impl.dart @@ -1,34 +1,31 @@ -import 'package:firebase_auth/firebase_auth.dart' as auth; import 'package:firebase_data_connect/firebase_data_connect.dart'; import 'package:krow_data_connect/krow_data_connect.dart'; import 'package:krow_domain/krow_domain.dart'; import '../../domain/repositories/bank_account_repository.dart'; /// Implementation of [BankAccountRepository] that integrates with Data Connect. -class BankAccountRepositoryImpl - with DataErrorHandler - implements BankAccountRepository { +class BankAccountRepositoryImpl implements BankAccountRepository { /// Creates a [BankAccountRepositoryImpl]. - const BankAccountRepositoryImpl({ - required this.dataConnect, - required this.firebaseAuth, - }); + BankAccountRepositoryImpl({ + DataConnectService? service, + }) : _service = service ?? DataConnectService.instance; - /// The Data Connect instance. - final ExampleConnector dataConnect; - /// The Firebase Auth instance. - final auth.FirebaseAuth firebaseAuth; + /// The Data Connect service. + final DataConnectService _service; @override Future> getAccounts() async { - return executeProtected(() async { - final String staffId = _getStaffId(); - + return _service.run(() async { + final String staffId = await _service.getStaffId(); + + var x = staffId; + + print(x); final QueryResult - result = await dataConnect + result = await _service.connector .getAccountsByOwnerId(ownerId: staffId) .execute(); - + return result.data.accounts.map((GetAccountsByOwnerIdAccounts account) { return BankAccountAdapter.fromPrimitives( id: account.id, @@ -37,7 +34,9 @@ class BankAccountRepositoryImpl accountNumber: account.accountNumber, last4: account.last4, sortCode: account.routeNumber, - type: account.type is Known ? (account.type as Known).value.name : null, + type: account.type is Known + ? (account.type as Known).value.name + : null, isPrimary: account.isPrimary, ); }).toList(); @@ -46,44 +45,31 @@ class BankAccountRepositoryImpl @override Future addAccount(BankAccount account) async { - return executeProtected(() async { - final String staffId = _getStaffId(); + return _service.run(() async { + final String staffId = await _service.getStaffId(); final QueryResult - existingAccounts = await dataConnect + existingAccounts = await _service.connector .getAccountsByOwnerId(ownerId: staffId) .execute(); final bool hasAccounts = existingAccounts.data.accounts.isNotEmpty; final bool isPrimary = !hasAccounts; - await dataConnect.createAccount( - bank: account.bankName, - type: AccountType.values.byName(BankAccountAdapter.typeToString(account.type)), - last4: _safeLast4(account.last4, account.accountNumber), - ownerId: staffId, - ) - .isPrimary(isPrimary) - .accountNumber(account.accountNumber) - .routeNumber(account.sortCode) - .execute(); + await _service.connector + .createAccount( + bank: account.bankName, + type: AccountType.values + .byName(BankAccountAdapter.typeToString(account.type)), + last4: _safeLast4(account.last4, account.accountNumber), + ownerId: staffId, + ) + .isPrimary(isPrimary) + .accountNumber(account.accountNumber) + .routeNumber(account.sortCode) + .execute(); }); } - /// Helper to get the logged-in staff ID. - String _getStaffId() { - final auth.User? user = firebaseAuth.currentUser; - if (user == null) { - throw const NotAuthenticatedException( - technicalMessage: 'User not authenticated'); - } - - final String? staffId = StaffSessionStore.instance.session?.staff?.id; - if (staffId == null || staffId.isEmpty) { - throw const ServerException(technicalMessage: 'Staff profile is missing or session not initialized.'); - } - return staffId; - } - /// Ensures we have a last4 value, either from input or derived from account number. String _safeLast4(String? last4, String accountNumber) { if (last4 != null && last4.isNotEmpty) { diff --git a/apps/mobile/packages/features/staff/profile_sections/finances/staff_bank_account/lib/src/staff_bank_account_module.dart b/apps/mobile/packages/features/staff/profile_sections/finances/staff_bank_account/lib/src/staff_bank_account_module.dart index 2312e299..93e7d69d 100644 --- a/apps/mobile/packages/features/staff/profile_sections/finances/staff_bank_account/lib/src/staff_bank_account_module.dart +++ b/apps/mobile/packages/features/staff/profile_sections/finances/staff_bank_account/lib/src/staff_bank_account_module.dart @@ -1,4 +1,4 @@ -import 'package:firebase_auth/firebase_auth.dart' as auth; +import 'package:flutter/widgets.dart'; import 'package:flutter_modular/flutter_modular.dart'; import 'package:krow_core/core.dart'; import 'package:krow_data_connect/krow_data_connect.dart'; @@ -17,12 +17,7 @@ class StaffBankAccountModule extends Module { @override void binds(Injector i) { // Repositories - i.addLazySingleton( - () => BankAccountRepositoryImpl( - firebaseAuth: auth.FirebaseAuth.instance, - dataConnect: ExampleConnector.instance, - ), - ); + i.addLazySingleton(BankAccountRepositoryImpl.new); // Use Cases i.addLazySingleton(GetBankAccountsUseCase.new); @@ -41,7 +36,7 @@ class StaffBankAccountModule extends Module { void routes(RouteManager r) { r.child( StaffPaths.childRoute(StaffPaths.bankAccount, StaffPaths.bankAccount), - child: (_) => const BankAccountPage(), + child: (BuildContext context) => const BankAccountPage(), ); } } From 17423c5d66c05faf4e3a220976be37e7b6533072 Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Mon, 16 Feb 2026 16:54:20 -0500 Subject: [PATCH 14/15] feat: Refactor repositories to utilize DataConnectService and remove FirebaseAuth dependency --- .../attire/lib/src/attire_module.dart | 7 +-- .../attire_repository_impl.dart | 30 +++++++------ .../emergency_contact_repository_impl.dart | 42 +++++------------- .../src/staff_emergency_contact_module.dart | 8 +--- .../experience_repository_impl.dart | 43 +++++++------------ .../lib/staff_profile_experience.dart | 6 +-- .../personal_info_repository_impl.dart | 41 ++++++++---------- .../lib/src/staff_profile_info_module.dart | 8 +--- 8 files changed, 69 insertions(+), 116 deletions(-) diff --git a/apps/mobile/packages/features/staff/profile_sections/onboarding/attire/lib/src/attire_module.dart b/apps/mobile/packages/features/staff/profile_sections/onboarding/attire/lib/src/attire_module.dart index 7da0bc6a..7937e0c1 100644 --- a/apps/mobile/packages/features/staff/profile_sections/onboarding/attire/lib/src/attire_module.dart +++ b/apps/mobile/packages/features/staff/profile_sections/onboarding/attire/lib/src/attire_module.dart @@ -1,6 +1,5 @@ import 'package:flutter_modular/flutter_modular.dart'; import 'package:krow_core/core.dart'; -import 'package:krow_data_connect/krow_data_connect.dart'; import 'data/repositories_impl/attire_repository_impl.dart'; import 'domain/repositories/attire_repository.dart'; @@ -14,10 +13,8 @@ class StaffAttireModule extends Module { @override void binds(Injector i) { // Repository - i.addLazySingleton( - () => AttireRepositoryImpl(ExampleConnector.instance), - ); - + i.addLazySingleton(AttireRepositoryImpl.new); + // Use Cases i.addLazySingleton(GetAttireOptionsUseCase.new); i.addLazySingleton(SaveAttireUseCase.new); diff --git a/apps/mobile/packages/features/staff/profile_sections/onboarding/attire/lib/src/data/repositories_impl/attire_repository_impl.dart b/apps/mobile/packages/features/staff/profile_sections/onboarding/attire/lib/src/data/repositories_impl/attire_repository_impl.dart index aec7ee03..cff32f53 100644 --- a/apps/mobile/packages/features/staff/profile_sections/onboarding/attire/lib/src/data/repositories_impl/attire_repository_impl.dart +++ b/apps/mobile/packages/features/staff/profile_sections/onboarding/attire/lib/src/data/repositories_impl/attire_repository_impl.dart @@ -6,24 +6,30 @@ import '../../domain/repositories/attire_repository.dart'; /// Implementation of [AttireRepository]. /// -/// Delegates data access to [ExampleConnector] from `data_connect`. +/// Delegates data access to [DataConnectService]. class AttireRepositoryImpl implements AttireRepository { - /// The Data Connect connector instance. - final ExampleConnector _connector; + /// The Data Connect service. + final DataConnectService _service; /// Creates an [AttireRepositoryImpl]. - AttireRepositoryImpl(this._connector); + AttireRepositoryImpl({DataConnectService? service}) + : _service = service ?? DataConnectService.instance; @override Future> getAttireOptions() async { - final QueryResult result = await _connector.listAttireOptions().execute(); - return result.data.attireOptions.map((ListAttireOptionsAttireOptions e) => AttireItem( - id: e.itemId, - label: e.label, - iconName: e.icon, - imageUrl: e.imageUrl, - isMandatory: e.isMandatory ?? false, - )).toList(); + return _service.run(() async { + final QueryResult result = + await _service.connector.listAttireOptions().execute(); + return result.data.attireOptions + .map((ListAttireOptionsAttireOptions e) => AttireItem( + id: e.itemId, + label: e.label, + iconName: e.icon, + imageUrl: e.imageUrl, + isMandatory: e.isMandatory ?? false, + )) + .toList(); + }); } @override diff --git a/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/data/repositories/emergency_contact_repository_impl.dart b/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/data/repositories/emergency_contact_repository_impl.dart index c3ec4792..afea63f9 100644 --- a/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/data/repositories/emergency_contact_repository_impl.dart +++ b/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/data/repositories/emergency_contact_repository_impl.dart @@ -1,4 +1,3 @@ -import 'package:firebase_auth/firebase_auth.dart'; import 'package:krow_data_connect/krow_data_connect.dart' as dc; import 'package:krow_domain/krow_domain.dart'; import '../../domain/repositories/emergency_contact_repository_interface.dart'; @@ -7,38 +6,19 @@ import '../../domain/repositories/emergency_contact_repository_interface.dart'; /// /// This repository delegates data operations to Firebase Data Connect. class EmergencyContactRepositoryImpl - with dc.DataErrorHandler implements EmergencyContactRepositoryInterface { - final dc.ExampleConnector _dataConnect; - final FirebaseAuth _firebaseAuth; + final dc.DataConnectService _service; /// Creates an [EmergencyContactRepositoryImpl]. EmergencyContactRepositoryImpl({ - required dc.ExampleConnector dataConnect, - required FirebaseAuth firebaseAuth, - }) : _dataConnect = dataConnect, - _firebaseAuth = firebaseAuth; - - Future _getStaffId() async { - final user = _firebaseAuth.currentUser; - if (user == null) { - throw const NotAuthenticatedException( - technicalMessage: 'User not authenticated'); - } - - final result = - await _dataConnect.getStaffByUserId(userId: user.uid).execute(); - if (result.data.staffs.isEmpty) { - throw const ServerException(technicalMessage: 'Staff profile not found'); - } - return result.data.staffs.first.id; - } + dc.DataConnectService? service, + }) : _service = service ?? dc.DataConnectService.instance; @override Future> getContacts() async { - return executeProtected(() async { - final staffId = await _getStaffId(); - final result = await _dataConnect + return _service.run(() async { + final staffId = await _service.getStaffId(); + final result = await _service.connector .getEmergencyContactsByStaffId(staffId: staffId) .execute(); @@ -55,11 +35,11 @@ class EmergencyContactRepositoryImpl @override Future saveContacts(List contacts) async { - return executeProtected(() async { - final staffId = await _getStaffId(); + return _service.run(() async { + final staffId = await _service.getStaffId(); // 1. Get existing to delete - final existingResult = await _dataConnect + final existingResult = await _service.connector .getEmergencyContactsByStaffId(staffId: staffId) .execute(); final existingIds = @@ -67,7 +47,7 @@ class EmergencyContactRepositoryImpl // 2. Delete all existing await Future.wait(existingIds.map( - (id) => _dataConnect.deleteEmergencyContact(id: id).execute())); + (id) => _service.connector.deleteEmergencyContact(id: id).execute())); // 3. Create new await Future.wait(contacts.map((contact) { @@ -87,7 +67,7 @@ class EmergencyContactRepositoryImpl break; } - return _dataConnect + return _service.connector .createEmergencyContact( name: contact.name, phone: contact.phone, diff --git a/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/staff_emergency_contact_module.dart b/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/staff_emergency_contact_module.dart index 5dfb7a30..3f7bea36 100644 --- a/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/staff_emergency_contact_module.dart +++ b/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/staff_emergency_contact_module.dart @@ -1,6 +1,5 @@ -import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter_modular/flutter_modular.dart'; -import 'package:krow_data_connect/krow_data_connect.dart'; + import 'data/repositories/emergency_contact_repository_impl.dart'; import 'domain/repositories/emergency_contact_repository_interface.dart'; import 'domain/usecases/get_emergency_contacts_usecase.dart'; @@ -13,10 +12,7 @@ class StaffEmergencyContactModule extends Module { void binds(Injector i) { // Repository i.addLazySingleton( - () => EmergencyContactRepositoryImpl( - dataConnect: ExampleConnector.instance, - firebaseAuth: FirebaseAuth.instance, - ), + EmergencyContactRepositoryImpl.new, ); // UseCases diff --git a/apps/mobile/packages/features/staff/profile_sections/onboarding/experience/lib/src/data/repositories/experience_repository_impl.dart b/apps/mobile/packages/features/staff/profile_sections/onboarding/experience/lib/src/data/repositories/experience_repository_impl.dart index 159dd31f..4b104d82 100644 --- a/apps/mobile/packages/features/staff/profile_sections/onboarding/experience/lib/src/data/repositories/experience_repository_impl.dart +++ b/apps/mobile/packages/features/staff/profile_sections/onboarding/experience/lib/src/data/repositories/experience_repository_impl.dart @@ -1,42 +1,31 @@ -import 'package:firebase_auth/firebase_auth.dart'; import 'package:krow_data_connect/krow_data_connect.dart' as dc; -import '../../domain/repositories/experience_repository_interface.dart'; - import 'package:krow_domain/krow_domain.dart'; +import '../../domain/repositories/experience_repository_interface.dart'; + /// Implementation of [ExperienceRepositoryInterface] that delegates to Data Connect. -class ExperienceRepositoryImpl - with dc.DataErrorHandler - implements ExperienceRepositoryInterface { - final dc.ExampleConnector _dataConnect; - // ignore: unused_field - final FirebaseAuth _firebaseAuth; +class ExperienceRepositoryImpl implements ExperienceRepositoryInterface { + final dc.DataConnectService _service; - /// Creates a [ExperienceRepositoryImpl] using Data Connect and Auth. + /// Creates a [ExperienceRepositoryImpl] using Data Connect Service. ExperienceRepositoryImpl({ - required dc.ExampleConnector dataConnect, - required FirebaseAuth firebaseAuth, - }) : _dataConnect = dataConnect, - _firebaseAuth = firebaseAuth; + dc.DataConnectService? service, + }) : _service = service ?? dc.DataConnectService.instance; - Future _getStaff() async { - final user = _firebaseAuth.currentUser; - if (user == null) { - throw const NotAuthenticatedException( - technicalMessage: 'User not authenticated'); - } + Future _getStaff() async { + final staffId = await _service.getStaffId(); final result = - await _dataConnect.getStaffByUserId(userId: user.uid).execute(); - if (result.data.staffs.isEmpty) { + await _service.connector.getStaffById(id: staffId).execute(); + if (result.data.staff == null) { throw const ServerException(technicalMessage: 'Staff profile not found'); } - return result.data.staffs.first; + return result.data.staff!; } @override Future> getIndustries() async { - return executeProtected(() async { + return _service.run(() async { final staff = await _getStaff(); return staff.industries ?? []; }); @@ -44,7 +33,7 @@ class ExperienceRepositoryImpl @override Future> getSkills() async { - return executeProtected(() async { + return _service.run(() async { final staff = await _getStaff(); return staff.skills ?? []; }); @@ -55,9 +44,9 @@ class ExperienceRepositoryImpl List industries, List skills, ) async { - return executeProtected(() async { + return _service.run(() async { final staff = await _getStaff(); - await _dataConnect + await _service.connector .updateStaff(id: staff.id) .industries(industries) .skills(skills) diff --git a/apps/mobile/packages/features/staff/profile_sections/onboarding/experience/lib/staff_profile_experience.dart b/apps/mobile/packages/features/staff/profile_sections/onboarding/experience/lib/staff_profile_experience.dart index ab4c83e9..db83d59f 100644 --- a/apps/mobile/packages/features/staff/profile_sections/onboarding/experience/lib/staff_profile_experience.dart +++ b/apps/mobile/packages/features/staff/profile_sections/onboarding/experience/lib/staff_profile_experience.dart @@ -1,6 +1,5 @@ library staff_profile_experience; -import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter_modular/flutter_modular.dart'; import 'package:krow_data_connect/krow_data_connect.dart'; @@ -22,10 +21,7 @@ class StaffProfileExperienceModule extends Module { void binds(Injector i) { // Repository i.addLazySingleton( - () => ExperienceRepositoryImpl( - dataConnect: ExampleConnector.instance, - firebaseAuth: FirebaseAuth.instance, - ), + ExperienceRepositoryImpl.new, ); // UseCases diff --git a/apps/mobile/packages/features/staff/profile_sections/onboarding/profile_info/lib/src/data/repositories/personal_info_repository_impl.dart b/apps/mobile/packages/features/staff/profile_sections/onboarding/profile_info/lib/src/data/repositories/personal_info_repository_impl.dart index e2e4b5ba..439a3ba2 100644 --- a/apps/mobile/packages/features/staff/profile_sections/onboarding/profile_info/lib/src/data/repositories/personal_info_repository_impl.dart +++ b/apps/mobile/packages/features/staff/profile_sections/onboarding/profile_info/lib/src/data/repositories/personal_info_repository_impl.dart @@ -1,4 +1,3 @@ -import 'package:firebase_auth/firebase_auth.dart' as firebase_auth; import 'package:firebase_data_connect/firebase_data_connect.dart'; import 'package:krow_data_connect/krow_data_connect.dart'; import 'package:krow_domain/krow_domain.dart'; @@ -14,32 +13,24 @@ import '../../domain/repositories/personal_info_repository_interface.dart'; /// - Mapping between data_connect DTOs and domain entities /// - Containing no business logic class PersonalInfoRepositoryImpl - with DataErrorHandler implements PersonalInfoRepositoryInterface { - /// Creates a [PersonalInfoRepositoryImpl]. /// - /// Requires the Firebase Data Connect connector instance and Firebase Auth. + /// Requires the Firebase Data Connect service. PersonalInfoRepositoryImpl({ - required ExampleConnector dataConnect, - required firebase_auth.FirebaseAuth firebaseAuth, - }) : _dataConnect = dataConnect, - _firebaseAuth = firebaseAuth; - final ExampleConnector _dataConnect; - final firebase_auth.FirebaseAuth _firebaseAuth; + DataConnectService? service, + }) : _service = service ?? DataConnectService.instance; + + final DataConnectService _service; @override Future getStaffProfile() async { - return executeProtected(() async { - final firebase_auth.User? user = _firebaseAuth.currentUser; - if (user == null) { - throw NotAuthenticatedException( - technicalMessage: 'User not authenticated'); - } + return _service.run(() async { + final String uid = _service.auth.currentUser!.uid; // Query staff data from Firebase Data Connect final QueryResult result = - await _dataConnect.getStaffByUserId(userId: user.uid).execute(); + await _service.connector.getStaffByUserId(userId: uid).execute(); if (result.data.staffs.isEmpty) { throw const ServerException(technicalMessage: 'Staff profile not found'); @@ -53,10 +44,12 @@ class PersonalInfoRepositoryImpl } @override - Future updateStaffProfile({required String staffId, required Map data}) async { - return executeProtected(() async { + Future updateStaffProfile( + {required String staffId, required Map data}) async { + return _service.run(() async { // Start building the update mutation - UpdateStaffVariablesBuilder updateBuilder = _dataConnect.updateStaff(id: staffId); + UpdateStaffVariablesBuilder updateBuilder = + _service.connector.updateStaff(id: staffId); // Apply updates from map if present if (data.containsKey('name')) { @@ -72,8 +65,9 @@ class PersonalInfoRepositoryImpl updateBuilder = updateBuilder.photoUrl(data['avatar'] as String?); } if (data.containsKey('preferredLocations')) { - // After schema update and SDK regeneration, preferredLocations accepts List - updateBuilder = updateBuilder.preferredLocations(data['preferredLocations'] as List); + // After schema update and SDK regeneration, preferredLocations accepts List + updateBuilder = updateBuilder.preferredLocations( + data['preferredLocations'] as List); } // Execute the update @@ -81,7 +75,8 @@ class PersonalInfoRepositoryImpl await updateBuilder.execute(); if (result.data.staff_update == null) { - throw const ServerException(technicalMessage: 'Failed to update staff profile'); + throw const ServerException( + technicalMessage: 'Failed to update staff profile'); } // Fetch the updated staff profile to return complete entity diff --git a/apps/mobile/packages/features/staff/profile_sections/onboarding/profile_info/lib/src/staff_profile_info_module.dart b/apps/mobile/packages/features/staff/profile_sections/onboarding/profile_info/lib/src/staff_profile_info_module.dart index 984d010a..47c80748 100644 --- a/apps/mobile/packages/features/staff/profile_sections/onboarding/profile_info/lib/src/staff_profile_info_module.dart +++ b/apps/mobile/packages/features/staff/profile_sections/onboarding/profile_info/lib/src/staff_profile_info_module.dart @@ -1,7 +1,5 @@ -import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:flutter_modular/flutter_modular.dart'; -import 'package:krow_data_connect/krow_data_connect.dart'; import 'data/repositories/personal_info_repository_impl.dart'; import 'domain/repositories/personal_info_repository_interface.dart'; @@ -25,11 +23,7 @@ class StaffProfileInfoModule extends Module { void binds(Injector i) { // Repository i.addLazySingleton( - () => PersonalInfoRepositoryImpl( - dataConnect: ExampleConnector.instance, - firebaseAuth: FirebaseAuth.instance, - ), - ); + PersonalInfoRepositoryImpl.new); // Use Cases - delegate business logic to repository i.addLazySingleton( From d2cb05fe2e51e043fed73774f307255d26e867c5 Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Mon, 16 Feb 2026 16:54:59 -0500 Subject: [PATCH 15/15] fix: Update documentation to reflect correct backend communication via DataConnectService --- .../data/repositories_impl/certificates_repository_impl.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/data/repositories_impl/certificates_repository_impl.dart b/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/data/repositories_impl/certificates_repository_impl.dart index f643a65d..dfb7e44e 100644 --- a/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/data/repositories_impl/certificates_repository_impl.dart +++ b/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/data/repositories_impl/certificates_repository_impl.dart @@ -6,7 +6,7 @@ import '../../domain/repositories/certificates_repository.dart'; /// Implementation of [CertificatesRepository] using Data Connect. /// -/// This class handles the communication with the backend via [ExampleConnector]. +/// This class handles the communication with the backend via [DataConnectService]. /// It maps raw generated data types to clean [domain.StaffDocument] entities. class CertificatesRepositoryImpl implements CertificatesRepository {