feat: Implement staff profile retrieval and sign-out use cases; refactor profile management in the client app
This commit is contained in:
@@ -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_emergency_contacts_completion_usecase.dart';
|
||||||
export 'src/connectors/staff/domain/usecases/get_experience_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_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';
|
export '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:firebase_data_connect/firebase_data_connect.dart';
|
||||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
/// Implementation of [StaffConnectorRepository].
|
/// Implementation of [StaffConnectorRepository].
|
||||||
///
|
///
|
||||||
@@ -137,4 +138,52 @@ class StaffConnectorRepositoryImpl implements StaffConnectorRepository {
|
|||||||
taxForms.isNotEmpty &&
|
taxForms.isNotEmpty &&
|
||||||
hasExperience;
|
hasExperience;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Staff> getStaffProfile() async {
|
||||||
|
return _service.run(() async {
|
||||||
|
final String staffId = await _service.getStaffId();
|
||||||
|
|
||||||
|
final QueryResult<GetStaffByIdData, GetStaffByIdVariables> 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<void> signOut() async {
|
||||||
|
try {
|
||||||
|
await _service.auth.signOut();
|
||||||
|
_service.clearCache();
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Error signing out: ${e.toString()}');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
/// Repository interface for staff connector queries.
|
/// Repository interface for staff connector queries.
|
||||||
///
|
///
|
||||||
/// This interface defines the contract for accessing staff-related data
|
/// 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.
|
/// Returns true if at least one tax form exists.
|
||||||
Future<bool> getTaxFormsCompletion();
|
Future<bool> 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<Staff> getStaffProfile();
|
||||||
|
|
||||||
|
/// Signs out the current user.
|
||||||
|
///
|
||||||
|
/// Clears the user's session and authentication state.
|
||||||
|
///
|
||||||
|
/// Throws an exception if the sign-out fails.
|
||||||
|
Future<void> signOut();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<void, Staff> {
|
||||||
|
/// 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<Staff> call([void params]) => _repository.getStaffProfile();
|
||||||
|
}
|
||||||
@@ -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<void> {
|
||||||
|
/// 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<void> call() => _repository.signOut();
|
||||||
|
}
|
||||||
@@ -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<Staff> 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<void> signOut() async {
|
|
||||||
try {
|
|
||||||
await _service.auth.signOut();
|
|
||||||
_service.clearCache();
|
|
||||||
} catch (e) {
|
|
||||||
throw Exception('Error signing out: ${e.toString()}');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<Staff> getStaffProfile();
|
|
||||||
|
|
||||||
/// Signs out the current user.
|
|
||||||
///
|
|
||||||
/// Clears the user's session and authentication state.
|
|
||||||
/// Should be followed by navigation to the authentication flow.
|
|
||||||
Future<void> signOut();
|
|
||||||
}
|
|
||||||
@@ -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<void, Staff> {
|
|
||||||
final ProfileRepositoryInterface _repository;
|
|
||||||
|
|
||||||
/// Creates a [GetProfileUseCase].
|
|
||||||
///
|
|
||||||
/// Requires a [ProfileRepositoryInterface] to interact with the profile data source.
|
|
||||||
const GetProfileUseCase(this._repository);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Staff> call([void params]) async {
|
|
||||||
// Fetch staff object from repository and return directly
|
|
||||||
return await _repository.getStaffProfile();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<void> {
|
|
||||||
final ProfileRepositoryInterface _repository;
|
|
||||||
|
|
||||||
/// Creates a [SignOutUseCase].
|
|
||||||
///
|
|
||||||
/// Requires a [ProfileRepositoryInterface] to perform the sign-out operation.
|
|
||||||
const SignOutUseCase(this._repository);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> call() {
|
|
||||||
return _repository.signOut();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:krow_core/core.dart';
|
import 'package:krow_core/core.dart';
|
||||||
import '../../domain/usecases/get_profile_usecase.dart';
|
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||||
import '../../domain/usecases/sign_out_usecase.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
import 'profile_state.dart';
|
import 'profile_state.dart';
|
||||||
|
|
||||||
/// Cubit for managing the Profile feature state.
|
/// Cubit for managing the Profile feature state.
|
||||||
@@ -9,8 +10,8 @@ import 'profile_state.dart';
|
|||||||
/// Handles loading profile data and user sign-out actions.
|
/// Handles loading profile data and user sign-out actions.
|
||||||
class ProfileCubit extends Cubit<ProfileState>
|
class ProfileCubit extends Cubit<ProfileState>
|
||||||
with BlocErrorHandler<ProfileState> {
|
with BlocErrorHandler<ProfileState> {
|
||||||
final GetProfileUseCase _getProfileUseCase;
|
final GetStaffProfileUseCase _getProfileUseCase;
|
||||||
final SignOutUseCase _signOutUseCase;
|
final SignOutStaffUseCase _signOutUseCase;
|
||||||
|
|
||||||
/// Creates a [ProfileCubit] with the required use cases.
|
/// Creates a [ProfileCubit] with the required use cases.
|
||||||
ProfileCubit(this._getProfileUseCase, this._signOutUseCase)
|
ProfileCubit(this._getProfileUseCase, this._signOutUseCase)
|
||||||
@@ -27,7 +28,7 @@ class ProfileCubit extends Cubit<ProfileState>
|
|||||||
await handleError(
|
await handleError(
|
||||||
emit: emit,
|
emit: emit,
|
||||||
action: () async {
|
action: () async {
|
||||||
final profile = await _getProfileUseCase();
|
final Staff profile = await _getProfileUseCase();
|
||||||
emit(state.copyWith(status: ProfileStatus.loaded, profile: profile));
|
emit(state.copyWith(status: ProfileStatus.loaded, profile: profile));
|
||||||
},
|
},
|
||||||
onError:
|
onError:
|
||||||
|
|||||||
@@ -40,9 +40,16 @@ class StaffProfilePage extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final ProfileCubit cubit = Modular.get<ProfileCubit>();
|
||||||
|
|
||||||
|
// Load profile data on first build if not already loaded
|
||||||
|
if (cubit.state.status == ProfileStatus.initial) {
|
||||||
|
cubit.loadProfile();
|
||||||
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: BlocProvider<ProfileCubit>(
|
body: BlocProvider<ProfileCubit>.value(
|
||||||
create: (_) => Modular.get<ProfileCubit>()..loadProfile(),
|
value: cubit,
|
||||||
child: BlocConsumer<ProfileCubit, ProfileState>(
|
child: BlocConsumer<ProfileCubit, ProfileState>(
|
||||||
listener: (BuildContext context, ProfileState state) {
|
listener: (BuildContext context, ProfileState state) {
|
||||||
if (state.status == ProfileStatus.signedOut) {
|
if (state.status == ProfileStatus.signedOut) {
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_modular/flutter_modular.dart';
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
import 'package:krow_core/core.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/blocs/profile_cubit.dart';
|
||||||
import 'presentation/pages/staff_profile_page.dart';
|
import 'presentation/pages/staff_profile_page.dart';
|
||||||
|
|
||||||
@@ -15,28 +12,32 @@ import 'presentation/pages/staff_profile_page.dart';
|
|||||||
/// following Clean Architecture principles.
|
/// following Clean Architecture principles.
|
||||||
///
|
///
|
||||||
/// Dependency flow:
|
/// Dependency flow:
|
||||||
/// - Repository implementation (ProfileRepositoryImpl) delegates to data_connect
|
/// - Use cases from data_connect layer (StaffConnectorRepository)
|
||||||
/// - Use cases depend on repository interface
|
|
||||||
/// - Cubit depends on use cases
|
/// - Cubit depends on use cases
|
||||||
class StaffProfileModule extends Module {
|
class StaffProfileModule extends Module {
|
||||||
@override
|
@override
|
||||||
void binds(Injector i) {
|
void binds(Injector i) {
|
||||||
// Repository implementation - delegates to data_connect
|
// StaffConnectorRepository intialization
|
||||||
i.addLazySingleton<ProfileRepositoryInterface>(
|
i.addLazySingleton<StaffConnectorRepository>(
|
||||||
ProfileRepositoryImpl.new,
|
() => StaffConnectorRepositoryImpl(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Use cases - depend on repository interface
|
// Use cases from data_connect - depend on StaffConnectorRepository
|
||||||
i.addLazySingleton<GetProfileUseCase>(
|
i.addLazySingleton<GetStaffProfileUseCase>(
|
||||||
() => GetProfileUseCase(i.get<ProfileRepositoryInterface>()),
|
() =>
|
||||||
|
GetStaffProfileUseCase(repository: i.get<StaffConnectorRepository>()),
|
||||||
);
|
);
|
||||||
i.addLazySingleton<SignOutUseCase>(
|
i.addLazySingleton<SignOutStaffUseCase>(
|
||||||
() => SignOutUseCase(i.get<ProfileRepositoryInterface>()),
|
() => SignOutStaffUseCase(repository: i.get<StaffConnectorRepository>()),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Presentation layer - Cubit depends on use cases
|
// Presentation layer - Cubit as singleton to avoid recreation
|
||||||
i.add<ProfileCubit>(
|
// BlocProvider will use this same instance, preventing state emission after close
|
||||||
() => ProfileCubit(i.get<GetProfileUseCase>(), i.get<SignOutUseCase>()),
|
i.addSingleton<ProfileCubit>(
|
||||||
|
() => ProfileCubit(
|
||||||
|
i.get<GetStaffProfileUseCase>(),
|
||||||
|
i.get<SignOutStaffUseCase>(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user