From 11a9a7800c3a0e1234631653334ddc9c5758d938 Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Wed, 18 Feb 2026 20:58:49 -0500 Subject: [PATCH 1/7] feat: Implement Privacy & Security Settings Module for Staff - Add PrivacySettingsRepositoryImpl to handle backend communication for privacy settings. - Create PrivacySettingsEntity to represent user privacy preferences. - Define PrivacySettingsRepositoryInterface for repository operations. - Implement use cases for fetching and updating profile visibility, terms of service, and privacy policy. - Create PrivacyPolicyCubit and TermsCubit for managing legal document states. - Develop PrivacySecurityBloc to manage privacy and security settings state. - Create UI pages for Privacy Policy and Terms of Service with corresponding widgets. - Implement PrivacySectionWidget and LegalSectionWidget for displaying privacy settings and legal documents. - Add settings action tiles and section headers for better UI organization. - Update pubspec.yaml with necessary dependencies and asset paths. --- .../privacy_security/lib/src/assets/legal/privacy_policy.txt | 0 .../privacy_security/lib/src/assets/legal/terms_of_service.txt | 0 .../data/repositories_impl/privacy_settings_repository_impl.dart | 0 .../lib/src/domain/entities/privacy_settings_entity.dart | 0 .../repositories/privacy_settings_repository_interface.dart | 0 .../lib/src/domain/usecases/get_privacy_policy_usecase.dart | 0 .../lib/src/domain/usecases/get_profile_visibility_usecase.dart | 0 .../lib/src/domain/usecases/get_terms_usecase.dart | 0 .../src/domain/usecases/update_profile_visibility_usecase.dart | 0 .../lib/src/presentation/blocs/legal/privacy_policy_cubit.dart | 0 .../lib/src/presentation/blocs/legal/terms_cubit.dart | 0 .../lib/src/presentation/blocs/privacy_security_bloc.dart | 0 .../lib/src/presentation/blocs/privacy_security_event.dart | 0 .../lib/src/presentation/blocs/privacy_security_state.dart | 0 .../lib/src/presentation/pages/legal/privacy_policy_page.dart | 0 .../lib/src/presentation/pages/legal/terms_of_service_page.dart | 0 .../lib/src/presentation/pages/privacy_security_page.dart | 0 .../lib/src/presentation/widgets/legal/legal_section_widget.dart | 0 .../src/presentation/widgets/privacy/privacy_section_widget.dart | 0 .../lib/src/presentation/widgets/settings_action_tile_widget.dart | 0 .../lib/src/presentation/widgets/settings_divider_widget.dart | 0 .../src/presentation/widgets/settings_section_header_widget.dart | 0 .../lib/src/presentation/widgets/settings_switch_tile_widget.dart | 0 .../privacy_security/lib/src/staff_privacy_security_module.dart | 0 .../privacy_security/lib/staff_privacy_security.dart | 0 .../{settings => support}/privacy_security/pubspec.yaml | 0 26 files changed, 0 insertions(+), 0 deletions(-) rename apps/mobile/packages/features/staff/profile_sections/{settings => support}/privacy_security/lib/src/assets/legal/privacy_policy.txt (100%) rename apps/mobile/packages/features/staff/profile_sections/{settings => support}/privacy_security/lib/src/assets/legal/terms_of_service.txt (100%) rename apps/mobile/packages/features/staff/profile_sections/{settings => support}/privacy_security/lib/src/data/repositories_impl/privacy_settings_repository_impl.dart (100%) rename apps/mobile/packages/features/staff/profile_sections/{settings => support}/privacy_security/lib/src/domain/entities/privacy_settings_entity.dart (100%) rename apps/mobile/packages/features/staff/profile_sections/{settings => support}/privacy_security/lib/src/domain/repositories/privacy_settings_repository_interface.dart (100%) rename apps/mobile/packages/features/staff/profile_sections/{settings => support}/privacy_security/lib/src/domain/usecases/get_privacy_policy_usecase.dart (100%) rename apps/mobile/packages/features/staff/profile_sections/{settings => support}/privacy_security/lib/src/domain/usecases/get_profile_visibility_usecase.dart (100%) rename apps/mobile/packages/features/staff/profile_sections/{settings => support}/privacy_security/lib/src/domain/usecases/get_terms_usecase.dart (100%) rename apps/mobile/packages/features/staff/profile_sections/{settings => support}/privacy_security/lib/src/domain/usecases/update_profile_visibility_usecase.dart (100%) rename apps/mobile/packages/features/staff/profile_sections/{settings => support}/privacy_security/lib/src/presentation/blocs/legal/privacy_policy_cubit.dart (100%) rename apps/mobile/packages/features/staff/profile_sections/{settings => support}/privacy_security/lib/src/presentation/blocs/legal/terms_cubit.dart (100%) rename apps/mobile/packages/features/staff/profile_sections/{settings => support}/privacy_security/lib/src/presentation/blocs/privacy_security_bloc.dart (100%) rename apps/mobile/packages/features/staff/profile_sections/{settings => support}/privacy_security/lib/src/presentation/blocs/privacy_security_event.dart (100%) rename apps/mobile/packages/features/staff/profile_sections/{settings => support}/privacy_security/lib/src/presentation/blocs/privacy_security_state.dart (100%) rename apps/mobile/packages/features/staff/profile_sections/{settings => support}/privacy_security/lib/src/presentation/pages/legal/privacy_policy_page.dart (100%) rename apps/mobile/packages/features/staff/profile_sections/{settings => support}/privacy_security/lib/src/presentation/pages/legal/terms_of_service_page.dart (100%) rename apps/mobile/packages/features/staff/profile_sections/{settings => support}/privacy_security/lib/src/presentation/pages/privacy_security_page.dart (100%) rename apps/mobile/packages/features/staff/profile_sections/{settings => support}/privacy_security/lib/src/presentation/widgets/legal/legal_section_widget.dart (100%) rename apps/mobile/packages/features/staff/profile_sections/{settings => support}/privacy_security/lib/src/presentation/widgets/privacy/privacy_section_widget.dart (100%) rename apps/mobile/packages/features/staff/profile_sections/{settings => support}/privacy_security/lib/src/presentation/widgets/settings_action_tile_widget.dart (100%) rename apps/mobile/packages/features/staff/profile_sections/{settings => support}/privacy_security/lib/src/presentation/widgets/settings_divider_widget.dart (100%) rename apps/mobile/packages/features/staff/profile_sections/{settings => support}/privacy_security/lib/src/presentation/widgets/settings_section_header_widget.dart (100%) rename apps/mobile/packages/features/staff/profile_sections/{settings => support}/privacy_security/lib/src/presentation/widgets/settings_switch_tile_widget.dart (100%) rename apps/mobile/packages/features/staff/profile_sections/{settings => support}/privacy_security/lib/src/staff_privacy_security_module.dart (100%) rename apps/mobile/packages/features/staff/profile_sections/{settings => support}/privacy_security/lib/staff_privacy_security.dart (100%) rename apps/mobile/packages/features/staff/profile_sections/{settings => support}/privacy_security/pubspec.yaml (100%) 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 From 316a010779a9cbbea2d8b54dd525ef96489ebd8c Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Wed, 18 Feb 2026 22:21:18 -0500 Subject: [PATCH 2/7] feat: Implement FAQs feature for staff application - Created a modular package for Frequently Asked Questions (FAQs) functionality. - Established Clean Architecture with Domain, Data, and Presentation layers. - Implemented BLoC for state management with events and states. - Developed search functionality with real-time filtering of FAQs. - Designed an accordion UI for displaying FAQs by category. - Added localization support for English and Spanish. - Included comprehensive documentation and testing checklist. - Integrated dependency injection for repositories and use cases. - Configured routing for seamless navigation to FAQs page. --- .../lib/src/l10n/en.i18n.json | 6 + .../lib/src/l10n/es.i18n.json | 6 + .../profile_sections/support/faqs/README.md | 122 +++++++++++ .../faqs/lib/src/assets/faqs/faqs.json | 53 +++++ .../faqs_repository_impl.dart | 101 +++++++++ .../lib/src/domain/entities/faq_category.dart | 20 ++ .../lib/src/domain/entities/faq_item.dart | 18 ++ .../faqs_repository_interface.dart | 11 + .../src/domain/usecases/get_faqs_usecase.dart | 19 ++ .../domain/usecases/search_faqs_usecase.dart | 27 +++ .../lib/src/presentation/blocs/faqs_bloc.dart | 76 +++++++ .../src/presentation/blocs/faqs_event.dart | 25 +++ .../src/presentation/blocs/faqs_state.dart | 46 ++++ .../lib/src/presentation/pages/faqs_page.dart | 95 +++++++++ .../src/presentation/widgets/faqs_widget.dart | 196 ++++++++++++++++++ .../faqs/lib/src/staff_faqs_module.dart | 51 +++++ .../support/faqs/lib/staff_faqs.dart | 4 + .../support/faqs/pubspec.yaml | 37 ++++ .../features/staff/staff_main/pubspec.yaml | 4 +- apps/mobile/pubspec.lock | 17 +- 20 files changed, 932 insertions(+), 2 deletions(-) create mode 100644 apps/mobile/packages/features/staff/profile_sections/support/faqs/README.md create mode 100644 apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/assets/faqs/faqs.json create mode 100644 apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/data/repositories_impl/faqs_repository_impl.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/domain/entities/faq_category.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/domain/entities/faq_item.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/domain/repositories/faqs_repository_interface.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/domain/usecases/get_faqs_usecase.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/domain/usecases/search_faqs_usecase.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/presentation/blocs/faqs_bloc.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/presentation/blocs/faqs_event.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/presentation/blocs/faqs_state.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/presentation/pages/faqs_page.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/presentation/widgets/faqs_widget.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/staff_faqs_module.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/staff_faqs.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/support/faqs/pubspec.yaml 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..beef30fb 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 @@ -1143,6 +1143,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..80df41d0 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 @@ -1143,6 +1143,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/features/staff/profile_sections/support/faqs/README.md b/apps/mobile/packages/features/staff/profile_sections/support/faqs/README.md new file mode 100644 index 00000000..5213dad3 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/support/faqs/README.md @@ -0,0 +1,122 @@ +# Staff FAQs Feature + +A modular feature package providing Frequently Asked Questions functionality for the staff mobile application. + +## Architecture + +This package follows clean architecture principles with clear separation of concerns: + +### Domain Layer (`lib/src/domain/`) +- **Entities**: `FaqCategory`, `FaqItem` - Domain models representing FAQ data +- **Repositories**: `FaqsRepositoryInterface` - Abstract interface for FAQ data access +- **Use Cases**: + - `GetFaqsUseCase` - Retrieves all FAQs + - `SearchFaqsUseCase` - Searches FAQs by query + +### Data Layer (`lib/src/data/`) +- **Repositories Implementation**: `FaqsRepositoryImpl` + - Loads FAQs from JSON asset file (`lib/src/assets/faqs/faqs.json`) + - Implements caching to avoid repeated file reads + - Provides search filtering logic + +### Presentation Layer (`lib/src/presentation/`) +- **BLoC**: `FaqsBloc` - State management with events and states + - Events: `FetchFaqsEvent`, `SearchFaqsEvent` + - State: `FaqsState` - Manages categories, loading, search query, and errors +- **Pages**: `FaqsPage` - Full-screen FAQ view with AppBar and contact button +- **Widgets**: `FaqsWidget` - Reusable accordion widget with search functionality + +## Features + +✅ **Search Functionality** - Real-time search across questions and answers +✅ **Accordion UI** - Expandable/collapsible FAQ items +✅ **Category Organization** - FAQs grouped by category +✅ **Localization** - Support for English and Spanish +✅ **Contact Support Button** - Direct link to messaging/support +✅ **Empty State** - Helpful message when no results found +✅ **Loading State** - Loading indicator while fetching FAQs +✅ **Asset-based Data** - FAQ content stored in JSON for easy updates + +## Data Structure + +FAQs are stored in `lib/src/assets/faqs/faqs.json` with the following structure: + +```json +[ + { + "category": "Getting Started", + "questions": [ + { + "q": "How do I apply for shifts?", + "a": "Browse available shifts..." + } + ] + } +] +``` + +## Localization + +Localization strings are defined in: +- `packages/core_localization/lib/src/l10n/en.i18n.json` +- `packages/core_localization/lib/src/l10n/es.i18n.json` + +Available keys: +- `staff_faqs.title` - Page title +- `staff_faqs.search_placeholder` - Search input hint +- `staff_faqs.no_results` - Empty state message +- `staff_faqs.contact_support` - Button label + +## Dependency Injection + +The `FaqsModule` provides all dependencies: + +```dart +FaqsModule().binds(injector); +``` + +Registered singletons: +- `FaqsRepositoryInterface` → `FaqsRepositoryImpl` +- `GetFaqsUseCase` +- `SearchFaqsUseCase` +- `FaqsBloc` + +## Routing + +Routes are defined in `FaqsModule.routes()`: +- `/` → `FaqsPage` + +## Usage + +1. Add `FaqsModule` to your modular configuration +2. Access the page via routing: `context.push('/faqs')` +3. The BLoC will automatically fetch FAQs on page load + +## Asset Configuration + +Update `pubspec.yaml` to include assets: + +```yaml +flutter: + assets: + - lib/src/assets/faqs/ +``` + +## Testing + +The package includes test support with: +- `bloc_test` for BLoC testing +- `mocktail` for mocking dependencies + +## Design System Integration + +Uses the common design system components: +- `UiColors` - Color constants +- `UiConstants` - Sizing and radius constants +- `LucideIcons` - Icon library + +## Notes + +- FAQs are cached in memory after first load to improve performance +- Search is case-insensitive +- The widget state (expanded/collapsed items) is local to the widget and resets on navigation 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..9c1b77a1 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/presentation/pages/faqs_page.dart @@ -0,0 +1,95 @@ +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 'package:go_router/go_router.dart'; +import 'package:lucide_icons/lucide_icons.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 BlocProvider( + create: (BuildContext context) => Modular.get()..add(const FetchFaqsEvent()), + child: Scaffold( + backgroundColor: UiColors.background, + appBar: AppBar( + backgroundColor: Colors.white, + elevation: 0, + leading: GestureDetector( + onTap: () => context.pop(), + child: const Icon( + LucideIcons.chevronLeft, + color: UiColors.textSecondary, + ), + ), + title: Text( + t.staff_faqs.title, + style: const TextStyle( + color: UiColors.textPrimary, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + bottom: PreferredSize( + preferredSize: const Size.fromHeight(1), + child: Container(color: UiColors.border, height: 1), + ), + ), + body: Stack( + children: [ + const FaqsWidget(), + // Contact Support Button at Bottom + Positioned( + left: 0, + right: 0, + bottom: 0, + child: Container( + padding: const EdgeInsets.all(20), + decoration: const BoxDecoration( + color: Colors.white, + border: Border(top: BorderSide(color: UiColors.border)), + ), + child: SafeArea( + top: false, + child: SizedBox( + width: double.infinity, + height: 48, + child: ElevatedButton( + onPressed: () => context.push('/messages'), + style: ElevatedButton.styleFrom( + backgroundColor: UiColors.primary, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(UiConstants.radiusBase), + ), + elevation: 0, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(LucideIcons.messageCircle, size: 20), + const SizedBox(width: 8), + Text( + t.staff_faqs.contact_support, + style: const TextStyle(fontWeight: FontWeight.w600), + ), + ], + ), + ), + ), + ), + ), + ), + ], + ), + ), + ); + } +} 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..317e607d --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/presentation/widgets/faqs_widget.dart @@ -0,0 +1,196 @@ +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:lucide_icons/lucide_icons.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( + LucideIcons.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( + LucideIcons.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: const TextStyle( + fontWeight: FontWeight.w500, + color: UiColors.textPrimary, + ), + ), + ), + Icon( + isOpen + ? LucideIcons.chevronUp + : LucideIcons.chevronDown, + color: UiColors.textSecondary, + size: 20, + ), + ], + ), + ), + ), + if (isOpen) + Padding( + padding: const EdgeInsets.fromLTRB( + 16, + 0, + 16, + 16, + ), + child: Text( + questionItem.answer, + style: const TextStyle( + color: UiColors.textSecondary, + fontSize: 14, + height: 1.5, + ), + ), + ), + ], + ), + ); + }).toList(), + const SizedBox(height: 12), + ], + ); + }).toList(), + ], + ), + ); + }, + ); + } +} 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..f1a42142 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/support/faqs/lib/src/staff_faqs_module.dart @@ -0,0 +1,51 @@ +import 'package:flutter_modular/flutter_modular.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( + '/', + 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..7582e056 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/support/faqs/pubspec.yaml @@ -0,0 +1,37 @@ +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 + go_router: ^14.0.0 + lucide_icons: ^0.257.0 + + # Architecture Packages + krow_core: + path: ../../../../../core + design_system: + path: ../../../../../design_system + core_localization: + path: ../../../../../core_localization + +dev_dependencies: + flutter_test: + sdk: flutter + bloc_test: ^9.1.0 + mocktail: ^1.0.0 + +flutter: + uses-material-design: true + assets: + - lib/src/assets/faqs/ 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..c5eaa978 100644 --- a/apps/mobile/pubspec.lock +++ b/apps/mobile/pubspec.lock @@ -573,6 +573,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3" + go_router: + dependency: transitive + description: + name: go_router + sha256: f02fd7d2a4dc512fec615529824fdd217fecb3a3d3de68360293a551f21634b3 + url: "https://pub.dev" + source: hosted + version: "14.8.1" google_fonts: dependency: transitive description: @@ -1290,10 +1298,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" From 8578723fb39ca2894598941e5c3256d897ea5c26 Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Wed, 18 Feb 2026 23:21:26 -0500 Subject: [PATCH 3/7] feat: Implement FAQs feature for staff application with updated routing and UI components --- .../lib/src/routing/staff/route_paths.dart | 4 +- .../design_system/lib/src/ui_icons.dart | 3 + .../pages/staff_profile_page.dart | 5 + .../profile_sections/support/faqs/README.md | 122 ------------------ .../lib/src/presentation/pages/faqs_page.dart | 87 ++----------- .../src/presentation/widgets/faqs_widget.dart | 42 +++--- .../faqs/lib/src/staff_faqs_module.dart | 3 +- .../support/faqs/pubspec.yaml | 8 -- .../staff_main/lib/src/staff_main_module.dart | 5 + apps/mobile/pubspec.lock | 8 -- .../client-mobile-application/architecture.md | 6 +- .../staff-mobile-application/architecture.md | 2 +- 12 files changed, 54 insertions(+), 241 deletions(-) delete mode 100644 apps/mobile/packages/features/staff/profile_sections/support/faqs/README.md 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/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/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..69a954b9 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 @@ -198,6 +198,11 @@ class StaffProfilePage extends StatelessWidget { ProfileMenuGrid( crossAxisCount: 3, children: [ + ProfileMenuItem( + icon: UiIcons.helpCircle, + label: i18n.header.title.contains("Perfil") ? "Preguntas Frecuentes" : "FAQs", + onTap: () => Modular.to.toFaqs(), + ), ProfileMenuItem( icon: UiIcons.shield, label: i18n.header.title.contains("Perfil") ? "Privacidad" : "Privacy & Security", diff --git a/apps/mobile/packages/features/staff/profile_sections/support/faqs/README.md b/apps/mobile/packages/features/staff/profile_sections/support/faqs/README.md deleted file mode 100644 index 5213dad3..00000000 --- a/apps/mobile/packages/features/staff/profile_sections/support/faqs/README.md +++ /dev/null @@ -1,122 +0,0 @@ -# Staff FAQs Feature - -A modular feature package providing Frequently Asked Questions functionality for the staff mobile application. - -## Architecture - -This package follows clean architecture principles with clear separation of concerns: - -### Domain Layer (`lib/src/domain/`) -- **Entities**: `FaqCategory`, `FaqItem` - Domain models representing FAQ data -- **Repositories**: `FaqsRepositoryInterface` - Abstract interface for FAQ data access -- **Use Cases**: - - `GetFaqsUseCase` - Retrieves all FAQs - - `SearchFaqsUseCase` - Searches FAQs by query - -### Data Layer (`lib/src/data/`) -- **Repositories Implementation**: `FaqsRepositoryImpl` - - Loads FAQs from JSON asset file (`lib/src/assets/faqs/faqs.json`) - - Implements caching to avoid repeated file reads - - Provides search filtering logic - -### Presentation Layer (`lib/src/presentation/`) -- **BLoC**: `FaqsBloc` - State management with events and states - - Events: `FetchFaqsEvent`, `SearchFaqsEvent` - - State: `FaqsState` - Manages categories, loading, search query, and errors -- **Pages**: `FaqsPage` - Full-screen FAQ view with AppBar and contact button -- **Widgets**: `FaqsWidget` - Reusable accordion widget with search functionality - -## Features - -✅ **Search Functionality** - Real-time search across questions and answers -✅ **Accordion UI** - Expandable/collapsible FAQ items -✅ **Category Organization** - FAQs grouped by category -✅ **Localization** - Support for English and Spanish -✅ **Contact Support Button** - Direct link to messaging/support -✅ **Empty State** - Helpful message when no results found -✅ **Loading State** - Loading indicator while fetching FAQs -✅ **Asset-based Data** - FAQ content stored in JSON for easy updates - -## Data Structure - -FAQs are stored in `lib/src/assets/faqs/faqs.json` with the following structure: - -```json -[ - { - "category": "Getting Started", - "questions": [ - { - "q": "How do I apply for shifts?", - "a": "Browse available shifts..." - } - ] - } -] -``` - -## Localization - -Localization strings are defined in: -- `packages/core_localization/lib/src/l10n/en.i18n.json` -- `packages/core_localization/lib/src/l10n/es.i18n.json` - -Available keys: -- `staff_faqs.title` - Page title -- `staff_faqs.search_placeholder` - Search input hint -- `staff_faqs.no_results` - Empty state message -- `staff_faqs.contact_support` - Button label - -## Dependency Injection - -The `FaqsModule` provides all dependencies: - -```dart -FaqsModule().binds(injector); -``` - -Registered singletons: -- `FaqsRepositoryInterface` → `FaqsRepositoryImpl` -- `GetFaqsUseCase` -- `SearchFaqsUseCase` -- `FaqsBloc` - -## Routing - -Routes are defined in `FaqsModule.routes()`: -- `/` → `FaqsPage` - -## Usage - -1. Add `FaqsModule` to your modular configuration -2. Access the page via routing: `context.push('/faqs')` -3. The BLoC will automatically fetch FAQs on page load - -## Asset Configuration - -Update `pubspec.yaml` to include assets: - -```yaml -flutter: - assets: - - lib/src/assets/faqs/ -``` - -## Testing - -The package includes test support with: -- `bloc_test` for BLoC testing -- `mocktail` for mocking dependencies - -## Design System Integration - -Uses the common design system components: -- `UiColors` - Color constants -- `UiConstants` - Sizing and radius constants -- `LucideIcons` - Icon library - -## Notes - -- FAQs are cached in memory after first load to improve performance -- Search is case-insensitive -- The widget state (expanded/collapsed items) is local to the widget and resets on navigation 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 index 9c1b77a1..1c99a9ab 100644 --- 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 @@ -3,8 +3,6 @@ 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 'package:go_router/go_router.dart'; -import 'package:lucide_icons/lucide_icons.dart'; import '../blocs/faqs_bloc.dart'; import '../widgets/faqs_widget.dart'; @@ -15,81 +13,20 @@ class FaqsPage extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocProvider( - create: (BuildContext context) => Modular.get()..add(const FetchFaqsEvent()), - child: Scaffold( - backgroundColor: UiColors.background, - appBar: AppBar( - backgroundColor: Colors.white, - elevation: 0, - leading: GestureDetector( - onTap: () => context.pop(), - child: const Icon( - LucideIcons.chevronLeft, - color: UiColors.textSecondary, - ), - ), - title: Text( - t.staff_faqs.title, - style: const TextStyle( - color: UiColors.textPrimary, - fontSize: 18, - fontWeight: FontWeight.w600, - ), - ), - bottom: PreferredSize( - preferredSize: const Size.fromHeight(1), - child: Container(color: UiColors.border, height: 1), - ), - ), - body: Stack( - children: [ - const FaqsWidget(), - // Contact Support Button at Bottom - Positioned( - left: 0, - right: 0, - bottom: 0, - child: Container( - padding: const EdgeInsets.all(20), - decoration: const BoxDecoration( - color: Colors.white, - border: Border(top: BorderSide(color: UiColors.border)), - ), - child: SafeArea( - top: false, - child: SizedBox( - width: double.infinity, - height: 48, - child: ElevatedButton( - onPressed: () => context.push('/messages'), - style: ElevatedButton.styleFrom( - backgroundColor: UiColors.primary, - foregroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(UiConstants.radiusBase), - ), - elevation: 0, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(LucideIcons.messageCircle, size: 20), - const SizedBox(width: 8), - Text( - t.staff_faqs.contact_support, - style: const TextStyle(fontWeight: FontWeight.w600), - ), - ], - ), - ), - ), - ), - ), - ), - ], + 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 index 317e607d..bda66591 100644 --- 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 @@ -2,7 +2,6 @@ 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:lucide_icons/lucide_icons.dart'; import 'package:staff_faqs/src/presentation/blocs/faqs_bloc.dart'; /// Widget displaying FAQs with search functionality and accordion items @@ -65,7 +64,7 @@ class _FaqsWidgetState extends State { hintText: t.staff_faqs.search_placeholder, hintStyle: const TextStyle(color: UiColors.textPlaceholder), prefixIcon: const Icon( - LucideIcons.search, + UiIcons.search, color: UiColors.textSecondary, ), border: InputBorder.none, @@ -87,7 +86,7 @@ class _FaqsWidgetState extends State { child: Column( children: [ const Icon( - LucideIcons.helpCircle, + UiIcons.helpCircle, size: 48, color: UiColors.textSecondary, ), @@ -100,7 +99,9 @@ class _FaqsWidgetState extends State { ), ) else - ...state.categories.asMap().entries.map((MapEntry entry) { + ...state.categories.asMap().entries.map(( + MapEntry entry, + ) { final int catIndex = entry.key; final dynamic categoryItem = entry.value; final String categoryName = categoryItem.category; @@ -118,7 +119,9 @@ class _FaqsWidgetState extends State { ), ), const SizedBox(height: 12), - ...questions.asMap().entries.map((MapEntry qEntry) { + ...questions.asMap().entries.map(( + MapEntry qEntry, + ) { final int qIndex = qEntry.key; final dynamic questionItem = qEntry.value; final String key = '$catIndex-$qIndex'; @@ -128,16 +131,18 @@ class _FaqsWidgetState extends State { margin: const EdgeInsets.only(bottom: 8), decoration: BoxDecoration( color: Colors.white, - borderRadius: - BorderRadius.circular(UiConstants.radiusBase), + borderRadius: BorderRadius.circular( + UiConstants.radiusBase, + ), border: Border.all(color: UiColors.border), ), child: Column( children: [ InkWell( onTap: () => _toggleItem(key), - borderRadius: - BorderRadius.circular(UiConstants.radiusBase), + borderRadius: BorderRadius.circular( + UiConstants.radiusBase, + ), child: Padding( padding: const EdgeInsets.all(16), child: Row( @@ -145,16 +150,13 @@ class _FaqsWidgetState extends State { Expanded( child: Text( questionItem.question, - style: const TextStyle( - fontWeight: FontWeight.w500, - color: UiColors.textPrimary, - ), + style: UiTypography.body1r, ), ), Icon( isOpen - ? LucideIcons.chevronUp - : LucideIcons.chevronDown, + ? UiIcons.chevronUp + : UiIcons.chevronDown, color: UiColors.textSecondary, size: 20, ), @@ -172,21 +174,17 @@ class _FaqsWidgetState extends State { ), child: Text( questionItem.answer, - style: const TextStyle( - color: UiColors.textSecondary, - fontSize: 14, - height: 1.5, - ), + style: UiTypography.body1r.textSecondary, ), ), ], ), ); - }).toList(), + }), const SizedBox(height: 12), ], ); - }).toList(), + }), ], ), ); 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 index f1a42142..6faf7c3a 100644 --- 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 @@ -1,4 +1,5 @@ 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'; @@ -44,7 +45,7 @@ class FaqsModule extends Module { @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/pubspec.yaml b/apps/mobile/packages/features/staff/profile_sections/support/faqs/pubspec.yaml index 7582e056..e50b0511 100644 --- a/apps/mobile/packages/features/staff/profile_sections/support/faqs/pubspec.yaml +++ b/apps/mobile/packages/features/staff/profile_sections/support/faqs/pubspec.yaml @@ -14,8 +14,6 @@ dependencies: flutter_bloc: ^8.1.0 flutter_modular: ^6.3.0 equatable: ^2.0.5 - go_router: ^14.0.0 - lucide_icons: ^0.257.0 # Architecture Packages krow_core: @@ -25,12 +23,6 @@ dependencies: core_localization: path: ../../../../../core_localization -dev_dependencies: - flutter_test: - sdk: flutter - bloc_test: ^9.1.0 - mocktail: ^1.0.0 - flutter: uses-material-design: true assets: 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/pubspec.lock b/apps/mobile/pubspec.lock index c5eaa978..d9afe13f 100644 --- a/apps/mobile/pubspec.lock +++ b/apps/mobile/pubspec.lock @@ -573,14 +573,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3" - go_router: - dependency: transitive - description: - name: go_router - sha256: f02fd7d2a4dc512fec615529824fdd217fecb3a3d3de68360293a551f21634b3 - url: "https://pub.dev" - source: hosted - version: "14.8.1" google_fonts: dependency: transitive description: 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"] From 3bda0cc0c3784f881e7096cfcc87042cbcfe1723 Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Wed, 18 Feb 2026 23:32:47 -0500 Subject: [PATCH 4/7] feat: Implement sections for compliance, finance, onboarding, settings, and support in staff profile --- .../lib/src/l10n/en.i18n.json | 6 +- .../lib/src/l10n/es.i18n.json | 6 +- .../pages/staff_profile_page.dart | 116 ++---------------- .../widgets/sections/compliance_section.dart | 39 ++++++ .../widgets/sections/finance_section.dart | 48 ++++++++ .../presentation/widgets/sections/index.dart | 5 + .../widgets/sections/onboarding_section.dart | 49 ++++++++ .../widgets/sections/settings_section.dart | 47 +++++++ .../widgets/sections/support_section.dart | 46 +++++++ 9 files changed, 253 insertions(+), 109 deletions(-) create mode 100644 apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/compliance_section.dart create mode 100644 apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/finance_section.dart create mode 100644 apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/index.dart create mode 100644 apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/onboarding_section.dart create mode 100644 apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/settings_section.dart create mode 100644 apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/support_section.dart 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 beef30fb..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", 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 80df41d0..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", 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 69a954b9..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,105 +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.helpCircle, - label: i18n.header.title.contains("Perfil") ? "Preguntas Frecuentes" : "FAQs", - onTap: () => Modular.to.toFaqs(), - ), - 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(), + ), + ], + ), + ], + ); + } +} From f0453f267b9e4d9bd0db5dc87c5a1c8b14fb2f3d Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Wed, 18 Feb 2026 23:43:33 -0500 Subject: [PATCH 5/7] feat: Update color definitions and improve PlaceholderBanner widget styling --- .../design_system/lib/src/ui_colors.dart | 4 +-- .../presentation/pages/worker_home_page.dart | 2 +- .../widgets/home_page/placeholder_banner.dart | 30 ++++++++++++------- 3 files changed, 23 insertions(+), 13 deletions(-) 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..b01c4b07 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(29, 10, 56, 223); /// Secondary background color (#F1F3F5) static const Color secondary = Color(0xFFF1F3F5); 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..f314656a 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 @@ -67,7 +67,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(); 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), ], ), ), From 963fc05f9f8f7232885f2becf22cb7c554f84fd4 Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Thu, 19 Feb 2026 00:39:24 -0500 Subject: [PATCH 6/7] fix: Correct primaryInverse color value and improve code formatting in WorkerHomePage --- .../design_system/lib/src/ui_colors.dart | 2 +- .../presentation/pages/worker_home_page.dart | 18 +++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) 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 b01c4b07..5bb0a5af 100644 --- a/apps/mobile/packages/design_system/lib/src/ui_colors.dart +++ b/apps/mobile/packages/design_system/lib/src/ui_colors.dart @@ -22,7 +22,7 @@ class UiColors { static const Color primaryForeground = Color(0xFFF7FAFC); /// Inverse primary color (#0A39DF) - static const Color primaryInverse = Color.fromARGB(29, 10, 56, 223); + 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/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 f314656a..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( @@ -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], ), From cb329938e35b171456ec6421fb23bf8b229fa259 Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Thu, 19 Feb 2026 00:47:58 -0500 Subject: [PATCH 7/7] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) 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.