add price policy

This commit is contained in:
Phuoc Nguyen
2025-11-03 11:20:09 +07:00
parent c0527a086c
commit 21c1c3372c
53 changed files with 7160 additions and 2361 deletions

718
CLAUDE.md
View File

@@ -19,6 +19,16 @@ A Flutter-based mobile application designed for contractors, distributors, archi
### 📁 Reference Materials:
The `html/` folder contains UI/UX reference mockups that show the desired design and flow. These HTML files serve as design specifications for the Flutter implementation.
### 📝 Code Examples:
All Dart code examples, patterns, and snippets are maintained in **CODE_EXAMPLES.md**. Refer to that document for:
- Best practices (Hive, AppBar standardization)
- UI/UX components (colors, typography, specs)
- State management patterns
- Performance optimization
- Offline strategies
- Localization setup
- Deployment configurations
---
## 🤖 SUBAGENT DELEGATION SYSTEM 🤖
@@ -89,55 +99,14 @@ You have access to these expert subagents - USE THEM PROACTIVELY:
### Hive Best Practices
**IMPORTANT: Box Type Management**
When working with Hive boxes, always use `Box<dynamic>` in data sources and apply `.whereType<T>()` for type-safe queries:
```dart
// ✅ CORRECT - Use Box<dynamic> with type filtering
Box<dynamic> get _box {
return Hive.box<dynamic>(HiveBoxNames.favoriteBox);
}
Future<List<FavoriteModel>> getAllFavorites(String userId) async {
try {
final favorites = _box.values
.whereType<FavoriteModel>() // Type-safe filtering
.where((fav) => fav.userId == userId)
.toList();
return favorites;
} catch (e) {
debugPrint('[DataSource] Error: $e');
rethrow;
}
}
// ❌ INCORRECT - Will cause HiveError
Box<FavoriteModel> get _box {
return Hive.box<FavoriteModel>(HiveBoxNames.favoriteBox);
}
```
When working with Hive boxes, always use `Box<dynamic>` in data sources and apply `.whereType<T>()` for type-safe queries.
**Reason**: Hive boxes are opened as `Box<dynamic>` in the central HiveService. Re-opening with a specific type causes `HiveError: The box is already open and of type Box<dynamic>`.
### AppBar Standardization
**ALL AppBars must follow this standard pattern** (reference: `products_page.dart`):
**See CODE_EXAMPLES.md → Best Practices → Hive Box Type Management** for correct and incorrect patterns.
```dart
AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.black),
onPressed: () => context.pop(),
),
title: const Text('Page Title', style: TextStyle(color: Colors.black)),
elevation: AppBarSpecs.elevation,
backgroundColor: AppColors.white,
foregroundColor: AppColors.grey900,
centerTitle: false,
actions: [
// Custom actions here
const SizedBox(width: AppSpacing.sm), // Always end with spacing
],
)
```
### AppBar Standardization
**ALL AppBars must follow this standard pattern** (reference: `products_page.dart`).
**Key Requirements**:
- Black back arrow with explicit color
@@ -147,25 +116,7 @@ AppBar(
- Use `AppBarSpecs.elevation` (not hardcoded values)
- Always add `SizedBox(width: AppSpacing.sm)` after actions
**For SliverAppBar** (in CustomScrollView):
```dart
SliverAppBar(
pinned: true,
backgroundColor: AppColors.white,
foregroundColor: AppColors.grey900,
elevation: AppBarSpecs.elevation,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.black),
onPressed: () => context.pop(),
),
title: const Text('Page Title', style: TextStyle(color: Colors.black)),
centerTitle: false,
actions: [
// Custom actions
const SizedBox(width: AppSpacing.sm),
],
)
```
**See CODE_EXAMPLES.md → Best Practices → AppBar Standardization** for standard AppBar and SliverAppBar patterns.
---
@@ -641,11 +592,7 @@ The `html/` folder contains 25+ HTML mockup files that serve as UI/UX design ref
- Full registration form with user type selection
- Form validation for all fields
**State Management**:
```dart
final authProvider = AsyncNotifierProvider<AuthNotifier, AuthState>
final otpTimerProvider = StateNotifierProvider<OTPTimerNotifier, int>
```
**State Management**: See **CODE_EXAMPLES.md → State Management → Authentication Providers**
**Key Widgets**:
- `PhoneInputField`: Vietnamese phone number format (+84)
@@ -688,19 +635,7 @@ final otpTimerProvider = StateNotifierProvider<OTPTimerNotifier, int>
- Positioned bottom-right
- Accent cyan color (#35C6F4)
**State Management**:
```dart
final memberCardProvider = Provider<MemberCard>((ref) {
final user = ref.watch(authProvider).user;
return MemberCard(
tier: user.memberTier,
name: user.name,
memberId: user.id,
points: user.points,
qrCode: generateQRCode(user.id),
);
});
```
**State Management**: See **CODE_EXAMPLES.md → State Management → Home Providers**
**Design Reference**: `html/index.html`
@@ -732,10 +667,7 @@ final memberCardProvider = Provider<MemberCard>((ref) {
- `PointsBadge`: Circular points display
- `TierBenefitsCard`: Expandable card with benefits list
**State Management**:
```dart
final loyaltyPointsProvider = AsyncNotifierProvider<LoyaltyPointsNotifier, LoyaltyPoints>
```
**State Management**: See **CODE_EXAMPLES.md → State Management → Loyalty Providers**
**Design Reference**: `html/loyalty.html`
@@ -763,27 +695,7 @@ final loyaltyPointsProvider = AsyncNotifierProvider<LoyaltyPointsNotifier, Loyal
- `widgets/reward_card.dart`: Individual gift card with bottom-aligned action
- `pages/rewards_page.dart`: Main rewards screen
**State Management**:
```dart
// Providers in lib/features/loyalty/presentation/providers/
@riverpod
class LoyaltyPoints extends _$LoyaltyPoints {
// Manages 9,750 available points, 1,200 expiring
}
@riverpod
class Gifts extends _$Gifts {
// 6 mock gifts matching HTML design
}
@riverpod
List<GiftCatalog> filteredGifts(ref) {
// Filters by selected category
}
final selectedGiftCategoryProvider = StateNotifierProvider...
final hasEnoughPointsProvider = Provider.family<bool, int>...
```
**State Management**: See **CODE_EXAMPLES.md → State Management → Loyalty Providers → Rewards Page Providers**
**Navigation**:
- Route: `/loyalty/rewards` (RouteNames in app_router.dart)
@@ -836,10 +748,7 @@ final hasEnoughPointsProvider = Provider.family<bool, int>...
- `ReferralLinkShare`: Link with copy/share buttons
- `ReferralShareSheet`: Bottom sheet with share options
**State Management**:
```dart
final referralProvider = AsyncNotifierProvider<ReferralNotifier, Referral>
```
**State Management**: See **CODE_EXAMPLES.md → State Management → Referral Provider**
**Design Reference**: `html/referral.html`
@@ -894,12 +803,7 @@ final referralProvider = AsyncNotifierProvider<ReferralNotifier, Referral>
- `ProductSearchBar`: Search with clear button
- `CategoryFilterChips`: Horizontal chip list
**State Management**:
```dart
final productsProvider = AsyncNotifierProvider<ProductsNotifier, List<Product>>
final productSearchProvider = StateProvider<String>
final selectedCategoryProvider = StateProvider<String?>
```
**State Management**: See **CODE_EXAMPLES.md → State Management → Products Providers**
**Design Reference**: `html/products.html`
@@ -938,11 +842,7 @@ final selectedCategoryProvider = StateProvider<String?>
- Order details summary
- Action buttons: View order, Continue shopping
**State Management**:
```dart
final cartProvider = NotifierProvider<CartNotifier, List<CartItem>>
final cartTotalProvider = Provider<double>
```
**State Management**: See **CODE_EXAMPLES.md → State Management → Cart Providers**
**Design Reference**: `html/cart.html`, `html/checkout.html`, `html/order-success.html`
@@ -985,12 +885,7 @@ final cartTotalProvider = Provider<double>
- Status (Processing/Completed)
- Search and filter options
**State Management**:
```dart
final ordersProvider = AsyncNotifierProvider<OrdersNotifier, List<Order>>
final orderFilterProvider = StateProvider<OrderStatus?>
final paymentsProvider = AsyncNotifierProvider<PaymentsNotifier, List<Payment>>
```
**State Management**: See **CODE_EXAMPLES.md → State Management → Orders Providers**
**Design Reference**: `html/orders.html`, `html/payments.html`
@@ -1029,11 +924,7 @@ final paymentsProvider = AsyncNotifierProvider<PaymentsNotifier, List<Payment>>
- Date pickers
- Auto-generate project code option
**State Management**:
```dart
final projectsProvider = AsyncNotifierProvider<ProjectsNotifier, List<Project>>
final projectFormProvider = StateNotifierProvider<ProjectFormNotifier, ProjectFormState>
```
**State Management**: See **CODE_EXAMPLES.md → State Management → Projects Providers**
**Design Reference**: `html/projects.html`, `html/project-create.html`
@@ -1084,12 +975,7 @@ final projectFormProvider = StateNotifierProvider<ProjectFormNotifier, ProjectFo
- `TypingIndicator`: Animated dots
- `ChatAppBar`: Custom app bar with agent info
**State Management**:
```dart
final chatProvider = AsyncNotifierProvider<ChatNotifier, ChatRoom>
final messagesProvider = StreamProvider<List<Message>>
final typingIndicatorProvider = StateProvider<bool>
```
**State Management**: See **CODE_EXAMPLES.md → State Management → Chat Providers**
**Design Reference**: `html/chat.html`
@@ -1240,186 +1126,30 @@ final typingIndicatorProvider = StateProvider<bool>
## UI/UX Design System
### Color Palette
```dart
// colors.dart
class AppColors {
// Primary
static const primaryBlue = Color(0xFF005B9A);
static const lightBlue = Color(0xFF38B6FF);
static const accentCyan = Color(0xFF35C6F4);
// Status
static const success = Color(0xFF28a745);
static const warning = Color(0xFFffc107);
static const danger = Color(0xFFdc3545);
static const info = Color(0xFF17a2b8);
// Neutrals
static const grey50 = Color(0xFFf8f9fa);
static const grey100 = Color(0xFFe9ecef);
static const grey500 = Color(0xFF6c757d);
static const grey900 = Color(0xFF343a40);
// Tier Gradients
static const diamondGradient = LinearGradient(
colors: [Color(0xFF4A00E0), Color(0xFF8E2DE2)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
static const platinumGradient = LinearGradient(
colors: [Color(0xFF7F8C8D), Color(0xFFBDC3C7)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
static const goldGradient = LinearGradient(
colors: [Color(0xFFf7b733), Color(0xFFfc4a1a)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
}
```
See **CODE_EXAMPLES.md → UI/UX Components → Color Palette** for the complete color system including:
- Primary colors (blue shades)
- Status colors (success, warning, danger, info)
- Neutral grays
- Tier gradients (Diamond, Platinum, Gold)
### Typography
```dart
// typography.dart
class AppTypography {
static const fontFamily = 'Roboto';
static const displayLarge = TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
fontFamily: fontFamily,
);
static const headlineLarge = TextStyle(
fontSize: 24,
fontWeight: FontWeight.w600,
fontFamily: fontFamily,
);
static const titleLarge = TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
fontFamily: fontFamily,
);
static const bodyLarge = TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
fontFamily: fontFamily,
);
static const labelSmall = TextStyle(
fontSize: 12,
fontWeight: FontWeight.normal,
fontFamily: fontFamily,
);
}
```
See **CODE_EXAMPLES.md → UI/UX Components → Typography** for text styles:
- Display, headline, title, body, and label styles
- Roboto font family
- Font sizes and weights
### Component Specifications
#### Member Card Design
```dart
class MemberCardSpecs {
static const double width = double.infinity;
static const double height = 200;
static const double borderRadius = 16;
static const double elevation = 8;
static const EdgeInsets padding = EdgeInsets.all(20);
All component specifications are documented in **CODE_EXAMPLES.md → UI/UX Components**:
// QR Code
static const double qrSize = 80;
static const double qrBackgroundSize = 90;
- **Member Card Design**: Dimensions, padding, QR code specs, points display
- **Status Badges**: Color mapping for order statuses
- **Bottom Navigation**: Heights, icon sizes, colors
- **Floating Action Button**: Size, elevation, colors, position
- **AppBar Specifications**: Standard pattern with helper method
// Points Display
static const double pointsFontSize = 28;
static const FontWeight pointsFontWeight = FontWeight.bold;
}
```
#### Status Badges
```dart
class StatusBadge extends StatelessWidget {
final String status;
final Color color;
static Color getColorForStatus(OrderStatus status) {
switch (status) {
case OrderStatus.pending:
return AppColors.info;
case OrderStatus.processing:
return AppColors.warning;
case OrderStatus.shipping:
return AppColors.lightBlue;
case OrderStatus.completed:
return AppColors.success;
case OrderStatus.cancelled:
return AppColors.danger;
}
}
}
```
#### Bottom Navigation
```dart
class BottomNavSpecs {
static const double height = 72;
static const double iconSize = 24;
static const double selectedIconSize = 28;
static const double labelFontSize = 12;
static const Color selectedColor = AppColors.primaryBlue;
static const Color unselectedColor = AppColors.grey500;
}
```
#### Floating Action Button
```dart
class FABSpecs {
static const double size = 56;
static const double elevation = 6;
static const Color backgroundColor = AppColors.accentCyan;
static const Color iconColor = Colors.white;
static const double iconSize = 24;
static const Offset position = Offset(16, 16); // from bottom-right
}
```
#### AppBar (Standardized across all pages)
```dart
class AppBarSpecs {
// From ui_constants.dart
static const double elevation = 0.5;
// Standard pattern for all pages
static AppBar standard({
required String title,
required VoidCallback onBack,
List<Widget>? actions,
}) {
return AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.black),
onPressed: onBack,
),
title: Text(title, style: const TextStyle(color: Colors.black)),
elevation: elevation,
backgroundColor: AppColors.white,
foregroundColor: AppColors.grey900,
centerTitle: false,
actions: [
...?actions,
const SizedBox(width: AppSpacing.sm),
],
);
}
}
```
**Usage Notes**:
- ALL pages use this standard AppBar pattern
**AppBar Usage Notes**:
- ALL pages use the standard AppBar pattern
- Back arrow is always black with explicit color
- Title is always left-aligned (`centerTitle: false`)
- Title text is always black
@@ -1433,38 +1163,10 @@ class AppBarSpecs {
### State Management (Riverpod 2.x)
#### Authentication State
```dart
@riverpod
class Auth extends _$Auth {
@override
Future<AuthState> build() async {
final token = await _getStoredToken();
if (token != null) {
final user = await _getUserFromToken(token);
return AuthState.authenticated(user);
}
return const AuthState.unauthenticated();
}
Future<void> loginWithPhone(String phone) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
await ref.read(authRepositoryProvider).requestOTP(phone);
return AuthState.otpSent(phone);
});
}
Future<void> verifyOTP(String phone, String otp) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
final response = await ref.read(authRepositoryProvider).verifyOTP(phone, otp);
await _storeToken(response.token);
return AuthState.authenticated(response.user);
});
}
}
```
All state management patterns and implementations are documented in **CODE_EXAMPLES.md → State Management**, including:
- Authentication State with phone login and OTP verification
- All feature-specific providers (Home, Loyalty, Products, Cart, Orders, Projects, Chat)
- Provider patterns and best practices
### Domain Entities & Data Models
@@ -1597,292 +1299,39 @@ All enums are defined in `lib/core/database/models/enums.dart` with Hive type ad
## Performance Optimization
### Image Caching
```dart
// Use cached_network_image for all remote images
CachedNetworkImage(
imageUrl: product.images.first,
placeholder: (context, url) => const ShimmerPlaceholder(),
errorWidget: (context, url, error) => const Icon(Icons.error),
fit: BoxFit.cover,
memCacheWidth: 400, // Optimize memory usage
fadeInDuration: const Duration(milliseconds: 300),
)
```
All performance optimization patterns are documented in **CODE_EXAMPLES.md → Performance Optimization**:
### List Performance
```dart
// Use ListView.builder with RepaintBoundary for long lists
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return RepaintBoundary(
child: ProductCard(product: items[index]),
);
},
cacheExtent: 1000, // Pre-render items
)
// Use AutomaticKeepAliveClientMixin for expensive widgets
class ProductCard extends StatefulWidget {
@override
State<ProductCard> createState() => _ProductCardState();
}
class _ProductCardState extends State<ProductCard>
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
return Card(...);
}
}
```
### State Optimization
```dart
// Use .select() to avoid unnecessary rebuilds
final userName = ref.watch(authProvider.select((state) => state.user?.name));
// Use family modifiers for parameterized providers
@riverpod
Future<Product> product(ProductRef ref, String id) async {
return await ref.read(productRepositoryProvider).getProduct(id);
}
// Keep providers outside build method
final productsProvider = ...;
class ProductsPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final products = ref.watch(productsProvider);
return ...;
}
}
```
- **Image Caching**: Using CachedNetworkImage with proper configuration
- **List Performance**: RepaintBoundary, AutomaticKeepAliveClientMixin, cacheExtent
- **State Optimization**: Using .select(), family modifiers, provider best practices
---
## Offline Strategy
### Data Sync Flow
```dart
@riverpod
class DataSync extends _$DataSync {
@override
Future<SyncStatus> build() async {
// Listen to connectivity changes
ref.listen(connectivityProvider, (previous, next) {
if (next == ConnectivityStatus.connected) {
syncData();
}
});
All offline strategy patterns are documented in **CODE_EXAMPLES.md → Offline Strategy**:
return SyncStatus.idle;
}
Future<void> syncData() async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
// Sync in order of dependency
await _syncUserData();
await _syncProducts();
await _syncOrders();
await _syncProjects();
await _syncLoyaltyData();
await ref.read(settingsRepositoryProvider).updateLastSyncTime();
return SyncStatus.success;
});
}
Future<void> _syncUserData() async {
final user = await ref.read(authRepositoryProvider).getCurrentUser();
await ref.read(authLocalDataSourceProvider).saveUser(user);
}
Future<void> _syncProducts() async {
final products = await ref.read(productRepositoryProvider).getAllProducts();
await ref.read(productLocalDataSourceProvider).saveProducts(products);
}
// ... other sync methods
}
```
### Offline Queue
```dart
// Queue failed requests for retry when online
class OfflineQueue {
final HiveInterface hive;
late Box<Map> _queueBox;
Future<void> init() async {
_queueBox = await hive.openBox('offline_queue');
}
Future<void> addToQueue(ApiRequest request) async {
await _queueBox.add({
'endpoint': request.endpoint,
'method': request.method,
'body': request.body,
'timestamp': DateTime.now().toIso8601String(),
});
}
Future<void> processQueue() async {
final requests = _queueBox.values.toList();
for (var i = 0; i < requests.length; i++) {
try {
await _executeRequest(requests[i]);
await _queueBox.deleteAt(i);
} catch (e) {
// Keep in queue for next retry
}
}
}
}
```
- **Data Sync Flow**: Complete sync implementation with connectivity monitoring
- **Offline Queue**: Request queuing system for failed API calls
## Localization (Vietnamese Primary)
### Setup
```dart
// l10n.yaml
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
All localization setup and usage examples are documented in **CODE_EXAMPLES.md → Localization**:
// lib/l10n/app_vi.arb (Vietnamese)
{
"@@locale": "vi",
"appTitle": "Worker App",
"login": "Đăng nhập",
"phone": "Số điện thoại",
"enterPhone": "Nhập số điện thoại",
"continue": "Tiếp tục",
"verifyOTP": "Xác thực OTP",
"enterOTP": "Nhập mã OTP 6 số",
"resendOTP": "Gửi lại mã",
"home": "Trang chủ",
"products": "Sản phẩm",
"loyalty": "Hội viên",
"account": "Tài khoản",
"points": "Điểm",
"cart": "Giỏ hàng",
"checkout": "Thanh toán",
"orders": "Đơn hàng",
"projects": "Công trình",
"quotes": "Báo giá",
"myGifts": "Quà của tôi",
"referral": "Giới thiệu bạn bè",
"pointsHistory": "Lịch sử điểm"
}
// lib/l10n/app_en.arb (English)
{
"@@locale": "en",
"appTitle": "Worker App",
"login": "Login",
"phone": "Phone Number",
"enterPhone": "Enter phone number",
"continue": "Continue",
...
}
```
### Usage
```dart
class LoginPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(
title: Text(l10n.login),
),
body: Column(
children: [
TextField(
decoration: InputDecoration(
labelText: l10n.phone,
hintText: l10n.enterPhone,
),
),
ElevatedButton(
onPressed: () {},
child: Text(l10n.continue),
),
],
),
);
}
}
```
- **Setup**: l10n.yaml configuration, Vietnamese and English .arb files
- **Usage**: LoginPage example showing how to use AppLocalizations in widgets
---
## Deployment
### Android
```gradle
// android/app/build.gradle
android {
compileSdkVersion 34
All deployment configurations are documented in **CODE_EXAMPLES.md → Deployment**:
defaultConfig {
applicationId "com.eurotile.worker"
minSdkVersion 21
targetSdkVersion 34
versionCode 1
versionName "1.0.0"
}
signingConfigs {
release {
storeFile file(RELEASE_STORE_FILE)
storePassword RELEASE_STORE_PASSWORD
keyAlias RELEASE_KEY_ALIAS
keyPassword RELEASE_KEY_PASSWORD
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
```
### iOS
```ruby
# ios/Podfile
platform :ios, '13.0'
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
end
end
end
```
- **Android**: build.gradle configuration with signing and build settings
- **iOS**: Podfile configuration with deployment target settings
---
@@ -2006,21 +1455,7 @@ When working on this Flutter Worker app:
### ✅ AppBar Standardization
**Status**: Completed across all pages
**Standard Pattern**:
```dart
AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.black),
onPressed: () => context.pop(),
),
title: const Text('Title', style: TextStyle(color: Colors.black)),
elevation: AppBarSpecs.elevation,
backgroundColor: AppColors.white,
foregroundColor: AppColors.grey900,
centerTitle: false,
actions: [..., const SizedBox(width: AppSpacing.sm)],
)
```
See **CODE_EXAMPLES.md → Best Practices → AppBar Standardization** for the standard pattern.
**Updated Pages**:
- `cart_page.dart` - Lines 84-103
@@ -2032,21 +1467,7 @@ AppBar(
### ✅ Dynamic Cart Badge
**Status**: Implemented across home and products pages
**Implementation**:
```dart
// Added provider in cart_provider.dart
@riverpod
int cartItemCount(CartItemCountRef ref) {
final cartState = ref.watch(cartProvider);
return cartState.items.fold(0, (sum, item) => sum + item.quantity);
}
// Used in home_page.dart and products_page.dart
final cartItemCount = ref.watch(cartItemCountProvider);
QuickAction(
badge: cartItemCount > 0 ? '$cartItemCount' : null,
)
```
See **CODE_EXAMPLES.md → State Management → Cart Providers → Dynamic Cart Badge** for the implementation.
**Behavior**:
- Shows total quantity across all cart items
@@ -2057,27 +1478,14 @@ QuickAction(
### ✅ Hive Box Type Management
**Status**: Best practices documented and implemented
**Problem Solved**:
```
HiveError: The box "favorite_box" is already open and of type Box<dynamic>
```
**Problem Solved**: `HiveError: The box "favorite_box" is already open and of type Box<dynamic>`
**Solution Applied**:
- All data sources now use `Box<dynamic>` getters
- Type-safe queries via `.whereType<T>()`
- Applied to `favorites_local_datasource.dart`
**Pattern**:
```dart
Box<dynamic> get _box => Hive.box<dynamic>(boxName);
Future<List<FavoriteModel>> getAllFavorites() async {
return _box.values
.whereType<FavoriteModel>() // Type-safe!
.where((fav) => fav.userId == userId)
.toList();
}
```
See **CODE_EXAMPLES.md → Best Practices → Hive Box Type Management** for the correct pattern.
### 🔄 Next Steps (Planned)
1. Points history page with transaction list