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"]