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.
This commit is contained in:
Achintha Isuru
2026-02-18 22:21:18 -05:00
parent 11a9a7800c
commit 316a010779
20 changed files with 932 additions and 2 deletions

View File

@@ -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!",

View File

@@ -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!",

View File

@@ -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

View File

@@ -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."
}
]
}
]

View File

@@ -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<FaqCategory>? _cachedFaqs;
@override
Future<List<FaqCategory>> 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<dynamic> decoded = jsonDecode(faqsJson) as List<dynamic>;
// Convert to domain entities
_cachedFaqs = decoded.map((dynamic item) {
final Map<String, dynamic> category = item as Map<String, dynamic>;
final String categoryName = category['category'] as String;
final List<dynamic> questionsData =
category['questions'] as List<dynamic>;
final List<FaqItem> questions = questionsData.map((dynamic q) {
final Map<String, dynamic> questionMap = q as Map<String, dynamic>;
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 <FaqCategory>[];
}
}
@override
Future<List<FaqCategory>> searchFaqs(String query) async {
try {
// Get all FAQs first
final List<FaqCategory> allFaqs = await getFaqs();
if (query.isEmpty) {
return allFaqs;
}
final String lowerQuery = query.toLowerCase();
// Filter categories based on matching questions
final List<FaqCategory> filtered = allFaqs
.map((FaqCategory category) {
// Filter questions that match the query
final List<FaqItem> 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<FaqCategory>()
.toList();
return filtered;
} catch (e) {
return <FaqCategory>[];
}
}
}

View File

@@ -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<FaqItem> questions;
const FaqCategory({
required this.category,
required this.questions,
});
@override
List<Object?> get props => <Object?>[category, questions];
}

View File

@@ -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<Object?> get props => <Object?>[question, answer];
}

View File

@@ -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<List<FaqCategory>> getFaqs();
/// Search FAQs by query string
/// Returns categories that contain matching questions
Future<List<FaqCategory>> searchFaqs(String query);
}

View File

@@ -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<List<FaqCategory>> call() async {
try {
return await _repository.getFaqs();
} catch (e) {
// Return empty list on error
return <FaqCategory>[];
}
}
}

View File

@@ -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<List<FaqCategory>> call(SearchFaqsParams params) async {
try {
return await _repository.searchFaqs(params.query);
} catch (e) {
// Return empty list on error
return <FaqCategory>[];
}
}
}

View File

@@ -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<FaqsEvent, FaqsState> {
final GetFaqsUseCase _getFaqsUseCase;
final SearchFaqsUseCase _searchFaqsUseCase;
FaqsBloc({
required GetFaqsUseCase getFaqsUseCase,
required SearchFaqsUseCase searchFaqsUseCase,
}) : _getFaqsUseCase = getFaqsUseCase,
_searchFaqsUseCase = searchFaqsUseCase,
super(const FaqsState()) {
on<FetchFaqsEvent>(_onFetchFaqs);
on<SearchFaqsEvent>(_onSearchFaqs);
}
Future<void> _onFetchFaqs(
FetchFaqsEvent event,
Emitter<FaqsState> emit,
) async {
emit(state.copyWith(isLoading: true, error: null));
try {
final List<FaqCategory> 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<void> _onSearchFaqs(
SearchFaqsEvent event,
Emitter<FaqsState> emit,
) async {
emit(state.copyWith(isLoading: true, error: null, searchQuery: event.query));
try {
final List<FaqCategory> 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',
),
);
}
}
}

View File

@@ -0,0 +1,25 @@
part of 'faqs_bloc.dart';
/// Base class for FAQs BLoC events
abstract class FaqsEvent extends Equatable {
const FaqsEvent();
@override
List<Object?> get props => <Object?>[];
}
/// 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<Object?> get props => <Object?>[query];
}

View File

@@ -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<FaqCategory> 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 <FaqCategory>[],
this.isLoading = false,
this.searchQuery = '',
this.error,
});
/// Create a copy with optional field overrides
FaqsState copyWith({
List<FaqCategory>? 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<Object?> get props => <Object?>[
categories,
isLoading,
searchQuery,
error,
];
}

View File

@@ -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<FaqsBloc>(
create: (BuildContext context) => Modular.get<FaqsBloc>()..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: <Widget>[
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: <Widget>[
const Icon(LucideIcons.messageCircle, size: 20),
const SizedBox(width: 8),
Text(
t.staff_faqs.contact_support,
style: const TextStyle(fontWeight: FontWeight.w600),
),
],
),
),
),
),
),
),
],
),
),
);
}
}

View File

@@ -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<FaqsWidget> createState() => _FaqsWidgetState();
}
class _FaqsWidgetState extends State<FaqsWidget> {
late TextEditingController _searchController;
final Map<String, bool> _openItems = <String, bool>{};
@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<FaqsBloc>().add(const FetchFaqsEvent());
} else {
context.read<FaqsBloc>().add(SearchFaqsEvent(query: value));
}
}
@override
Widget build(BuildContext context) {
return BlocBuilder<FaqsBloc, FaqsState>(
builder: (BuildContext context, FaqsState state) {
return SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 100),
child: Column(
children: <Widget>[
// 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: <Widget>[
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<int, dynamic> entry) {
final int catIndex = entry.key;
final dynamic categoryItem = entry.value;
final String categoryName = categoryItem.category;
final List<dynamic> questions = categoryItem.questions;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
categoryName,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: UiColors.textPrimary,
),
),
const SizedBox(height: 12),
...questions.asMap().entries.map((MapEntry<int, dynamic> 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: <Widget>[
InkWell(
onTap: () => _toggleItem(key),
borderRadius:
BorderRadius.circular(UiConstants.radiusBase),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: <Widget>[
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(),
],
),
);
},
);
}
}

View File

@@ -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<FaqsRepositoryInterface>(
() => FaqsRepositoryImpl(),
);
// Use Cases
i.addSingleton(
() => GetFaqsUseCase(
i<FaqsRepositoryInterface>(),
),
);
i.addSingleton(
() => SearchFaqsUseCase(
i<FaqsRepositoryInterface>(),
),
);
// BLoC
i.add(
() => FaqsBloc(
getFaqsUseCase: i<GetFaqsUseCase>(),
searchFaqsUseCase: i<SearchFaqsUseCase>(),
),
);
}
@override
void routes(RouteManager r) {
r.child(
'/',
child: (_) => const FaqsPage(),
);
}
}

View File

@@ -0,0 +1,4 @@
library staff_faqs;
export 'src/staff_faqs_module.dart';
export 'src/presentation/pages/faqs_page.dart';

View File

@@ -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/

View File

@@ -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:

View File

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