diff --git a/README.md b/README.md index 04116b6a..597a8a4b 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,11 @@ This project uses a modular `Makefile` for all common tasks. - **[03-contributing.md](./docs/03-contributing.md)**: Guidelines for new developers and setup checklist. - **[04-sync-prototypes.md](./docs/04-sync-prototypes.md)**: How to sync prototypes for local dev and AI context. +### Mobile Development Documentation +- **[MOBILE/01-architecture-principles.md](./docs/MOBILE/01-architecture-principles.md)**: Flutter clean architecture, package roles, and dependency flow. +- **[MOBILE/02-design-system-usage.md](./docs/MOBILE/02-design-system-usage.md)**: Design system components and theming guidelines. +- **[MOBILE/00-agent-development-rules.md](./docs/MOBILE/00-agent-development-rules.md)**: Rules and best practices for mobile development. + ## 🤝 Contributing New to the team? Please read our **[Contributing Guide](./docs/03-contributing.md)** to get your environment set up and understand our workflow. diff --git a/apps/mobile/packages/core/lib/src/routing/staff/route_paths.dart b/apps/mobile/packages/core/lib/src/routing/staff/route_paths.dart index e7cc0c09..97badf3c 100644 --- a/apps/mobile/packages/core/lib/src/routing/staff/route_paths.dart +++ b/apps/mobile/packages/core/lib/src/routing/staff/route_paths.dart @@ -203,7 +203,9 @@ class StaffPaths { static const String leaderboard = '/leaderboard'; /// FAQs - frequently asked questions. - static const String faqs = '/faqs'; + /// + /// Access to frequently asked questions about the staff application. + static const String faqs = '/worker-main/faqs/'; // ========================================================================== // PRIVACY & SECURITY diff --git a/apps/mobile/packages/core_localization/lib/src/l10n/en.i18n.json b/apps/mobile/packages/core_localization/lib/src/l10n/en.i18n.json index 9fb4251f..0ba97bd4 100644 --- a/apps/mobile/packages/core_localization/lib/src/l10n/en.i18n.json +++ b/apps/mobile/packages/core_localization/lib/src/l10n/en.i18n.json @@ -521,7 +521,8 @@ "compliance": "COMPLIANCE", "level_up": "LEVEL UP", "finance": "FINANCE", - "support": "SUPPORT" + "support": "SUPPORT", + "settings": "SETTINGS" }, "menu_items": { "personal_info": "Personal Info", @@ -543,7 +544,8 @@ "timecard": "Timecard", "faqs": "FAQs", "privacy_security": "Privacy & Security", - "messages": "Messages" + "messages": "Messages", + "language": "Language" }, "bank_account_page": { "title": "Bank Account", @@ -1143,6 +1145,12 @@ "profile_visibility_updated": "Profile visibility updated successfully!" } }, + "staff_faqs": { + "title": "FAQs", + "search_placeholder": "Search questions...", + "no_results": "No matching questions found", + "contact_support": "Contact Support" + }, "success": { "hub": { "created": "Hub created successfully!", diff --git a/apps/mobile/packages/core_localization/lib/src/l10n/es.i18n.json b/apps/mobile/packages/core_localization/lib/src/l10n/es.i18n.json index 77370c8e..6ce171fc 100644 --- a/apps/mobile/packages/core_localization/lib/src/l10n/es.i18n.json +++ b/apps/mobile/packages/core_localization/lib/src/l10n/es.i18n.json @@ -521,7 +521,8 @@ "compliance": "CUMPLIMIENTO", "level_up": "MEJORAR NIVEL", "finance": "FINANZAS", - "support": "SOPORTE" + "support": "SOPORTE", + "settings": "AJUSTES" }, "menu_items": { "personal_info": "Información Personal", @@ -543,7 +544,8 @@ "timecard": "Tarjeta de Tiempo", "faqs": "Preguntas Frecuentes", "privacy_security": "Privacidad y Seguridad", - "messages": "Mensajes" + "messages": "Mensajes", + "language": "Idioma" }, "bank_account_page": { "title": "Cuenta Bancaria", @@ -1143,6 +1145,12 @@ "profile_visibility_updated": "¡Visibilidad del perfil actualizada exitosamente!" } }, + "staff_faqs": { + "title": "Preguntas Frecuentes", + "search_placeholder": "Buscar preguntas...", + "no_results": "No se encontraron preguntas coincidentes", + "contact_support": "Contactar Soporte" + }, "success": { "hub": { "created": "¡Hub creado exitosamente!", diff --git a/apps/mobile/packages/design_system/lib/src/ui_colors.dart b/apps/mobile/packages/design_system/lib/src/ui_colors.dart index 30a56dc3..5bb0a5af 100644 --- a/apps/mobile/packages/design_system/lib/src/ui_colors.dart +++ b/apps/mobile/packages/design_system/lib/src/ui_colors.dart @@ -21,8 +21,8 @@ class UiColors { /// Foreground color on primary background (#F7FAFC) static const Color primaryForeground = Color(0xFFF7FAFC); - /// Inverse primary color (#9FABF1) - static const Color primaryInverse = Color(0xFF9FABF1); + /// Inverse primary color (#0A39DF) + static const Color primaryInverse = Color.fromARGB(23, 10, 56, 223); /// Secondary background color (#F1F3F5) static const Color secondary = Color(0xFFF1F3F5); diff --git a/apps/mobile/packages/design_system/lib/src/ui_icons.dart b/apps/mobile/packages/design_system/lib/src/ui_icons.dart index cd813769..cf446f0a 100644 --- a/apps/mobile/packages/design_system/lib/src/ui_icons.dart +++ b/apps/mobile/packages/design_system/lib/src/ui_icons.dart @@ -264,4 +264,7 @@ class UiIcons { /// Chef hat icon for attire static const IconData chefHat = _IconLib.chefHat; + + /// Help circle icon for FAQs + static const IconData helpCircle = _IconLib.helpCircle; } diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/pages/worker_home_page.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/pages/worker_home_page.dart index 7de014d6..906d45f1 100644 --- a/apps/mobile/packages/features/staff/home/lib/src/presentation/pages/worker_home_page.dart +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/pages/worker_home_page.dart @@ -49,13 +49,17 @@ class WorkerHomePage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ BlocBuilder( - buildWhen: (previous, current) => previous.staffName != current.staffName, + buildWhen: (previous, current) => + previous.staffName != current.staffName, builder: (context, state) { return HomeHeader(userName: state.staffName); }, ), Padding( - padding: const EdgeInsets.symmetric(horizontal: UiConstants.space4), + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space4, + vertical: UiConstants.space4, + ), child: Column( children: [ BlocBuilder( @@ -67,7 +71,7 @@ class WorkerHomePage extends StatelessWidget { return PlaceholderBanner( title: bannersI18n.complete_profile_title, subtitle: bannersI18n.complete_profile_subtitle, - bg: UiColors.bgHighlight, + bg: UiColors.primaryInverse, accent: UiColors.primary, onTap: () { Modular.to.toProfile(); @@ -135,7 +139,8 @@ class WorkerHomePage extends StatelessWidget { EmptyStateWidget( message: emptyI18n.no_shifts_today, actionLink: emptyI18n.find_shifts_cta, - onAction: () => Modular.to.toShifts(initialTab: 'find'), + onAction: () => + Modular.to.toShifts(initialTab: 'find'), ) else Column( @@ -183,9 +188,7 @@ class WorkerHomePage extends StatelessWidget { const SizedBox(height: UiConstants.space6), // Recommended Shifts - SectionHeader( - title: sectionsI18n.recommended_for_you, - ), + SectionHeader(title: sectionsI18n.recommended_for_you), BlocBuilder( builder: (context, state) { if (state.recommendedShifts.isEmpty) { @@ -201,7 +204,8 @@ class WorkerHomePage extends StatelessWidget { clipBehavior: Clip.none, itemBuilder: (context, index) => Padding( padding: const EdgeInsets.only( - right: UiConstants.space3), + right: UiConstants.space3, + ), child: RecommendedShiftCard( shift: state.recommendedShifts[index], ), diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/placeholder_banner.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/placeholder_banner.dart index 1d648bc4..af821f42 100644 --- a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/placeholder_banner.dart +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/placeholder_banner.dart @@ -1,24 +1,33 @@ import 'package:flutter/material.dart'; - import 'package:design_system/design_system.dart'; - /// Banner widget for placeholder actions, using design system tokens. class PlaceholderBanner extends StatelessWidget { /// Banner title final String title; + /// Banner subtitle final String subtitle; + /// Banner background color final Color bg; + /// Banner accent color final Color accent; + /// Optional tap callback final VoidCallback? onTap; /// Creates a [PlaceholderBanner]. - const PlaceholderBanner({super.key, required this.title, required this.subtitle, required this.bg, required this.accent, this.onTap}); + const PlaceholderBanner({ + super.key, + required this.title, + required this.subtitle, + required this.bg, + required this.accent, + this.onTap, + }); @override Widget build(BuildContext context) { @@ -29,7 +38,7 @@ class PlaceholderBanner extends StatelessWidget { decoration: BoxDecoration( color: bg, borderRadius: BorderRadius.circular(UiConstants.radiusBase), - border: Border.all(color: accent.withValues(alpha: 0.3)), + border: Border.all(color: accent, width: 1), ), child: Row( children: [ @@ -41,7 +50,11 @@ class PlaceholderBanner extends StatelessWidget { color: UiColors.bgBanner, shape: BoxShape.circle, ), - child: Icon(UiIcons.sparkles, color: accent, size: UiConstants.space5), + child: Icon( + UiIcons.sparkles, + color: accent, + size: UiConstants.space5, + ), ), const SizedBox(width: UiConstants.space3), Expanded( @@ -50,12 +63,9 @@ class PlaceholderBanner extends StatelessWidget { children: [ Text( title, - style: UiTypography.body1b, - ), - Text( - subtitle, - style: UiTypography.body3r.copyWith(color: UiColors.mutedForeground), + style: UiTypography.body1b.copyWith(color: accent), ), + Text(subtitle, style: UiTypography.body3r.textSecondary), ], ), ), 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 e5569d53..96b98016 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 @@ -8,14 +8,11 @@ import 'package:krow_domain/krow_domain.dart'; import '../blocs/profile_cubit.dart'; import '../blocs/profile_state.dart'; -import '../widgets/language_selector_bottom_sheet.dart'; import '../widgets/logout_button.dart'; import '../widgets/profile_header.dart'; -import '../widgets/profile_menu_grid.dart'; -import '../widgets/profile_menu_item.dart'; import '../widgets/reliability_score_bar.dart'; import '../widgets/reliability_stats_card.dart'; -import '../widgets/section_title.dart'; +import '../widgets/sections/index.dart'; /// The main Staff Profile page. /// @@ -49,7 +46,6 @@ class StaffProfilePage extends StatelessWidget { @override Widget build(BuildContext context) { - final i18n = Translations.of(context).staff.profile; final ProfileCubit cubit = Modular.get(); // Load profile data on first build @@ -60,7 +56,7 @@ class StaffProfilePage extends StatelessWidget { return Scaffold( body: BlocConsumer( bloc: cubit, - listener: (context, state) { + listener: (BuildContext context, ProfileState state) { if (state.status == ProfileStatus.signedOut) { Modular.to.toGetStartedPage(); } else if (state.status == ProfileStatus.error && @@ -72,7 +68,7 @@ class StaffProfilePage extends StatelessWidget { ); } }, - builder: (context, state) { + builder: (BuildContext context, ProfileState state) { // Show loading spinner if status is loading if (state.status == ProfileStatus.loading) { return const Center(child: CircularProgressIndicator()); @@ -95,7 +91,7 @@ class StaffProfilePage extends StatelessWidget { ); } - final profile = state.profile; + final Staff? profile = state.profile; if (profile == null) { return const Center(child: CircularProgressIndicator()); } @@ -103,7 +99,7 @@ class StaffProfilePage extends StatelessWidget { return SingleChildScrollView( padding: const EdgeInsets.only(bottom: UiConstants.space16), child: Column( - children: [ + children: [ ProfileHeader( fullName: profile.name, level: _mapStatusToLevel(profile.status), @@ -117,7 +113,7 @@ class StaffProfilePage extends StatelessWidget { horizontal: UiConstants.space5, ), child: Column( - children: [ + children: [ ReliabilityStatsCard( totalShifts: profile.totalShifts, averageRating: profile.averageRating, @@ -130,100 +126,15 @@ class StaffProfilePage extends StatelessWidget { reliabilityScore: profile.reliabilityScore, ), const SizedBox(height: UiConstants.space6), - SectionTitle(i18n.sections.onboarding), - ProfileMenuGrid( - crossAxisCount: 3, - - children: [ - ProfileMenuItem( - icon: UiIcons.user, - label: i18n.menu_items.personal_info, - onTap: () => Modular.to.toPersonalInfo(), - ), - ProfileMenuItem( - icon: UiIcons.phone, - label: i18n.menu_items.emergency_contact, - onTap: () => Modular.to.toEmergencyContact(), - ), - ProfileMenuItem( - icon: UiIcons.briefcase, - label: i18n.menu_items.experience, - onTap: () => Modular.to.toExperience(), - ), - ], - ), + const OnboardingSection(), const SizedBox(height: UiConstants.space6), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SectionTitle(i18n.sections.compliance), - ProfileMenuGrid( - crossAxisCount: 3, - children: [ - ProfileMenuItem( - icon: UiIcons.file, - label: i18n.menu_items.tax_forms, - onTap: () => Modular.to.toTaxForms(), - ), - ], - ), - ], - ), + const ComplianceSection(), 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.toBankAccount(), - ), - ProfileMenuItem( - icon: UiIcons.creditCard, - label: i18n.menu_items.payments, - onTap: () => Modular.to.toPayments(), - ), - ProfileMenuItem( - icon: UiIcons.clock, - label: i18n.menu_items.timecard, - onTap: () => Modular.to.toTimeCard(), - ), - ], - ), + const FinanceSection(), const SizedBox(height: UiConstants.space6), - SectionTitle( - i18n.header.title.contains("Perfil") ? "Soporte" : "Support", - ), - ProfileMenuGrid( - crossAxisCount: 3, - children: [ - ProfileMenuItem( - icon: UiIcons.shield, - label: i18n.header.title.contains("Perfil") ? "Privacidad" : "Privacy & Security", - onTap: () => Modular.to.toPrivacySecurity(), - ), - ], - ), + const SupportSection(), const SizedBox(height: UiConstants.space6), - SectionTitle( - i18n.header.title.contains("Perfil") ? "Ajustes" : "Settings", - ), - ProfileMenuGrid( - crossAxisCount: 3, - children: [ - ProfileMenuItem( - icon: UiIcons.globe, - label: i18n.header.title.contains("Perfil") ? "Idioma" : "Language", - onTap: () { - showModalBottomSheet( - context: context, - builder: (context) => const LanguageSelectorBottomSheet(), - ); - }, - ), - ], - ), + const SettingsSection(), const SizedBox(height: UiConstants.space6), LogoutButton( onTap: () => _onSignOut(cubit, state), diff --git a/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/compliance_section.dart b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/compliance_section.dart new file mode 100644 index 00000000..a3a5211a --- /dev/null +++ b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/compliance_section.dart @@ -0,0 +1,39 @@ +import 'package:core_localization/core_localization.dart'; +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:krow_core/core.dart'; + +import '../profile_menu_grid.dart'; +import '../profile_menu_item.dart'; +import '../section_title.dart'; + +/// Widget displaying the compliance section of the staff profile. +/// +/// This section contains menu items for tax forms and other compliance-related documents. +class ComplianceSection extends StatelessWidget { + /// Creates a [ComplianceSection]. + const ComplianceSection({super.key}); + + @override + Widget build(BuildContext context) { + final TranslationsStaffProfileEn i18n = Translations.of(context).staff.profile; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SectionTitle(i18n.sections.compliance), + ProfileMenuGrid( + crossAxisCount: 3, + children: [ + ProfileMenuItem( + icon: UiIcons.file, + label: i18n.menu_items.tax_forms, + onTap: () => Modular.to.toTaxForms(), + ), + ], + ), + ], + ); + } +} diff --git a/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/finance_section.dart b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/finance_section.dart new file mode 100644 index 00000000..73db7355 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/finance_section.dart @@ -0,0 +1,48 @@ +import 'package:core_localization/core_localization.dart'; +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:krow_core/core.dart'; + +import '../profile_menu_grid.dart'; +import '../profile_menu_item.dart'; +import '../section_title.dart'; + +/// Widget displaying the finance section of the staff profile. +/// +/// This section contains menu items for bank account, payments, and timecard information. +class FinanceSection extends StatelessWidget { + /// Creates a [FinanceSection]. + const FinanceSection({super.key}); + + @override + Widget build(BuildContext context) { + final TranslationsStaffProfileEn i18n = Translations.of(context).staff.profile; + + return Column( + children: [ + SectionTitle(i18n.sections.finance), + ProfileMenuGrid( + crossAxisCount: 3, + children: [ + ProfileMenuItem( + icon: UiIcons.building, + label: i18n.menu_items.bank_account, + onTap: () => Modular.to.toBankAccount(), + ), + ProfileMenuItem( + icon: UiIcons.creditCard, + label: i18n.menu_items.payments, + onTap: () => Modular.to.toPayments(), + ), + ProfileMenuItem( + icon: UiIcons.clock, + label: i18n.menu_items.timecard, + onTap: () => Modular.to.toTimeCard(), + ), + ], + ), + ], + ); + } +} diff --git a/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/index.dart b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/index.dart new file mode 100644 index 00000000..967a4dac --- /dev/null +++ b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/index.dart @@ -0,0 +1,5 @@ +export 'compliance_section.dart'; +export 'finance_section.dart'; +export 'onboarding_section.dart'; +export 'settings_section.dart'; +export 'support_section.dart'; diff --git a/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/onboarding_section.dart b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/onboarding_section.dart new file mode 100644 index 00000000..2d9201e3 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/onboarding_section.dart @@ -0,0 +1,49 @@ +import 'package:core_localization/core_localization.dart'; +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:krow_core/core.dart'; + +import '../profile_menu_grid.dart'; +import '../profile_menu_item.dart'; +import '../section_title.dart'; + +/// Widget displaying the onboarding section of the staff profile. +/// +/// This section contains menu items for personal information, emergency contact, +/// and work experience setup. +class OnboardingSection extends StatelessWidget { + /// Creates an [OnboardingSection]. + const OnboardingSection({super.key}); + + @override + Widget build(BuildContext context) { + final TranslationsStaffProfileEn i18n = Translations.of(context).staff.profile; + + return Column( + children: [ + SectionTitle(i18n.sections.onboarding), + ProfileMenuGrid( + crossAxisCount: 3, + children: [ + ProfileMenuItem( + icon: UiIcons.user, + label: i18n.menu_items.personal_info, + onTap: () => Modular.to.toPersonalInfo(), + ), + ProfileMenuItem( + icon: UiIcons.phone, + label: i18n.menu_items.emergency_contact, + onTap: () => Modular.to.toEmergencyContact(), + ), + ProfileMenuItem( + icon: UiIcons.briefcase, + label: i18n.menu_items.experience, + onTap: () => Modular.to.toExperience(), + ), + ], + ), + ], + ); + } +} diff --git a/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/settings_section.dart b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/settings_section.dart new file mode 100644 index 00000000..5fa0b4f5 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/settings_section.dart @@ -0,0 +1,47 @@ +import 'package:core_localization/core_localization.dart'; +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; + +import '../language_selector_bottom_sheet.dart'; +import '../profile_menu_grid.dart'; +import '../profile_menu_item.dart'; +import '../section_title.dart'; + +/// Widget displaying the settings section of the staff profile. +/// +/// This section contains menu items for language selection. +class SettingsSection extends StatelessWidget { + /// Creates a [SettingsSection]. + const SettingsSection({super.key}); + + @override + Widget build(BuildContext context) { + final TranslationsStaffProfileEn i18n = Translations.of( + context, + ).staff.profile; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + + children: [ + SectionTitle(i18n.sections.settings), + ProfileMenuGrid( + crossAxisCount: 3, + children: [ + ProfileMenuItem( + icon: UiIcons.globe, + label: i18n.menu_items.language, + onTap: () { + showModalBottomSheet( + context: context, + builder: (BuildContext context) => + const LanguageSelectorBottomSheet(), + ); + }, + ), + ], + ), + ], + ); + } +} diff --git a/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/support_section.dart b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/support_section.dart new file mode 100644 index 00000000..f547c340 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/support_section.dart @@ -0,0 +1,46 @@ +import 'package:core_localization/core_localization.dart'; +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:krow_core/core.dart'; + +import '../profile_menu_grid.dart'; +import '../profile_menu_item.dart'; +import '../section_title.dart'; + +/// Widget displaying the support section of the staff profile. +/// +/// This section contains menu items for FAQs and privacy & security settings. +class SupportSection extends StatelessWidget { + /// Creates a [SupportSection]. + const SupportSection({super.key}); + + @override + Widget build(BuildContext context) { + final TranslationsStaffProfileEn i18n = Translations.of( + context, + ).staff.profile; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SectionTitle(i18n.sections.support), + ProfileMenuGrid( + crossAxisCount: 3, + children: [ + ProfileMenuItem( + icon: UiIcons.helpCircle, + label: i18n.menu_items.faqs, + onTap: () => Modular.to.toFaqs(), + ), + ProfileMenuItem( + icon: UiIcons.shield, + label: i18n.menu_items.privacy_security, + onTap: () => Modular.to.toPrivacySecurity(), + ), + ], + ), + ], + ); + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/assets/faqs/faqs.json b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/assets/faqs/faqs.json new file mode 100644 index 00000000..6b726e27 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/assets/faqs/faqs.json @@ -0,0 +1,53 @@ +[ + { + "category": "Getting Started", + "questions": [ + { + "q": "How do I apply for shifts?", + "a": "Browse available shifts on the Shifts tab and tap \"Accept\" on any shift that interests you. Once confirmed, you'll receive all the details you need." + }, + { + "q": "How do I get paid?", + "a": "Payments are processed weekly via direct deposit to your linked bank account. You can view your earnings in the Payments section." + }, + { + "q": "What if I need to cancel a shift?", + "a": "You can cancel a shift up to 24 hours before it starts without penalty. Late cancellations may affect your reliability score." + } + ] + }, + { + "category": "Shifts & Work", + "questions": [ + { + "q": "How do I clock in?", + "a": "Use the Clock In feature on the home screen when you arrive at your shift. Make sure location services are enabled for verification." + }, + { + "q": "What should I wear?", + "a": "Check the shift details for dress code requirements. You can manage your wardrobe in the Attire section of your profile." + }, + { + "q": "Who do I contact if I'm running late?", + "a": "Use the \"Running Late\" feature in the app to notify the client. You can also message the shift manager directly." + } + ] + }, + { + "category": "Payments & Earnings", + "questions": [ + { + "q": "When do I get paid?", + "a": "Payments are processed every Friday for shifts completed the previous week. Funds typically arrive within 1-2 business days." + }, + { + "q": "How do I update my bank account?", + "a": "Go to Profile > Finance > Bank Account to add or update your banking information." + }, + { + "q": "Where can I find my tax documents?", + "a": "Tax documents (1099) are available in Profile > Compliance > Tax Documents by January 31st each year." + } + ] + } +] diff --git a/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/data/repositories_impl/faqs_repository_impl.dart b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/data/repositories_impl/faqs_repository_impl.dart new file mode 100644 index 00000000..4bcc2ccd --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/data/repositories_impl/faqs_repository_impl.dart @@ -0,0 +1,101 @@ +import 'dart:convert'; + +import 'package:flutter/services.dart'; + +import '../../domain/entities/faq_category.dart'; +import '../../domain/entities/faq_item.dart'; +import '../../domain/repositories/faqs_repository_interface.dart'; + +/// Data layer implementation of FAQs repository +/// +/// Handles loading FAQs from app assets (JSON file) +class FaqsRepositoryImpl implements FaqsRepositoryInterface { + /// Private cache for FAQs to avoid reloading from assets multiple times + List? _cachedFaqs; + + @override + Future> getFaqs() async { + try { + // Return cached FAQs if available + if (_cachedFaqs != null) { + return _cachedFaqs!; + } + + // Load FAQs from JSON asset + final String faqsJson = await rootBundle.loadString( + 'packages/staff_faqs/lib/src/assets/faqs/faqs.json', + ); + + // Parse JSON + final List decoded = jsonDecode(faqsJson) as List; + + // Convert to domain entities + _cachedFaqs = decoded.map((dynamic item) { + final Map category = item as Map; + final String categoryName = category['category'] as String; + final List questionsData = + category['questions'] as List; + + final List questions = questionsData.map((dynamic q) { + final Map questionMap = q as Map; + return FaqItem( + question: questionMap['q'] as String, + answer: questionMap['a'] as String, + ); + }).toList(); + + return FaqCategory( + category: categoryName, + questions: questions, + ); + }).toList(); + + return _cachedFaqs!; + } catch (e) { + // Return empty list on error + return []; + } + } + + @override + Future> searchFaqs(String query) async { + try { + // Get all FAQs first + final List allFaqs = await getFaqs(); + + if (query.isEmpty) { + return allFaqs; + } + + final String lowerQuery = query.toLowerCase(); + + // Filter categories based on matching questions + final List filtered = allFaqs + .map((FaqCategory category) { + // Filter questions that match the query + final List matchingQuestions = + category.questions.where((FaqItem item) { + final String questionLower = item.question.toLowerCase(); + final String answerLower = item.answer.toLowerCase(); + return questionLower.contains(lowerQuery) || + answerLower.contains(lowerQuery); + }).toList(); + + // Only include category if it has matching questions + if (matchingQuestions.isNotEmpty) { + return FaqCategory( + category: category.category, + questions: matchingQuestions, + ); + } + return null; + }) + .whereType() + .toList(); + + return filtered; + } catch (e) { + return []; + } + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/domain/entities/faq_category.dart b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/domain/entities/faq_category.dart new file mode 100644 index 00000000..b199ea3b --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/domain/entities/faq_category.dart @@ -0,0 +1,20 @@ +import 'package:equatable/equatable.dart'; + +import 'faq_item.dart'; + +/// Entity representing an FAQ category with its questions +class FaqCategory extends Equatable { + /// The category name (e.g., "Getting Started", "Shifts & Work") + final String category; + + /// List of FAQ items in this category + final List questions; + + const FaqCategory({ + required this.category, + required this.questions, + }); + + @override + List get props => [category, questions]; +} diff --git a/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/domain/entities/faq_item.dart b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/domain/entities/faq_item.dart new file mode 100644 index 00000000..c8bb86d8 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/domain/entities/faq_item.dart @@ -0,0 +1,18 @@ +import 'package:equatable/equatable.dart'; + +/// Entity representing a single FAQ question and answer +class FaqItem extends Equatable { + /// The question text + final String question; + + /// The answer text + final String answer; + + const FaqItem({ + required this.question, + required this.answer, + }); + + @override + List get props => [question, answer]; +} diff --git a/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/domain/repositories/faqs_repository_interface.dart b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/domain/repositories/faqs_repository_interface.dart new file mode 100644 index 00000000..887ea0d1 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/domain/repositories/faqs_repository_interface.dart @@ -0,0 +1,11 @@ +import '../entities/faq_category.dart'; + +/// Interface for FAQs repository operations +abstract class FaqsRepositoryInterface { + /// Fetch all FAQ categories with their questions + Future> getFaqs(); + + /// Search FAQs by query string + /// Returns categories that contain matching questions + Future> searchFaqs(String query); +} diff --git a/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/domain/usecases/get_faqs_usecase.dart b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/domain/usecases/get_faqs_usecase.dart new file mode 100644 index 00000000..c4da8f89 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/domain/usecases/get_faqs_usecase.dart @@ -0,0 +1,19 @@ +import '../entities/faq_category.dart'; +import '../repositories/faqs_repository_interface.dart'; + +/// Use case to retrieve all FAQs +class GetFaqsUseCase { + final FaqsRepositoryInterface _repository; + + GetFaqsUseCase(this._repository); + + /// Execute the use case to get all FAQ categories + Future> call() async { + try { + return await _repository.getFaqs(); + } catch (e) { + // Return empty list on error + return []; + } + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/domain/usecases/search_faqs_usecase.dart b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/domain/usecases/search_faqs_usecase.dart new file mode 100644 index 00000000..39d36179 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/domain/usecases/search_faqs_usecase.dart @@ -0,0 +1,27 @@ +import '../entities/faq_category.dart'; +import '../repositories/faqs_repository_interface.dart'; + +/// Parameters for search FAQs use case +class SearchFaqsParams { + /// Search query string + final String query; + + SearchFaqsParams({required this.query}); +} + +/// Use case to search FAQs by query +class SearchFaqsUseCase { + final FaqsRepositoryInterface _repository; + + SearchFaqsUseCase(this._repository); + + /// Execute the use case to search FAQs + Future> call(SearchFaqsParams params) async { + try { + return await _repository.searchFaqs(params.query); + } catch (e) { + // Return empty list on error + return []; + } + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/presentation/blocs/faqs_bloc.dart b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/presentation/blocs/faqs_bloc.dart new file mode 100644 index 00000000..89c2291e --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/presentation/blocs/faqs_bloc.dart @@ -0,0 +1,76 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:equatable/equatable.dart'; + +import '../../domain/entities/faq_category.dart'; +import '../../domain/usecases/get_faqs_usecase.dart'; +import '../../domain/usecases/search_faqs_usecase.dart'; + +part 'faqs_event.dart'; +part 'faqs_state.dart'; + +/// BLoC managing FAQs state +class FaqsBloc extends Bloc { + final GetFaqsUseCase _getFaqsUseCase; + final SearchFaqsUseCase _searchFaqsUseCase; + + FaqsBloc({ + required GetFaqsUseCase getFaqsUseCase, + required SearchFaqsUseCase searchFaqsUseCase, + }) : _getFaqsUseCase = getFaqsUseCase, + _searchFaqsUseCase = searchFaqsUseCase, + super(const FaqsState()) { + on(_onFetchFaqs); + on(_onSearchFaqs); + } + + Future _onFetchFaqs( + FetchFaqsEvent event, + Emitter emit, + ) async { + emit(state.copyWith(isLoading: true, error: null)); + + try { + final List categories = await _getFaqsUseCase.call(); + emit( + state.copyWith( + isLoading: false, + categories: categories, + searchQuery: '', + ), + ); + } catch (e) { + emit( + state.copyWith( + isLoading: false, + error: 'Failed to load FAQs', + ), + ); + } + } + + Future _onSearchFaqs( + SearchFaqsEvent event, + Emitter emit, + ) async { + emit(state.copyWith(isLoading: true, error: null, searchQuery: event.query)); + + try { + final List results = await _searchFaqsUseCase.call( + SearchFaqsParams(query: event.query), + ); + emit( + state.copyWith( + isLoading: false, + categories: results, + ), + ); + } catch (e) { + emit( + state.copyWith( + isLoading: false, + error: 'Failed to search FAQs', + ), + ); + } + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/presentation/blocs/faqs_event.dart b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/presentation/blocs/faqs_event.dart new file mode 100644 index 00000000..a853c239 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/presentation/blocs/faqs_event.dart @@ -0,0 +1,25 @@ +part of 'faqs_bloc.dart'; + +/// Base class for FAQs BLoC events +abstract class FaqsEvent extends Equatable { + const FaqsEvent(); + + @override + List get props => []; +} + +/// Event to fetch all FAQs +class FetchFaqsEvent extends FaqsEvent { + const FetchFaqsEvent(); +} + +/// Event to search FAQs by query +class SearchFaqsEvent extends FaqsEvent { + /// Search query string + final String query; + + const SearchFaqsEvent({required this.query}); + + @override + List get props => [query]; +} diff --git a/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/presentation/blocs/faqs_state.dart b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/presentation/blocs/faqs_state.dart new file mode 100644 index 00000000..29302c5f --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/presentation/blocs/faqs_state.dart @@ -0,0 +1,46 @@ +part of 'faqs_bloc.dart'; + +/// State for FAQs BLoC +class FaqsState extends Equatable { + /// List of FAQ categories currently displayed + final List categories; + + /// Whether FAQs are currently loading + final bool isLoading; + + /// Current search query + final String searchQuery; + + /// Error message, if any + final String? error; + + const FaqsState({ + this.categories = const [], + this.isLoading = false, + this.searchQuery = '', + this.error, + }); + + /// Create a copy with optional field overrides + FaqsState copyWith({ + List? categories, + bool? isLoading, + String? searchQuery, + String? error, + }) { + return FaqsState( + categories: categories ?? this.categories, + isLoading: isLoading ?? this.isLoading, + searchQuery: searchQuery ?? this.searchQuery, + error: error, + ); + } + + @override + List get props => [ + categories, + isLoading, + searchQuery, + error, + ]; +} diff --git a/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/presentation/pages/faqs_page.dart b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/presentation/pages/faqs_page.dart new file mode 100644 index 00000000..1c99a9ab --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/presentation/pages/faqs_page.dart @@ -0,0 +1,32 @@ +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 'package:flutter_modular/flutter_modular.dart'; + +import '../blocs/faqs_bloc.dart'; +import '../widgets/faqs_widget.dart'; + +/// Page displaying frequently asked questions +class FaqsPage extends StatelessWidget { + const FaqsPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: UiAppBar( + title: t.staff_faqs.title, + showBackButton: true, + bottom: PreferredSize( + preferredSize: const Size.fromHeight(1), + child: Container(color: UiColors.border, height: 1), + ), + ), + body: BlocProvider( + create: (BuildContext context) => + Modular.get()..add(const FetchFaqsEvent()), + child: const Stack(children: [FaqsWidget()]), + ), + ); + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/presentation/widgets/faqs_widget.dart b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/presentation/widgets/faqs_widget.dart new file mode 100644 index 00000000..bda66591 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/presentation/widgets/faqs_widget.dart @@ -0,0 +1,194 @@ +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 'package:staff_faqs/src/presentation/blocs/faqs_bloc.dart'; + +/// Widget displaying FAQs with search functionality and accordion items +class FaqsWidget extends StatefulWidget { + const FaqsWidget({super.key}); + + @override + State createState() => _FaqsWidgetState(); +} + +class _FaqsWidgetState extends State { + late TextEditingController _searchController; + final Map _openItems = {}; + + @override + void initState() { + super.initState(); + _searchController = TextEditingController(); + } + + @override + void dispose() { + _searchController.dispose(); + super.dispose(); + } + + void _toggleItem(String key) { + setState(() { + _openItems[key] = !(_openItems[key] ?? false); + }); + } + + void _onSearchChanged(String value) { + if (value.isEmpty) { + context.read().add(const FetchFaqsEvent()); + } else { + context.read().add(SearchFaqsEvent(query: value)); + } + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (BuildContext context, FaqsState state) { + return SingleChildScrollView( + padding: const EdgeInsets.fromLTRB(20, 20, 20, 100), + child: Column( + children: [ + // Search Bar + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(UiConstants.radiusBase), + border: Border.all(color: UiColors.border), + ), + child: TextField( + controller: _searchController, + onChanged: _onSearchChanged, + decoration: InputDecoration( + hintText: t.staff_faqs.search_placeholder, + hintStyle: const TextStyle(color: UiColors.textPlaceholder), + prefixIcon: const Icon( + UiIcons.search, + color: UiColors.textSecondary, + ), + border: InputBorder.none, + contentPadding: const EdgeInsets.symmetric(vertical: 12), + ), + ), + ), + const SizedBox(height: 24), + + // FAQ List or Empty State + if (state.isLoading) + const Padding( + padding: EdgeInsets.symmetric(vertical: 48), + child: CircularProgressIndicator(), + ) + else if (state.categories.isEmpty) + Padding( + padding: const EdgeInsets.symmetric(vertical: 48), + child: Column( + children: [ + const Icon( + UiIcons.helpCircle, + size: 48, + color: UiColors.textSecondary, + ), + const SizedBox(height: 12), + Text( + t.staff_faqs.no_results, + style: const TextStyle(color: UiColors.textSecondary), + ), + ], + ), + ) + else + ...state.categories.asMap().entries.map(( + MapEntry entry, + ) { + final int catIndex = entry.key; + final dynamic categoryItem = entry.value; + final String categoryName = categoryItem.category; + final List questions = categoryItem.questions; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + categoryName, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: UiColors.textPrimary, + ), + ), + const SizedBox(height: 12), + ...questions.asMap().entries.map(( + MapEntry qEntry, + ) { + final int qIndex = qEntry.key; + final dynamic questionItem = qEntry.value; + final String key = '$catIndex-$qIndex'; + final bool isOpen = _openItems[key] ?? false; + + return Container( + margin: const EdgeInsets.only(bottom: 8), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular( + UiConstants.radiusBase, + ), + border: Border.all(color: UiColors.border), + ), + child: Column( + children: [ + InkWell( + onTap: () => _toggleItem(key), + borderRadius: BorderRadius.circular( + UiConstants.radiusBase, + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + Expanded( + child: Text( + questionItem.question, + style: UiTypography.body1r, + ), + ), + Icon( + isOpen + ? UiIcons.chevronUp + : UiIcons.chevronDown, + color: UiColors.textSecondary, + size: 20, + ), + ], + ), + ), + ), + if (isOpen) + Padding( + padding: const EdgeInsets.fromLTRB( + 16, + 0, + 16, + 16, + ), + child: Text( + questionItem.answer, + style: UiTypography.body1r.textSecondary, + ), + ), + ], + ), + ); + }), + const SizedBox(height: 12), + ], + ); + }), + ], + ), + ); + }, + ); + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/staff_faqs_module.dart b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/staff_faqs_module.dart new file mode 100644 index 00000000..6faf7c3a --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/staff_faqs_module.dart @@ -0,0 +1,52 @@ +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:krow_core/core.dart'; + +import 'data/repositories_impl/faqs_repository_impl.dart'; +import 'domain/repositories/faqs_repository_interface.dart'; +import 'domain/usecases/get_faqs_usecase.dart'; +import 'domain/usecases/search_faqs_usecase.dart'; +import 'presentation/blocs/faqs_bloc.dart'; +import 'presentation/pages/faqs_page.dart'; + +/// Module for FAQs feature +/// +/// Provides: +/// - Dependency injection for repositories, use cases, and BLoCs +/// - Route definitions delegated to core routing +class FaqsModule extends Module { + @override + void binds(Injector i) { + // Repository + i.addSingleton( + () => FaqsRepositoryImpl(), + ); + + // Use Cases + i.addSingleton( + () => GetFaqsUseCase( + i(), + ), + ); + i.addSingleton( + () => SearchFaqsUseCase( + i(), + ), + ); + + // BLoC + i.add( + () => FaqsBloc( + getFaqsUseCase: i(), + searchFaqsUseCase: i(), + ), + ); + } + + @override + void routes(RouteManager r) { + r.child( + StaffPaths.childRoute(StaffPaths.faqs, StaffPaths.faqs), + child: (_) => const FaqsPage(), + ); + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/staff_faqs.dart b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/staff_faqs.dart new file mode 100644 index 00000000..46c3940d --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/staff_faqs.dart @@ -0,0 +1,4 @@ +library staff_faqs; + +export 'src/staff_faqs_module.dart'; +export 'src/presentation/pages/faqs_page.dart'; diff --git a/apps/mobile/packages/features/staff/profile_sections/support/faqs/pubspec.yaml b/apps/mobile/packages/features/staff/profile_sections/support/faqs/pubspec.yaml new file mode 100644 index 00000000..e50b0511 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/support/faqs/pubspec.yaml @@ -0,0 +1,29 @@ +name: staff_faqs +description: Frequently Asked Questions feature for staff application. +version: 0.0.1 +publish_to: none +resolution: workspace + +environment: + sdk: '>=3.10.0 <4.0.0' + flutter: ">=3.0.0" + +dependencies: + flutter: + sdk: flutter + flutter_bloc: ^8.1.0 + flutter_modular: ^6.3.0 + equatable: ^2.0.5 + + # Architecture Packages + krow_core: + path: ../../../../../core + design_system: + path: ../../../../../design_system + core_localization: + path: ../../../../../core_localization + +flutter: + uses-material-design: true + assets: + - lib/src/assets/faqs/ diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/assets/legal/privacy_policy.txt b/apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/assets/legal/privacy_policy.txt similarity index 100% rename from apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/assets/legal/privacy_policy.txt rename to apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/assets/legal/privacy_policy.txt diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/assets/legal/terms_of_service.txt b/apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/assets/legal/terms_of_service.txt similarity index 100% rename from apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/assets/legal/terms_of_service.txt rename to apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/assets/legal/terms_of_service.txt diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/data/repositories_impl/privacy_settings_repository_impl.dart b/apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/data/repositories_impl/privacy_settings_repository_impl.dart similarity index 100% rename from apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/data/repositories_impl/privacy_settings_repository_impl.dart rename to apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/data/repositories_impl/privacy_settings_repository_impl.dart diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/entities/privacy_settings_entity.dart b/apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/domain/entities/privacy_settings_entity.dart similarity index 100% rename from apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/entities/privacy_settings_entity.dart rename to apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/domain/entities/privacy_settings_entity.dart diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/repositories/privacy_settings_repository_interface.dart b/apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/domain/repositories/privacy_settings_repository_interface.dart similarity index 100% rename from apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/repositories/privacy_settings_repository_interface.dart rename to apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/domain/repositories/privacy_settings_repository_interface.dart diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/usecases/get_privacy_policy_usecase.dart b/apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/domain/usecases/get_privacy_policy_usecase.dart similarity index 100% rename from apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/usecases/get_privacy_policy_usecase.dart rename to apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/domain/usecases/get_privacy_policy_usecase.dart diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/usecases/get_profile_visibility_usecase.dart b/apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/domain/usecases/get_profile_visibility_usecase.dart similarity index 100% rename from apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/usecases/get_profile_visibility_usecase.dart rename to apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/domain/usecases/get_profile_visibility_usecase.dart diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/usecases/get_terms_usecase.dart b/apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/domain/usecases/get_terms_usecase.dart similarity index 100% rename from apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/usecases/get_terms_usecase.dart rename to apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/domain/usecases/get_terms_usecase.dart diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/usecases/update_profile_visibility_usecase.dart b/apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/domain/usecases/update_profile_visibility_usecase.dart similarity index 100% rename from apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/usecases/update_profile_visibility_usecase.dart rename to apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/domain/usecases/update_profile_visibility_usecase.dart diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/blocs/legal/privacy_policy_cubit.dart b/apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/presentation/blocs/legal/privacy_policy_cubit.dart similarity index 100% rename from apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/blocs/legal/privacy_policy_cubit.dart rename to apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/presentation/blocs/legal/privacy_policy_cubit.dart diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/blocs/legal/terms_cubit.dart b/apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/presentation/blocs/legal/terms_cubit.dart similarity index 100% rename from apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/blocs/legal/terms_cubit.dart rename to apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/presentation/blocs/legal/terms_cubit.dart diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/blocs/privacy_security_bloc.dart b/apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/presentation/blocs/privacy_security_bloc.dart similarity index 100% rename from apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/blocs/privacy_security_bloc.dart rename to apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/presentation/blocs/privacy_security_bloc.dart diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/blocs/privacy_security_event.dart b/apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/presentation/blocs/privacy_security_event.dart similarity index 100% rename from apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/blocs/privacy_security_event.dart rename to apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/presentation/blocs/privacy_security_event.dart diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/blocs/privacy_security_state.dart b/apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/presentation/blocs/privacy_security_state.dart similarity index 100% rename from apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/blocs/privacy_security_state.dart rename to apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/presentation/blocs/privacy_security_state.dart diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/pages/legal/privacy_policy_page.dart b/apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/presentation/pages/legal/privacy_policy_page.dart similarity index 100% rename from apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/pages/legal/privacy_policy_page.dart rename to apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/presentation/pages/legal/privacy_policy_page.dart diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/pages/legal/terms_of_service_page.dart b/apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/presentation/pages/legal/terms_of_service_page.dart similarity index 100% rename from apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/pages/legal/terms_of_service_page.dart rename to apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/presentation/pages/legal/terms_of_service_page.dart diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/pages/privacy_security_page.dart b/apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/presentation/pages/privacy_security_page.dart similarity index 100% rename from apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/pages/privacy_security_page.dart rename to apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/presentation/pages/privacy_security_page.dart diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/widgets/legal/legal_section_widget.dart b/apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/presentation/widgets/legal/legal_section_widget.dart similarity index 100% rename from apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/widgets/legal/legal_section_widget.dart rename to apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/presentation/widgets/legal/legal_section_widget.dart diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/widgets/privacy/privacy_section_widget.dart b/apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/presentation/widgets/privacy/privacy_section_widget.dart similarity index 100% rename from apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/widgets/privacy/privacy_section_widget.dart rename to apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/presentation/widgets/privacy/privacy_section_widget.dart diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/widgets/settings_action_tile_widget.dart b/apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/presentation/widgets/settings_action_tile_widget.dart similarity index 100% rename from apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/widgets/settings_action_tile_widget.dart rename to apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/presentation/widgets/settings_action_tile_widget.dart diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/widgets/settings_divider_widget.dart b/apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/presentation/widgets/settings_divider_widget.dart similarity index 100% rename from apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/widgets/settings_divider_widget.dart rename to apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/presentation/widgets/settings_divider_widget.dart diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/widgets/settings_section_header_widget.dart b/apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/presentation/widgets/settings_section_header_widget.dart similarity index 100% rename from apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/widgets/settings_section_header_widget.dart rename to apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/presentation/widgets/settings_section_header_widget.dart diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/widgets/settings_switch_tile_widget.dart b/apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/presentation/widgets/settings_switch_tile_widget.dart similarity index 100% rename from apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/widgets/settings_switch_tile_widget.dart rename to apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/presentation/widgets/settings_switch_tile_widget.dart diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/staff_privacy_security_module.dart b/apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/staff_privacy_security_module.dart similarity index 100% rename from apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/staff_privacy_security_module.dart rename to apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/src/staff_privacy_security_module.dart diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/staff_privacy_security.dart b/apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/staff_privacy_security.dart similarity index 100% rename from apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/staff_privacy_security.dart rename to apps/mobile/packages/features/staff/profile_sections/support/privacy_security/lib/staff_privacy_security.dart diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/pubspec.yaml b/apps/mobile/packages/features/staff/profile_sections/support/privacy_security/pubspec.yaml similarity index 100% rename from apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/pubspec.yaml rename to apps/mobile/packages/features/staff/profile_sections/support/privacy_security/pubspec.yaml diff --git a/apps/mobile/packages/features/staff/staff_main/lib/src/staff_main_module.dart b/apps/mobile/packages/features/staff/staff_main/lib/src/staff_main_module.dart index ef0de90f..fd5ddc74 100644 --- a/apps/mobile/packages/features/staff/staff_main/lib/src/staff_main_module.dart +++ b/apps/mobile/packages/features/staff/staff_main/lib/src/staff_main_module.dart @@ -18,6 +18,7 @@ import 'package:staff_profile_experience/staff_profile_experience.dart'; import 'package:staff_profile_info/staff_profile_info.dart'; import 'package:staff_shifts/staff_shifts.dart'; import 'package:staff_tax_forms/staff_tax_forms.dart'; +import 'package:staff_faqs/staff_faqs.dart'; import 'package:staff_time_card/staff_time_card.dart'; class StaffMainModule extends Module { @@ -102,5 +103,9 @@ class StaffMainModule extends Module { StaffPaths.childRoute(StaffPaths.main, StaffPaths.shiftDetailsRoute), module: ShiftDetailsModule(), ); + r.module( + StaffPaths.childRoute(StaffPaths.main, StaffPaths.faqs), + module: FaqsModule(), + ); } } diff --git a/apps/mobile/packages/features/staff/staff_main/pubspec.yaml b/apps/mobile/packages/features/staff/staff_main/pubspec.yaml index 44865ecf..f31d21a8 100644 --- a/apps/mobile/packages/features/staff/staff_main/pubspec.yaml +++ b/apps/mobile/packages/features/staff/staff_main/pubspec.yaml @@ -55,7 +55,9 @@ dependencies: staff_clock_in: path: ../clock_in staff_privacy_security: - path: ../profile_sections/settings/privacy_security + path: ../profile_sections/support/privacy_security + staff_faqs: + path: ../profile_sections/support/faqs dev_dependencies: flutter_test: diff --git a/apps/mobile/pubspec.lock b/apps/mobile/pubspec.lock index 5a9b3aaf..d9afe13f 100644 --- a/apps/mobile/pubspec.lock +++ b/apps/mobile/pubspec.lock @@ -1290,10 +1290,17 @@ packages: url: "https://pub.dev" source: hosted version: "1.12.1" + staff_faqs: + dependency: transitive + description: + path: "packages/features/staff/profile_sections/support/faqs" + relative: true + source: path + version: "0.0.1" staff_privacy_security: dependency: transitive description: - path: "packages/features/staff/profile_sections/settings/privacy_security" + path: "packages/features/staff/profile_sections/support/privacy_security" relative: true source: path version: "0.0.1" diff --git a/internal/launchpad/assets/documents/prototype/client-mobile-application/architecture.md b/internal/launchpad/assets/documents/prototype/client-mobile-application/architecture.md index f035e224..174a0540 100644 --- a/internal/launchpad/assets/documents/prototype/client-mobile-application/architecture.md +++ b/internal/launchpad/assets/documents/prototype/client-mobile-application/architecture.md @@ -59,7 +59,7 @@ The application is broken down into several key functional modules: | Component | Primary Responsibility | Example Task | | :--- | :--- | :--- | -| **Router (GoRouter)** | Navigation traffic cop | Directs the user from the "Login" screen to the "Home" dashboard upon success. | +| **Router (Flutter Modular)** | Navigation traffic cop | Directs the user from the "Login" screen to the "Home" dashboard upon success. | | **Screens (UI)** | Displaying information | Renders the "Create Order" form and captures the user's input for date and time. | | **Providers (Riverpod)** | Data management & State | Holds the list of today's active shifts so multiple screens can access it without reloading. | | **Widgets** | Reusable UI building blocks | A "Shift Card" widget that displays shift details effectively, used in multiple lists throughout the app. | @@ -91,7 +91,7 @@ While currently operating as a high-fidelity prototype with mock data, the archi ## 8. Key Design Decisions * **Flutter Framework:** chosen for its ability to produce high-performance, native-feeling apps for both iOS and Android from a single codebase, reducing development time and cost. -* **GoRouter for Navigation:** A modern routing package that handles complex navigation scenarios (like deep linking and sub-routes) which are essential for a multi-layered app like this. +* **Flutter Modular for Navigation:** A modern routing package that handles complex navigation scenarios (like deep linking and sub-routes) which are essential for a multi-layered app like this. * **Riverpod for State Management:** A robust solution that catches programming errors at compile-time (while writing code) rather than run-time (while using the app), increasing app stability. * **Mock Data Services:** The decision to use extensive mock data allows for rapid UI/UX iteration and testing of business flows without waiting for the full backend infrastructure to be built. @@ -102,7 +102,7 @@ flowchart TD direction TB subgraph PresentationLayer["Presentation Layer (UI)"] direction TB - Router["GoRouter Navigation"] + Router["Flutter Modular Navigation"] subgraph FeatureModules["Feature Modules"] AuthUI["Auth Screens"] DashUI["Dashboard & Home"] diff --git a/internal/launchpad/assets/documents/prototype/staff-mobile-application/architecture.md b/internal/launchpad/assets/documents/prototype/staff-mobile-application/architecture.md index 0c2ffbff..07c385b7 100644 --- a/internal/launchpad/assets/documents/prototype/staff-mobile-application/architecture.md +++ b/internal/launchpad/assets/documents/prototype/staff-mobile-application/architecture.md @@ -98,7 +98,7 @@ flowchart TD direction TB subgraph PresentationLayer["Presentation Layer (UI)"] direction TB - Router["GoRouter Navigation"] + Router["Flutter Modular Navigation"] subgraph FeatureModules["Feature Modules"] AuthUI["Auth & Onboarding"] MarketUI["Marketplace & Jobs"]