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 8ffeefc3..23dbc84c 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 @@ -9,7 +9,7 @@ import 'package:krow_domain/krow_domain.dart'; import '../blocs/profile_cubit.dart'; import '../blocs/profile_state.dart'; import '../widgets/logout_button.dart'; -import '../widgets/profile_header.dart'; +import '../widgets/header/profile_header.dart'; import '../widgets/reliability_score_bar.dart'; import '../widgets/reliability_stats_card.dart'; import '../widgets/sections/index.dart'; @@ -25,19 +25,6 @@ class StaffProfilePage extends StatelessWidget { /// Creates a [StaffProfilePage]. const StaffProfilePage({super.key}); - String _mapStatusToLevel(StaffStatus status) { - switch (status) { - case StaffStatus.active: - case StaffStatus.verified: - return 'Krower I'; - case StaffStatus.pending: - case StaffStatus.completedProfile: - return 'Pending'; - default: - return 'New'; - } - } - @override Widget build(BuildContext context) { final ProfileCubit cubit = Modular.get(); @@ -106,7 +93,6 @@ class StaffProfilePage extends StatelessWidget { children: [ ProfileHeader( fullName: profile.name, - level: _mapStatusToLevel(profile.status), photoUrl: profile.avatar, ), Transform.translate( diff --git a/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/header/profile_header.dart b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/header/profile_header.dart new file mode 100644 index 00000000..33eead3a --- /dev/null +++ b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/header/profile_header.dart @@ -0,0 +1,116 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; + +import 'profile_level_badge.dart'; + +/// 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 { + /// Creates a [ProfileHeader]. + const ProfileHeader({ + super.key, + required this.fullName, + this.photoUrl, + }); + + /// The staff member's full name + final String fullName; + + /// Optional photo URL for the avatar + final String? photoUrl; + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB( + UiConstants.space5, + UiConstants.space5, + UiConstants.space5, + UiConstants.space16, + ), + decoration: const BoxDecoration( + color: UiColors.primary, + borderRadius: BorderRadius.vertical( + bottom: Radius.circular(UiConstants.space6), + ), + ), + child: SafeArea( + bottom: false, + child: Column( + children: [ + // Avatar Section + Container( + width: 112, + height: 112, + padding: const EdgeInsets.all(UiConstants.space1), + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + UiColors.accent, + UiColors.accent.withValues(alpha: 0.5), + UiColors.primaryForeground, + ], + ), + boxShadow: [ + BoxShadow( + color: UiColors.foreground.withValues(alpha: 0.2), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + ), + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: UiColors.primaryForeground.withValues(alpha: 0.2), + width: 4, + ), + ), + child: CircleAvatar( + backgroundColor: UiColors.background, + backgroundImage: photoUrl != null + ? NetworkImage(photoUrl!) + : null, + child: photoUrl == null + ? Container( + width: double.infinity, + height: double.infinity, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + UiColors.accent, + UiColors.accent.withValues(alpha: 0.7), + ], + ), + ), + alignment: Alignment.center, + child: Text( + fullName.isNotEmpty + ? fullName[0].toUpperCase() + : 'K', + style: UiTypography.displayM.primary, + ), + ) + : null, + ), + ), + ), + const SizedBox(height: UiConstants.space4), + Text(fullName, style: UiTypography.headline2m.white), + const SizedBox(height: UiConstants.space1), + const ProfileLevelBadge(), + ], + ), + ), + ); + } +} diff --git a/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/header/profile_level_badge.dart b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/header/profile_level_badge.dart new file mode 100644 index 00000000..3661e192 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/header/profile_level_badge.dart @@ -0,0 +1,56 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:krow_domain/krow_domain.dart'; + +import '../../blocs/profile_cubit.dart'; +import '../../blocs/profile_state.dart'; + +/// A widget that displays the staff member's level badge. +/// +/// The level is calculated based on the staff status from ProfileCubit and displayed +/// in a styled container with the design system tokens. +class ProfileLevelBadge extends StatelessWidget { + /// Creates a [ProfileLevelBadge]. + const ProfileLevelBadge({super.key}); + + /// Maps staff status to a user-friendly level string. + String _mapStatusToLevel(StaffStatus status) { + switch (status) { + case StaffStatus.active: + case StaffStatus.verified: + return 'Krower I'; + case StaffStatus.pending: + case StaffStatus.completedProfile: + return 'Pending'; + default: + return 'New'; + } + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (BuildContext context, ProfileState state) { + final Staff? profile = state.profile; + if (profile == null) { + return const SizedBox.shrink(); + } + + final String level = _mapStatusToLevel(profile.status); + + return Container( + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space3, + vertical: UiConstants.space1, + ), + decoration: BoxDecoration( + color: UiColors.accent.withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(UiConstants.space5), + ), + child: Text(level, style: UiTypography.footnote1b.accent), + ); + }, + ); + } +} diff --git a/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/profile_header.dart b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/profile_header.dart deleted file mode 100644 index 04991ba1..00000000 --- a/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/profile_header.dart +++ /dev/null @@ -1,173 +0,0 @@ -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, and level. -/// -/// Uses design system tokens for all colors, typography, and spacing. -class ProfileHeader extends StatelessWidget { - /// The staff member's full name - final String fullName; - - /// The staff member's level (e.g., "Krower I") - final String level; - - /// Optional photo URL for the avatar - final String? photoUrl; - - /// Creates a [ProfileHeader]. - const ProfileHeader({ - super.key, - required this.fullName, - required this.level, - this.photoUrl, - }); - - @override - Widget build(BuildContext context) { - final TranslationsStaffProfileHeaderEn i18n = t.staff.profile.header; - - return Container( - width: double.infinity, - padding: const EdgeInsets.fromLTRB( - UiConstants.space5, - UiConstants.space5, - UiConstants.space5, - UiConstants.space16, - ), - decoration: const BoxDecoration( - color: UiColors.primary, - borderRadius: BorderRadius.vertical( - bottom: Radius.circular(UiConstants.space6), - ), - ), - child: SafeArea( - bottom: false, - child: Column( - children: [ - // Top Bar - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - i18n.title, - style: UiTypography.headline4m.textSecondary, - ), - ], - ), - const SizedBox(height: UiConstants.space8), - // Avatar Section - Stack( - alignment: Alignment.bottomRight, - children: [ - Container( - width: 112, - height: 112, - padding: const EdgeInsets.all(UiConstants.space1), - decoration: BoxDecoration( - shape: BoxShape.circle, - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - UiColors.accent, - UiColors.accent.withValues(alpha: 0.5), - UiColors.primaryForeground, - ], - ), - boxShadow: [ - BoxShadow( - color: UiColors.foreground.withValues(alpha: 0.2), - blurRadius: 10, - offset: const Offset(0, 4), - ), - ], - ), - child: Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - color: UiColors.primaryForeground.withValues(alpha: 0.2), - width: 4, - ), - ), - child: CircleAvatar( - backgroundColor: UiColors.background, - backgroundImage: photoUrl != null - ? NetworkImage(photoUrl!) - : null, - child: photoUrl == null - ? Container( - width: double.infinity, - height: double.infinity, - decoration: BoxDecoration( - shape: BoxShape.circle, - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - UiColors.accent, - UiColors.accent.withValues(alpha: 0.7), - ], - ), - ), - alignment: Alignment.center, - child: Text( - fullName.isNotEmpty - ? fullName[0].toUpperCase() - : 'K', - style: UiTypography.displayM.primary, - ), - ) - : null, - ), - ), - ), - Container( - width: 36, - height: 36, - decoration: BoxDecoration( - color: UiColors.primaryForeground, - shape: BoxShape.circle, - border: Border.all(color: UiColors.primary, width: 2), - boxShadow: [ - BoxShadow( - color: UiColors.foreground.withValues(alpha: 0.1), - blurRadius: 4, - ), - ], - ), - child: const Icon( - UiIcons.camera, - size: 16, - color: UiColors.primary, - ), - ), - ], - ), - const SizedBox(height: UiConstants.space4), - Text( - fullName, - style: UiTypography.headline3m.textPlaceholder, - ), - const SizedBox(height: UiConstants.space1), - Container( - padding: const EdgeInsets.symmetric( - horizontal: UiConstants.space3, - vertical: UiConstants.space1, - ), - decoration: BoxDecoration( - color: UiColors.accent.withValues(alpha: 0.2), - borderRadius: BorderRadius.circular(UiConstants.space5), - ), - child: Text( - level, - style: UiTypography.footnote1b.accent, - ), - ), - ], - ), - ), - ); - } -}