This commit is contained in:
Phuoc Nguyen
2025-10-24 17:39:24 +07:00
parent 860a8788b6
commit 380ad60ee5

349
CLAUDE.md
View File

@@ -81,12 +81,92 @@ You have access to these expert subagents - USE THEM PROACTIVELY:
- Use Flutter 3.x features and Material 3 design
- Implement clean architecture with Riverpod for state management
- Use Hive for local database and offline functionality
- Follow proper dependency injection with Riverpod DI
- Follow proper dependency injection with Riverpod DI
- Implement proper error handling and user feedback
- Follow iOS and Android platform-specific design guidelines
- Support Vietnamese language (primary) and English (secondary)
- Mobile-first design optimized for phone screens
### 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);
}
```
**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`):
```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
],
)
```
**Key Requirements**:
- Black back arrow with explicit color
- Black text title with TextStyle
- Left-aligned title (`centerTitle: false`)
- White background (`AppColors.white`)
- 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),
],
)
```
---
## Worker App Project Structure
@@ -593,6 +673,10 @@ final otpTimerProvider = StateNotifierProvider<OTPTimerNotifier, int>
- **Quick Action Grid**:
- 4-6 icon buttons for main features
- Products, Loyalty, Orders, Projects, Promotions
- **Cart badge**: Dynamic count from `cartItemCountProvider`
- Shows cart item count when > 0
- Hidden when cart is empty
- Updates in real-time across all pages
- **Bottom Navigation**:
- 5 tabs: Home, Products, Loyalty, Account, More
@@ -657,27 +741,55 @@ final loyaltyPointsProvider = AsyncNotifierProvider<LoyaltyPointsNotifier, Loyal
---
#### Rewards Page
#### Rewards Page ✅ IMPLEMENTED
**Page**: `rewards_page.dart`
**Features**:
- Grid of available rewards to redeem
- Filter by category (Vouchers/Products/Services)
- Reward cards showing:
- Image
- Title and description
- Points cost
- Expiration date
- Redeem button (disabled if insufficient points)
- Redemption confirmation dialog
- Success animation and gift code display
- **Points Balance Card**: Gradient blue card showing available points and expiring points
- **Category Filter Pills**: Horizontal scrollable chips (Tất cả/Voucher/Sản phẩm/Dịch vụ/Ưu đãi đặc biệt)
- **Gift Grid**: 2-column grid with 6 mock gifts
- **Reward Cards** (improved UI):
- 120px image with CachedNetworkImage
- Title and description (truncated)
- Points cost and button **at bottom** (using Spacer())
- Button states: "Đổi quà" (enabled) or "Không đủ điểm" (disabled)
- **Redemption Flow**:
- Confirmation dialog with gift details and balance after redemption
- Success SnackBar with checkmark icon
- Points deduction via `loyaltyPointsProvider`
**Widgets**:
- `RewardCard`: Card with image, info, and redeem button
- `RewardRedemptionDialog`: Confirmation dialog
- `GiftCodeDisplay`: Copy-able gift code after redemption
**Widgets** (Location: `lib/features/loyalty/presentation/`):
- `widgets/points_balance_card.dart`: Gradient card with points display
- `widgets/reward_card.dart`: Individual gift card with bottom-aligned action
- `pages/rewards_page.dart`: Main rewards screen
**Design Reference**: `html/loyalty-rewards.html`
**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>...
```
**Navigation**:
- Route: `/loyalty/rewards` (RouteNames in app_router.dart)
- Accessible from home quick action "Đổi quà"
**Design Reference**: `html/loyalty-rewards.html`
---
@@ -1275,6 +1387,46 @@ class FABSpecs {
}
```
#### 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
- Back arrow is always black with explicit color
- Title is always left-aligned (`centerTitle: false`)
- Title text is always black
- Background is always white
- Actions always end with `SizedBox(width: AppSpacing.sm)` spacing
- For SliverAppBar, add `pinned: true` property
---
## Technical Architecture
@@ -1761,16 +1913,32 @@ end
## Roadmap
### Phase 1 - Complete
- Authentication (login, OTP, register)
- Home with member cards (3 tiers)
- Complete loyalty system
- Product browsing and cart
- Order management
- Project and quote management
- Chat support
- Account management
- Promotions and notifications
### Phase 1 - Core Features
- Authentication (login, OTP, register)
- Home with member cards (3 tiers)
- ✅ Product browsing with search and filters
- ✅ Shopping cart with dynamic badge
- ✅ Favorites system with Hive persistence
- ✅ Order management
- ✅ Project and quote management
- Account management
- Promotions with detail pages
### Phase 1.5 - Loyalty System ✅ RECENT
-**Rewards/Gift Redemption Page** (Dec 2024)
- Points balance card with gradient
- Category filtering (Voucher/Product/Service/Discount)
- 2-column gift grid with 6 mock items
- Redemption flow with confirmation dialog
- Points deduction and success feedback
-**UI Standardization** (Dec 2024)
- All AppBars now follow consistent pattern
- Dynamic cart badge across all pages
- Hive Box<dynamic> best practices documented
- 🔄 Points history page (planned)
- 🔄 Referral program page (planned)
- 🔄 My gifts page (planned)
- 🔄 Chat support (planned)
### Phase 2 - Backend Integration 🔄
- REST API integration
@@ -1803,3 +1971,130 @@ When working on this Flutter Worker app:
- **Performance Issues** → performance-expert
**Think delegation first, implementation second!**
---
## Recent Updates & Implementation Notes (December 2024)
### ✅ Rewards/Gift Redemption System
**Status**: Fully implemented with mock data
**Implementation Details**:
1. **Providers** (`lib/features/loyalty/presentation/providers/`):
- `loyalty_points_provider.dart` - Manages user points (9,750 available, 1,200 expiring)
- `gifts_provider.dart` - 6 mock gifts matching HTML design
- `selectedGiftCategoryProvider` - Category filter state
- `filteredGiftsProvider` - Category-based filtering
- `hasEnoughPointsProvider.family` - Points validation per gift
2. **Widgets** (`lib/features/loyalty/presentation/widgets/`):
- `points_balance_card.dart` - Gradient blue card with points display
- `reward_card.dart` - Gift card with bottom-aligned action button
3. **Pages** (`lib/features/loyalty/presentation/pages/`):
- `rewards_page.dart` - Main screen with RefreshIndicator
4. **Route**: `/loyalty/rewards` in `app_router.dart`
**Key Features**:
- 2-column grid layout with `childAspectRatio: 0.7`
- Category filtering: Tất cả/Voucher/Sản phẩm/Dịch vụ/Ưu đãi đặc biệt
- Bottom-aligned buttons using `Expanded` + `Spacer()`
- Redemption dialog with points calculation
- Success feedback with SnackBar
### ✅ 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)],
)
```
**Updated Pages**:
- `cart_page.dart` - Lines 84-103
- `favorites_page.dart` - Lines 79-115
- `rewards_page.dart` - Lines 35-48
- `promotion_detail_page.dart` - Lines 59-86 (loading/error), 130-161 (SliverAppBar)
- `product_detail_page.dart` - Already correct ✅
### ✅ 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,
)
```
**Behavior**:
- Shows total quantity across all cart items
- Hidden when cart is empty
- Updates in real-time via Riverpod
- Navigation via `RouteNames.cart` constant
### ✅ 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>
```
**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();
}
```
### 🔄 Next Steps (Planned)
1. Points history page with transaction list
2. Referral program page with share functionality
3. My gifts page with tabs (Active/Used/Expired)
4. Backend API integration for loyalty endpoints
5. Real redemption flow with gift codes
### 📝 Code Quality Checklist
All recent implementations follow:
- ✅ Clean architecture (data/domain/presentation)
- ✅ Riverpod state management
- ✅ Hive for local persistence
- ✅ Material 3 design system
- ✅ Vietnamese localization
- ✅ AppBar standardization
- ✅ CachedNetworkImage for all remote images
- ✅ Proper error handling
- ✅ Loading states (CircularProgressIndicator)
- ✅ Empty states with helpful messages