feat: Refactor staff profile page and logout button for improved state management and navigation

This commit is contained in:
Achintha Isuru
2026-02-19 13:39:03 -05:00
parent 55344fad90
commit 7b9507b87f
4 changed files with 161 additions and 153 deletions

View File

@@ -38,24 +38,12 @@ class StaffProfilePage extends StatelessWidget {
}
}
void _onSignOut(ProfileCubit cubit, ProfileState state) {
if (state.status != ProfileStatus.loading) {
cubit.signOut();
}
}
@override
Widget build(BuildContext context) {
final ProfileCubit cubit = Modular.get<ProfileCubit>();
// Load profile data on first build
if (cubit.state.status == ProfileStatus.initial) {
cubit.loadProfile();
}
return Scaffold(
body: BlocConsumer<ProfileCubit, ProfileState>(
bloc: cubit,
body: BlocProvider<ProfileCubit>(
create: (_) => Modular.get<ProfileCubit>()..loadProfile(),
child: BlocConsumer<ProfileCubit, ProfileState>(
listener: (BuildContext context, ProfileState state) {
if (state.status == ProfileStatus.signedOut) {
Modular.to.toGetStartedPage();
@@ -104,7 +92,6 @@ class StaffProfilePage extends StatelessWidget {
fullName: profile.name,
level: _mapStatusToLevel(profile.status),
photoUrl: profile.avatar,
onSignOutTap: () => _onSignOut(cubit, state),
),
Transform.translate(
offset: const Offset(0, -UiConstants.space6),
@@ -113,7 +100,9 @@ class StaffProfilePage extends StatelessWidget {
horizontal: UiConstants.space5,
),
child: Column(
spacing: UiConstants.space6,
children: <Widget>[
// Reliability Stats and Score
ReliabilityStatsCard(
totalShifts: profile.totalShifts,
averageRating: profile.averageRating,
@@ -121,25 +110,31 @@ class StaffProfilePage extends StatelessWidget {
noShowCount: profile.noShowCount,
cancellationCount: profile.cancellationCount,
),
const SizedBox(height: UiConstants.space6),
// Reliability Score Bar
ReliabilityScoreBar(
reliabilityScore: profile.reliabilityScore,
),
const SizedBox(height: UiConstants.space6),
// Ordered sections
const OnboardingSection(),
const SizedBox(height: UiConstants.space6),
// Compliance section
const ComplianceSection(),
const SizedBox(height: UiConstants.space6),
// Finance section
const FinanceSection(),
const SizedBox(height: UiConstants.space6),
// Support section
const SupportSection(),
const SizedBox(height: UiConstants.space6),
// Settings section
const SettingsSection(),
// Logout button at the bottom
const LogoutButton(),
const SizedBox(height: UiConstants.space6),
LogoutButton(
onTap: () => _onSignOut(cubit, state),
),
const SizedBox(height: UiConstants.space12),
],
),
),
@@ -149,6 +144,7 @@ class StaffProfilePage extends StatelessWidget {
);
},
),
),
);
}
}

View File

@@ -1,20 +1,40 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../blocs/profile_cubit.dart';
import '../blocs/profile_state.dart';
/// The sign-out button widget.
///
/// Uses design system tokens for all colors, typography, spacing, and icons.
/// Handles logout logic when tapped and navigates to onboarding on success.
class LogoutButton extends StatelessWidget {
final VoidCallback onTap;
const LogoutButton({super.key});
const LogoutButton({super.key, required this.onTap});
/// Handles the sign-out action.
///
/// Checks if the profile is not currently loading, then triggers the
/// sign-out process via the ProfileCubit.
void _handleSignOut(BuildContext context, ProfileState state) {
if (state.status != ProfileStatus.loading) {
context.read<ProfileCubit>().signOut();
}
}
@override
Widget build(BuildContext context) {
final i18n = t.staff.profile.header;
final TranslationsStaffProfileHeaderEn i18n = t.staff.profile.header;
return Container(
return BlocListener<ProfileCubit, ProfileState>(
listener: (BuildContext context, ProfileState state) {
if (state.status == ProfileStatus.signedOut) {
// Navigate to get started page after successful sign-out
// This will be handled by the profile page listener
}
},
child: Container(
width: double.infinity,
decoration: BoxDecoration(
color: UiColors.bgPopup,
@@ -24,13 +44,18 @@ class LogoutButton extends StatelessWidget {
child: Material(
color: UiColors.transparent,
child: InkWell(
onTap: onTap,
onTap: () {
_handleSignOut(
context,
context.read<ProfileCubit>().state,
);
},
borderRadius: UiConstants.radiusLg,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: UiConstants.space4),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
children: <Widget>[
const Icon(
UiIcons.logOut,
color: UiColors.destructive,
@@ -46,6 +71,7 @@ class LogoutButton extends StatelessWidget {
),
),
),
),
);
}
}

View File

@@ -2,8 +2,7 @@ import 'package:flutter/material.dart';
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
/// The header section of the staff profile page, containing avatar, name, level,
/// and a sign-out button.
/// The header section of the staff profile page, containing avatar, name, and level.
///
/// Uses design system tokens for all colors, typography, and spacing.
class ProfileHeader extends StatelessWidget {
@@ -16,21 +15,17 @@ class ProfileHeader extends StatelessWidget {
/// Optional photo URL for the avatar
final String? photoUrl;
/// Callback when sign out is tapped
final VoidCallback onSignOutTap;
/// Creates a [ProfileHeader].
const ProfileHeader({
super.key,
required this.fullName,
required this.level,
this.photoUrl,
required this.onSignOutTap,
});
@override
Widget build(BuildContext context) {
final i18n = t.staff.profile.header;
final TranslationsStaffProfileHeaderEn i18n = t.staff.profile.header;
return Container(
width: double.infinity,
@@ -49,31 +44,22 @@ class ProfileHeader extends StatelessWidget {
child: SafeArea(
bottom: false,
child: Column(
children: [
children: <Widget>[
// Top Bar
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text(
i18n.title,
style: UiTypography.headline4m.textSecondary,
),
GestureDetector(
onTap: onSignOutTap,
child: Text(
i18n.sign_out,
style: UiTypography.body2m.copyWith(
color: UiColors.primaryForeground.withValues(alpha: 0.8),
),
),
),
],
),
const SizedBox(height: UiConstants.space8),
// Avatar Section
Stack(
alignment: Alignment.bottomRight,
children: [
children: <Widget>[
Container(
width: 112,
height: 112,
@@ -83,13 +69,13 @@ class ProfileHeader extends StatelessWidget {
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
colors: <Color>[
UiColors.accent,
UiColors.accent.withValues(alpha: 0.5),
UiColors.primaryForeground,
],
),
boxShadow: [
boxShadow: <BoxShadow>[
BoxShadow(
color: UiColors.foreground.withValues(alpha: 0.2),
blurRadius: 10,
@@ -119,7 +105,7 @@ class ProfileHeader extends StatelessWidget {
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
colors: <Color>[
UiColors.accent,
UiColors.accent.withValues(alpha: 0.7),
],
@@ -144,7 +130,7 @@ class ProfileHeader extends StatelessWidget {
color: UiColors.primaryForeground,
shape: BoxShape.circle,
border: Border.all(color: UiColors.primary, width: 2),
boxShadow: [
boxShadow: <BoxShadow>[
BoxShadow(
color: UiColors.foreground.withValues(alpha: 0.1),
blurRadius: 4,

View File

@@ -68,7 +68,7 @@ graph TD
- `data/`: Repository Implementations.
- `presentation/`:
- Pages, BLoCs, Widgets.
- For performance make the pages as `StatelessWidget` and move the state management to the BLoC or `StatefulWidget` to an external separate widget file.
- For performance make the pages as `StatelessWidget` and move the state management to the BLoC (always use a BlocProvider when providing the BLoC to the widget tree) or `StatefulWidget` to an external separate widget file.
- **Responsibilities**:
- **Presentation**: UI Pages, Modular Routes.
- **State Management**: BLoCs / Cubits.