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 4123cf8b..82d0bfb8 100644 --- a/apps/mobile/packages/data_connect/lib/krow_data_connect.dart +++ b/apps/mobile/packages/data_connect/lib/krow_data_connect.dart @@ -25,4 +25,6 @@ export 'src/connectors/staff/domain/usecases/get_personal_info_completion_usecas export 'src/connectors/staff/domain/usecases/get_emergency_contacts_completion_usecase.dart'; export 'src/connectors/staff/domain/usecases/get_experience_completion_usecase.dart'; export 'src/connectors/staff/domain/usecases/get_tax_forms_completion_usecase.dart'; +export 'src/connectors/staff/domain/usecases/get_staff_profile_usecase.dart'; +export 'src/connectors/staff/domain/usecases/sign_out_staff_usecase.dart'; export 'src/connectors/staff/data/repositories/staff_connector_repository_impl.dart'; \ No newline at end of file diff --git a/apps/mobile/packages/data_connect/lib/src/connectors/staff/data/repositories/staff_connector_repository_impl.dart b/apps/mobile/packages/data_connect/lib/src/connectors/staff/data/repositories/staff_connector_repository_impl.dart index 45c5fd3f..52e66b98 100644 --- a/apps/mobile/packages/data_connect/lib/src/connectors/staff/data/repositories/staff_connector_repository_impl.dart +++ b/apps/mobile/packages/data_connect/lib/src/connectors/staff/data/repositories/staff_connector_repository_impl.dart @@ -1,5 +1,6 @@ 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'; /// Implementation of [StaffConnectorRepository]. /// @@ -137,4 +138,52 @@ class StaffConnectorRepositoryImpl implements StaffConnectorRepository { taxForms.isNotEmpty && hasExperience; } + + @override + Future getStaffProfile() async { + return _service.run(() async { + final String staffId = await _service.getStaffId(); + + final QueryResult response = + await _service.connector + .getStaffById(id: staffId) + .execute(); + + if (response.data.staff == null) { + throw const ServerException( + technicalMessage: 'Staff not found', + ); + } + + final GetStaffByIdStaff rawStaff = response.data.staff!; + + // Map the raw data connect object to the Domain Entity + return Staff( + id: rawStaff.id, + authProviderId: rawStaff.userId, + name: rawStaff.fullName, + email: rawStaff.email ?? '', + phone: rawStaff.phone, + avatar: rawStaff.photoUrl, + status: StaffStatus.active, + address: rawStaff.addres, + totalShifts: rawStaff.totalShifts, + averageRating: rawStaff.averageRating, + onTimeRate: rawStaff.onTimeRate, + noShowCount: rawStaff.noShowCount, + cancellationCount: rawStaff.cancellationCount, + reliabilityScore: rawStaff.reliabilityScore, + ); + }); + } + + @override + Future signOut() async { + try { + await _service.auth.signOut(); + _service.clearCache(); + } catch (e) { + throw Exception('Error signing out: ${e.toString()}'); + } + } } diff --git a/apps/mobile/packages/data_connect/lib/src/connectors/staff/domain/repositories/staff_connector_repository.dart b/apps/mobile/packages/data_connect/lib/src/connectors/staff/domain/repositories/staff_connector_repository.dart index b4dc384b..abd25156 100644 --- a/apps/mobile/packages/data_connect/lib/src/connectors/staff/domain/repositories/staff_connector_repository.dart +++ b/apps/mobile/packages/data_connect/lib/src/connectors/staff/domain/repositories/staff_connector_repository.dart @@ -1,3 +1,5 @@ +import 'package:krow_domain/krow_domain.dart'; + /// Repository interface for staff connector queries. /// /// This interface defines the contract for accessing staff-related data @@ -30,4 +32,18 @@ abstract interface class StaffConnectorRepository { /// /// Returns true if at least one tax form exists. Future getTaxFormsCompletion(); + + /// Fetches the full staff profile for the current authenticated user. + /// + /// Returns a [Staff] entity containing all profile information. + /// + /// Throws an exception if the profile cannot be retrieved. + Future getStaffProfile(); + + /// Signs out the current user. + /// + /// Clears the user's session and authentication state. + /// + /// Throws an exception if the sign-out fails. + Future signOut(); } diff --git a/apps/mobile/packages/data_connect/lib/src/connectors/staff/domain/usecases/get_staff_profile_usecase.dart b/apps/mobile/packages/data_connect/lib/src/connectors/staff/domain/usecases/get_staff_profile_usecase.dart new file mode 100644 index 00000000..3889bd49 --- /dev/null +++ b/apps/mobile/packages/data_connect/lib/src/connectors/staff/domain/usecases/get_staff_profile_usecase.dart @@ -0,0 +1,28 @@ +import 'package:krow_core/core.dart'; +import 'package:krow_domain/krow_domain.dart'; + +import '../repositories/staff_connector_repository.dart'; + +/// Use case for fetching a staff member's full profile information. +/// +/// This use case encapsulates the business logic for retrieving the complete +/// staff profile including personal info, ratings, and reliability scores. +/// It delegates to the repository for data access. +class GetStaffProfileUseCase extends UseCase { + /// Creates a [GetStaffProfileUseCase]. + /// + /// Requires a [StaffConnectorRepository] for data access. + GetStaffProfileUseCase({ + required StaffConnectorRepository repository, + }) : _repository = repository; + + final StaffConnectorRepository _repository; + + /// Executes the use case to get the staff profile. + /// + /// Returns a [Staff] entity containing all profile information. + /// + /// Throws an exception if the operation fails. + @override + Future call([void params]) => _repository.getStaffProfile(); +} diff --git a/apps/mobile/packages/data_connect/lib/src/connectors/staff/domain/usecases/sign_out_staff_usecase.dart b/apps/mobile/packages/data_connect/lib/src/connectors/staff/domain/usecases/sign_out_staff_usecase.dart new file mode 100644 index 00000000..4331006c --- /dev/null +++ b/apps/mobile/packages/data_connect/lib/src/connectors/staff/domain/usecases/sign_out_staff_usecase.dart @@ -0,0 +1,25 @@ +import 'package:krow_core/core.dart'; + +import '../repositories/staff_connector_repository.dart'; + +/// Use case for signing out the current staff user. +/// +/// This use case encapsulates the business logic for signing out, +/// including clearing authentication state and cache. +/// It delegates to the repository for data access. +class SignOutStaffUseCase extends NoInputUseCase { + /// Creates a [SignOutStaffUseCase]. + /// + /// Requires a [StaffConnectorRepository] for data access. + SignOutStaffUseCase({ + required StaffConnectorRepository repository, + }) : _repository = repository; + + final StaffConnectorRepository _repository; + + /// Executes the use case to sign out the user. + /// + /// Throws an exception if the operation fails. + @override + Future call() => _repository.signOut(); +} 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 deleted file mode 100644 index 42aa3a17..00000000 --- a/apps/mobile/packages/features/staff/profile/lib/src/data/repositories/profile_repository_impl.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'package:krow_data_connect/krow_data_connect.dart'; -import 'package:krow_domain/krow_domain.dart'; - -import '../../domain/repositories/profile_repository.dart'; - -/// Implementation of [ProfileRepositoryInterface] that delegates to data_connect. -/// -/// This implementation follows Clean Architecture by: -/// - Implementing the domain layer's repository interface -/// - Delegating all data access to the data_connect package -/// - Not containing any business logic -/// - Only performing data transformation/mapping if needed -/// -/// Currently uses [ProfileRepositoryMock] from data_connect. -/// When Firebase Data Connect is ready, this will be swapped with a real implementation. -class ProfileRepositoryImpl - implements ProfileRepositoryInterface { - /// Creates a [ProfileRepositoryImpl]. - ProfileRepositoryImpl() : _service = DataConnectService.instance; - - final DataConnectService _service; - - @override - Future getStaffProfile() async { - return _service.run(() async { - final staffId = await _service.getStaffId(); - final response = await _service.connector.getStaffById(id: staffId).execute(); - - if (response.data.staff == null) { - throw const ServerException(technicalMessage: 'Staff not found'); - } - - final GetStaffByIdStaff rawStaff = response.data.staff!; - - // Map the raw data connect object to the Domain Entity - return Staff( - id: rawStaff.id, - authProviderId: rawStaff.userId, - name: rawStaff.fullName, - email: rawStaff.email ?? '', - phone: rawStaff.phone, - avatar: rawStaff.photoUrl, - status: StaffStatus.active, - address: rawStaff.addres, - totalShifts: rawStaff.totalShifts, - averageRating: rawStaff.averageRating, - onTimeRate: rawStaff.onTimeRate, - noShowCount: rawStaff.noShowCount, - cancellationCount: rawStaff.cancellationCount, - reliabilityScore: rawStaff.reliabilityScore, - ); - }); - } - - @override - Future signOut() async { - try { - 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/domain/repositories/profile_repository.dart b/apps/mobile/packages/features/staff/profile/lib/src/domain/repositories/profile_repository.dart deleted file mode 100644 index 05868bbb..00000000 --- a/apps/mobile/packages/features/staff/profile/lib/src/domain/repositories/profile_repository.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:krow_domain/krow_domain.dart'; - -/// Repository interface for staff profile operations. -/// -/// Defines the contract for accessing and managing staff profile data. -/// This interface lives in the domain layer and is implemented by the data layer. -/// -/// Following Clean Architecture principles, this interface: -/// - Returns domain entities (Staff from shared domain package) -/// - Defines business requirements without implementation details -/// - Allows the domain layer to be independent of data sources -abstract interface class ProfileRepositoryInterface { - /// Fetches the staff profile for the current authenticated user. - /// - /// Returns a [Staff] entity from the shared domain package containing - /// all profile information. - /// - /// Throws an exception if the profile cannot be retrieved. - Future getStaffProfile(); - - /// Signs out the current user. - /// - /// Clears the user's session and authentication state. - /// Should be followed by navigation to the authentication flow. - Future signOut(); -} diff --git a/apps/mobile/packages/features/staff/profile/lib/src/domain/usecases/get_profile_usecase.dart b/apps/mobile/packages/features/staff/profile/lib/src/domain/usecases/get_profile_usecase.dart deleted file mode 100644 index bb1a96d8..00000000 --- a/apps/mobile/packages/features/staff/profile/lib/src/domain/usecases/get_profile_usecase.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:krow_core/core.dart'; -import 'package:krow_domain/krow_domain.dart'; - -import '../repositories/profile_repository.dart'; - -/// Use case for fetching a staff member's extended profile information. -/// -/// This use case: -/// 1. Fetches the [Staff] object from the repository -/// 2. Returns it directly to the presentation layer -/// -class GetProfileUseCase implements UseCase { - final ProfileRepositoryInterface _repository; - - /// Creates a [GetProfileUseCase]. - /// - /// Requires a [ProfileRepositoryInterface] to interact with the profile data source. - const GetProfileUseCase(this._repository); - - @override - Future call([void params]) async { - // Fetch staff object from repository and return directly - return await _repository.getStaffProfile(); - } -} diff --git a/apps/mobile/packages/features/staff/profile/lib/src/domain/usecases/sign_out_usecase.dart b/apps/mobile/packages/features/staff/profile/lib/src/domain/usecases/sign_out_usecase.dart deleted file mode 100644 index 621d85a8..00000000 --- a/apps/mobile/packages/features/staff/profile/lib/src/domain/usecases/sign_out_usecase.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:krow_core/core.dart'; - -import '../repositories/profile_repository.dart'; - -/// Use case for signing out the current user. -/// -/// This use case delegates the sign-out logic to the [ProfileRepositoryInterface]. -/// -/// Following Clean Architecture principles, this use case: -/// - Encapsulates the sign-out business rule -/// - Depends only on the repository interface -/// - Keeps the domain layer independent of external frameworks -class SignOutUseCase implements NoInputUseCase { - final ProfileRepositoryInterface _repository; - - /// Creates a [SignOutUseCase]. - /// - /// Requires a [ProfileRepositoryInterface] to perform the sign-out operation. - const SignOutUseCase(this._repository); - - @override - Future call() { - return _repository.signOut(); - } -} diff --git a/apps/mobile/packages/features/staff/profile/lib/src/presentation/blocs/profile_cubit.dart b/apps/mobile/packages/features/staff/profile/lib/src/presentation/blocs/profile_cubit.dart index f4cba322..e1591ede 100644 --- a/apps/mobile/packages/features/staff/profile/lib/src/presentation/blocs/profile_cubit.dart +++ b/apps/mobile/packages/features/staff/profile/lib/src/presentation/blocs/profile_cubit.dart @@ -1,7 +1,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:krow_core/core.dart'; -import '../../domain/usecases/get_profile_usecase.dart'; -import '../../domain/usecases/sign_out_usecase.dart'; +import 'package:krow_data_connect/krow_data_connect.dart'; +import 'package:krow_domain/krow_domain.dart'; + import 'profile_state.dart'; /// Cubit for managing the Profile feature state. @@ -9,8 +10,8 @@ import 'profile_state.dart'; /// Handles loading profile data and user sign-out actions. class ProfileCubit extends Cubit with BlocErrorHandler { - final GetProfileUseCase _getProfileUseCase; - final SignOutUseCase _signOutUseCase; + final GetStaffProfileUseCase _getProfileUseCase; + final SignOutStaffUseCase _signOutUseCase; /// Creates a [ProfileCubit] with the required use cases. ProfileCubit(this._getProfileUseCase, this._signOutUseCase) @@ -27,7 +28,7 @@ class ProfileCubit extends Cubit await handleError( emit: emit, action: () async { - final profile = await _getProfileUseCase(); + final Staff profile = await _getProfileUseCase(); emit(state.copyWith(status: ProfileStatus.loaded, profile: profile)); }, onError: diff --git a/apps/mobile/packages/features/staff/profile/lib/src/presentation/pages/staff_profile_page.dart b/apps/mobile/packages/features/staff/profile/lib/src/presentation/pages/staff_profile_page.dart index 0ee25694..36427da0 100644 --- a/apps/mobile/packages/features/staff/profile/lib/src/presentation/pages/staff_profile_page.dart +++ b/apps/mobile/packages/features/staff/profile/lib/src/presentation/pages/staff_profile_page.dart @@ -40,9 +40,16 @@ class StaffProfilePage extends StatelessWidget { @override Widget build(BuildContext context) { + final ProfileCubit cubit = Modular.get(); + + // Load profile data on first build if not already loaded + if (cubit.state.status == ProfileStatus.initial) { + cubit.loadProfile(); + } + return Scaffold( - body: BlocProvider( - create: (_) => Modular.get()..loadProfile(), + body: BlocProvider.value( + value: cubit, child: BlocConsumer( listener: (BuildContext context, ProfileState state) { if (state.status == ProfileStatus.signedOut) { 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 88f56cc5..ff52e308 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,11 +1,8 @@ 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 'data/repositories/profile_repository_impl.dart'; -import 'domain/repositories/profile_repository.dart'; -import 'domain/usecases/get_profile_usecase.dart'; -import 'domain/usecases/sign_out_usecase.dart'; import 'presentation/blocs/profile_cubit.dart'; import 'presentation/pages/staff_profile_page.dart'; @@ -15,28 +12,32 @@ import 'presentation/pages/staff_profile_page.dart'; /// following Clean Architecture principles. /// /// Dependency flow: -/// - Repository implementation (ProfileRepositoryImpl) delegates to data_connect -/// - Use cases depend on repository interface +/// - Use cases from data_connect layer (StaffConnectorRepository) /// - Cubit depends on use cases class StaffProfileModule extends Module { @override void binds(Injector i) { - // Repository implementation - delegates to data_connect - i.addLazySingleton( - ProfileRepositoryImpl.new, + // StaffConnectorRepository intialization + i.addLazySingleton( + () => StaffConnectorRepositoryImpl(), ); - // Use cases - depend on repository interface - i.addLazySingleton( - () => GetProfileUseCase(i.get()), + // Use cases from data_connect - depend on StaffConnectorRepository + i.addLazySingleton( + () => + GetStaffProfileUseCase(repository: i.get()), ); - i.addLazySingleton( - () => SignOutUseCase(i.get()), + i.addLazySingleton( + () => SignOutStaffUseCase(repository: i.get()), ); - // Presentation layer - Cubit depends on use cases - i.add( - () => ProfileCubit(i.get(), i.get()), + // Presentation layer - Cubit as singleton to avoid recreation + // BlocProvider will use this same instance, preventing state emission after close + i.addSingleton( + () => ProfileCubit( + i.get(), + i.get(), + ), ); }