feat: Refactor Staff Profile page to use ProfileCubit and improve loading logic

This commit is contained in:
Achintha Isuru
2026-01-24 14:46:35 -05:00
parent 96ab07b58d
commit a38afb1940
6 changed files with 233 additions and 224 deletions

View File

@@ -29,207 +29,209 @@ class StaffProfilePage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final i18n = t.staff.profile; final i18n = t.staff.profile;
final cubit = Modular.get<ProfileCubit>();
return BlocProvider( // Load profile data on first build
create: (_) { // TODO: Get actual userId from auth session
// TODO: Get actual userId from auth session // For now, using mock userId that matches ProfileRepositoryMock data
// For now, using mock userId that matches ProfileRepositoryMock data const userId = 't8P3fYh4y1cPoZbbVPXUhfQCsDo3';
const userId = 't8P3fYh4y1cPoZbbVPXUhfQCsDo3'; if (cubit.state.status == ProfileStatus.initial) {
return Modular.get<ProfileCubit>()..loadProfile(userId); cubit.loadProfile(userId);
}, }
child: Scaffold(
backgroundColor: UiColors.background,
body: BlocBuilder<ProfileCubit, ProfileState>(
builder: (context, state) {
if (state.status == ProfileStatus.loading) {
return const Center(child: CircularProgressIndicator());
}
if (state.status == ProfileStatus.error) { return Scaffold(
return Center( backgroundColor: UiColors.background,
child: Text( body: BlocBuilder<ProfileCubit, ProfileState>(
state.errorMessage ?? 'An error occurred', bloc: cubit,
style: UiTypography.body1r.copyWith( builder: (context, state) {
color: UiColors.destructive, if (state.status == ProfileStatus.loading) {
), return const Center(child: CircularProgressIndicator());
}
if (state.status == ProfileStatus.error) {
return Center(
child: Text(
state.errorMessage ?? 'An error occurred',
style: UiTypography.body1r.copyWith(
color: UiColors.destructive,
), ),
);
}
final profile = state.profile;
if (profile == null) {
return const Center(child: CircularProgressIndicator());
}
return SingleChildScrollView(
padding: const EdgeInsets.only(bottom: UiConstants.space16),
child: Column(
children: [
ProfileHeader(
fullName: profile.fullName,
level: profile.level,
photoUrl: profile.photoUrl,
onSignOutTap: () {
context.read<ProfileCubit>().signOut();
Modular.to.navigateToGetStarted();
},
),
Transform.translate(
offset: const Offset(0, -24),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space5,
),
child: Column(
children: [
ReliabilityStatsCard(
totalShifts: profile.totalShifts,
averageRating: profile.averageRating,
onTimeRate: profile.onTimeRate,
noShowCount: profile.noShowCount,
cancellationCount: profile.cancellationCount,
),
const SizedBox(height: UiConstants.space6),
ReliabilityScoreBar(
reliabilityScore: profile.reliabilityScore,
),
const SizedBox(height: UiConstants.space6),
SectionTitle(i18n.sections.onboarding),
ProfileMenuGrid(
children: [
ProfileMenuItem(
icon: UiIcons.user,
label: i18n.menu_items.personal_info,
completed: profile.hasPersonalInfo,
onTap: () => Modular.to.pushPersonalInfo(),
),
ProfileMenuItem(
icon: UiIcons.phone,
label: i18n.menu_items.emergency_contact,
completed: profile.hasEmergencyContact,
onTap: () => Modular.to.pushEmergencyContact(),
),
ProfileMenuItem(
icon: UiIcons.briefcase,
label: i18n.menu_items.experience,
completed: profile.hasExperience,
onTap: () => Modular.to.pushExperience(),
),
ProfileMenuItem(
icon: UiIcons.user,
label: i18n.menu_items.attire,
completed: profile.hasAttire,
onTap: () => Modular.to.pushAttire(),
),
],
),
const SizedBox(height: UiConstants.space6),
SectionTitle(i18n.sections.compliance),
ProfileMenuGrid(
crossAxisCount: 3,
children: [
ProfileMenuItem(
icon: UiIcons.file,
label: i18n.menu_items.documents,
completed: profile.hasDocuments,
onTap: () => Modular.to.pushDocuments(),
),
ProfileMenuItem(
icon: UiIcons.shield,
label: i18n.menu_items.certificates,
completed: profile.hasCertificates,
onTap: () => Modular.to.pushCertificates(),
),
ProfileMenuItem(
icon: UiIcons.file,
label: i18n.menu_items.tax_forms,
completed: profile.hasTaxForms,
onTap: () => Modular.to.pushTaxForms(),
),
],
),
const SizedBox(height: UiConstants.space6),
SectionTitle(i18n.sections.level_up),
ProfileMenuGrid(
crossAxisCount: 3,
children: [
ProfileMenuItem(
icon: UiIcons.sparkles,
label: i18n.menu_items.krow_university,
onTap: () => Modular.to.pushKrowUniversity(),
),
ProfileMenuItem(
icon: UiIcons.briefcase,
label: i18n.menu_items.trainings,
onTap: () => Modular.to.pushTrainings(),
),
ProfileMenuItem(
icon: UiIcons.target,
label: i18n.menu_items.leaderboard,
onTap: () => Modular.to.pushLeaderboard(),
),
],
),
const SizedBox(height: UiConstants.space6),
SectionTitle(i18n.sections.finance),
ProfileMenuGrid(
crossAxisCount: 3,
children: [
ProfileMenuItem(
icon: UiIcons.building,
label: i18n.menu_items.bank_account,
onTap: () => Modular.to.pushBankAccount(),
),
ProfileMenuItem(
icon: UiIcons.creditCard,
label: i18n.menu_items.payments,
onTap: () => Modular.to.navigate('/payments'),
),
ProfileMenuItem(
icon: UiIcons.clock,
label: i18n.menu_items.timecard,
onTap: () => Modular.to.pushTimecard(),
),
],
),
const SizedBox(height: UiConstants.space6),
SectionTitle(i18n.sections.support),
ProfileMenuGrid(
crossAxisCount: 3,
children: [
ProfileMenuItem(
icon: UiIcons.help,
label: i18n.menu_items.faqs,
onTap: () => Modular.to.pushFaqs(),
),
ProfileMenuItem(
icon: UiIcons.shield,
label: i18n.menu_items.privacy_security,
onTap: () => Modular.to.pushPrivacy(),
),
ProfileMenuItem(
icon: UiIcons.messageCircle,
label: i18n.menu_items.messages,
onTap: () => Modular.to.pushMessages(),
),
],
),
const SizedBox(height: UiConstants.space6),
LogoutButton(
onTap: () {
context.read<ProfileCubit>().signOut();
Modular.to.navigateToGetStarted();
},
),
],
),
),
),
],
), ),
); );
}, }
),
final profile = state.profile;
if (profile == null) {
return const Center(child: CircularProgressIndicator());
}
return SingleChildScrollView(
padding: const EdgeInsets.only(bottom: UiConstants.space16),
child: Column(
children: [
ProfileHeader(
fullName: profile.fullName,
level: profile.level,
photoUrl: profile.photoUrl,
onSignOutTap: () {
context.read<ProfileCubit>().signOut();
Modular.to.navigateToGetStarted();
},
),
Transform.translate(
offset: const Offset(0, -24),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space5,
),
child: Column(
children: [
ReliabilityStatsCard(
totalShifts: profile.totalShifts,
averageRating: profile.averageRating,
onTimeRate: profile.onTimeRate,
noShowCount: profile.noShowCount,
cancellationCount: profile.cancellationCount,
),
const SizedBox(height: UiConstants.space6),
ReliabilityScoreBar(
reliabilityScore: profile.reliabilityScore,
),
const SizedBox(height: UiConstants.space6),
SectionTitle(i18n.sections.onboarding),
ProfileMenuGrid(
children: [
ProfileMenuItem(
icon: UiIcons.user,
label: i18n.menu_items.personal_info,
completed: profile.hasPersonalInfo,
onTap: () => Modular.to.pushPersonalInfo(),
),
ProfileMenuItem(
icon: UiIcons.phone,
label: i18n.menu_items.emergency_contact,
completed: profile.hasEmergencyContact,
onTap: () => Modular.to.pushEmergencyContact(),
),
ProfileMenuItem(
icon: UiIcons.briefcase,
label: i18n.menu_items.experience,
completed: profile.hasExperience,
onTap: () => Modular.to.pushExperience(),
),
ProfileMenuItem(
icon: UiIcons.user,
label: i18n.menu_items.attire,
completed: profile.hasAttire,
onTap: () => Modular.to.pushAttire(),
),
],
),
const SizedBox(height: UiConstants.space6),
SectionTitle(i18n.sections.compliance),
ProfileMenuGrid(
crossAxisCount: 3,
children: [
ProfileMenuItem(
icon: UiIcons.file,
label: i18n.menu_items.documents,
completed: profile.hasDocuments,
onTap: () => Modular.to.pushDocuments(),
),
ProfileMenuItem(
icon: UiIcons.shield,
label: i18n.menu_items.certificates,
completed: profile.hasCertificates,
onTap: () => Modular.to.pushCertificates(),
),
ProfileMenuItem(
icon: UiIcons.file,
label: i18n.menu_items.tax_forms,
completed: profile.hasTaxForms,
onTap: () => Modular.to.pushTaxForms(),
),
],
),
const SizedBox(height: UiConstants.space6),
SectionTitle(i18n.sections.level_up),
ProfileMenuGrid(
crossAxisCount: 3,
children: [
ProfileMenuItem(
icon: UiIcons.sparkles,
label: i18n.menu_items.krow_university,
onTap: () => Modular.to.pushKrowUniversity(),
),
ProfileMenuItem(
icon: UiIcons.briefcase,
label: i18n.menu_items.trainings,
onTap: () => Modular.to.pushTrainings(),
),
ProfileMenuItem(
icon: UiIcons.target,
label: i18n.menu_items.leaderboard,
onTap: () => Modular.to.pushLeaderboard(),
),
],
),
const SizedBox(height: UiConstants.space6),
SectionTitle(i18n.sections.finance),
ProfileMenuGrid(
crossAxisCount: 3,
children: [
ProfileMenuItem(
icon: UiIcons.building,
label: i18n.menu_items.bank_account,
onTap: () => Modular.to.pushBankAccount(),
),
ProfileMenuItem(
icon: UiIcons.creditCard,
label: i18n.menu_items.payments,
onTap: () => Modular.to.navigate('/payments'),
),
ProfileMenuItem(
icon: UiIcons.clock,
label: i18n.menu_items.timecard,
onTap: () => Modular.to.pushTimecard(),
),
],
),
const SizedBox(height: UiConstants.space6),
SectionTitle(i18n.sections.support),
ProfileMenuGrid(
crossAxisCount: 3,
children: [
ProfileMenuItem(
icon: UiIcons.help,
label: i18n.menu_items.faqs,
onTap: () => Modular.to.pushFaqs(),
),
ProfileMenuItem(
icon: UiIcons.shield,
label: i18n.menu_items.privacy_security,
onTap: () => Modular.to.pushPrivacy(),
),
ProfileMenuItem(
icon: UiIcons.messageCircle,
label: i18n.menu_items.messages,
onTap: () => Modular.to.pushMessages(),
),
],
),
const SizedBox(height: UiConstants.space6),
LogoutButton(
onTap: () {
context.read<ProfileCubit>().signOut();
Modular.to.navigateToGetStarted();
},
),
],
),
),
),
],
),
);
},
), ),
); );
} }

View File

@@ -39,7 +39,8 @@ class StaffProfileModule extends Module {
); );
// Presentation layer - Cubit depends on use cases // Presentation layer - Cubit depends on use cases
i.addSingleton( // Use addLazySingleton to create a new instance per module lifecycle
i.addLazySingleton(
() => ProfileCubit( () => ProfileCubit(
i.get<GetProfileUseCase>(), i.get<GetProfileUseCase>(),
i.get<SignOutUseCase>(), i.get<SignOutUseCase>(),

View File

@@ -1,41 +1,40 @@
name: staff_profile name: staff_profile
description: Staff Profile feature package. description: Staff Profile feature package.
version: 0.0.1 version: 0.0.1
publish_to: 'none' publish_to: none
resolution: workspace
environment: environment:
sdk: '>=3.0.0 <4.0.0' sdk: '>=3.10.0 <4.0.0'
flutter: ">=1.17.0" flutter: ">=3.0.0"
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
flutter_bloc: ^8.1.0
bloc: ^8.1.0
flutter_modular: ^6.3.0
equatable: ^2.0.5
lucide_icons: ^0.257.0
# Architecture # Architecture Packages
flutter_modular: ^5.0.0 design_system:
flutter_bloc: ^8.1.3 path: ../../../design_system
# Utility/DI
injectable: ^2.3.0
get_it: ^7.6.4
# Project-specific packages
domain:
path: ../../../domain
data_connect:
path: ../../../data_connect
core_localization: core_localization:
path: ../../../core_localization path: ../../../core_localization
design_system: krow_core:
path: ../../../design_system # Assuming this path path: ../../../core
krow_domain:
path: ../../../domain
krow_data_connect:
path: ../../../data_connect
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_lints: ^2.0.0 bloc_test: ^9.1.0
injectable_generator: ^2.4.1 mocktail: ^1.0.0
build_runner: ^2.4.6 flutter_lints: ^6.0.0
# Flutter modular configuration
flutter: flutter:
uses-material-design: true uses-material-design: true

View File

@@ -1,6 +1,7 @@
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:staff_home/staff_home.dart'; import 'package:staff_home/staff_home.dart';
import 'package:staff_profile/staff_profile.dart';
import 'package:staff_main/src/presentation/blocs/staff_main_cubit.dart'; import 'package:staff_main/src/presentation/blocs/staff_main_cubit.dart';
import 'package:staff_main/src/presentation/constants/staff_main_routes.dart'; import 'package:staff_main/src/presentation/constants/staff_main_routes.dart';
@@ -38,10 +39,9 @@ class StaffMainModule extends Module {
child: (BuildContext context) => child: (BuildContext context) =>
const PlaceholderPage(title: 'Clock In'), const PlaceholderPage(title: 'Clock In'),
), ),
ChildRoute<dynamic>( ModuleRoute<dynamic>(
StaffMainRoutes.profile, StaffMainRoutes.profile,
child: (BuildContext context) => module: StaffProfileModule(),
const PlaceholderPage(title: 'Profile'),
), ),
], ],
); );

View File

@@ -25,12 +25,12 @@ dependencies:
# Features # Features
staff_home: staff_home:
path: ../home path: ../home
staff_profile:
path: ../profile
# staff_shifts: # staff_shifts:
# path: ../shifts # path: ../shifts
# staff_payments: # staff_payments:
# path: ../payments # path: ../payments
# staff_profile:
# path: ../profile
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@@ -1064,6 +1064,13 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.12.1" version: "1.12.1"
staff_profile:
dependency: transitive
description:
path: "packages/features/staff/profile"
relative: true
source: path
version: "0.0.1"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description: