ionic-planb-logistic-app-fl.../CLAUDE.md
Claude Code 4b03e9aba5 Initial commit: Plan B Logistics Flutter app with dark mode and responsive design
Implements complete refactor of Ionic Angular logistics app to Flutter/Dart with:
- Svrnty dark mode console theme (Material Design 3)
- Responsive layouts (mobile, tablet, desktop) following FRONTEND standards
- CQRS API integration with Result<T> error handling
- OAuth2/OIDC authentication support (mocked for initial testing)
- Delivery route and delivery management features
- Multi-language support (EN/FR) with i18n
- Native integrations (camera, phone calls, maps)
- Strict typing throughout codebase
- Mock data for UI testing without backend

Follows all FRONTEND style guides, design patterns, and conventions.
App is running in dark mode and fully responsive across all device sizes.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-31 04:58:10 -04:00

426 lines
11 KiB
Markdown

# CLAUDE.md - Plan B Logistics Flutter App
This file provides guidance to Claude Code when working with this Flutter/Dart project.
## Project Overview
Plan B Logistics Flutter is a complete refactor of the Ionic Angular delivery management app into Flutter/Dart, maintaining all functionality while applying Svrnty design system (colors, typography, and Material Design 3).
**Key Features:**
- OAuth2/OIDC authentication with Keycloak
- CQRS pattern for API integration with Result<T> error handling
- Delivery route and delivery management
- Photo upload for delivery proof
- i18n support (French/English)
- Native features: Camera, Phone calls, Maps
## Essential Commands
### Setup & Dependencies
```bash
flutter pub get
flutter pub upgrade
flutter pub run build_runner build --delete-conflicting-outputs
```
### Development
```bash
flutter run -d chrome # Web
flutter run -d macos # macOS
flutter run -d ios # iOS simulator
flutter run -d android # Android emulator
```
### Testing & Analysis
```bash
flutter test
flutter analyze
flutter test --coverage
```
### Build
```bash
flutter build web
flutter build ios
flutter build android
```
## Code Architecture
### Core Structure
```
lib/
├── api/ # CQRS API client and types
│ ├── types.dart # Result<T>, Serializable, ApiError
│ ├── client.dart # CqrsApiClient implementation
│ └── openapi_config.dart
├── models/ # Data models (strict typing)
│ ├── delivery.dart
│ ├── delivery_route.dart
│ ├── user_profile.dart
│ └── ...
├── services/ # Business logic
│ └── auth_service.dart
├── providers/ # Riverpod state management
│ └── providers.dart
├── pages/ # Screen widgets
│ ├── login_page.dart
│ ├── routes_page.dart
│ ├── deliveries_page.dart
│ └── settings_page.dart
├── components/ # Reusable UI components
├── l10n/ # i18n translations (*.arb files)
├── utils/ # Utility functions
├── theme.dart # Svrnty theme configuration
└── main.dart # App entry point
```
### Design System (Svrnty)
**Primary Colors:**
- Primary (Crimson): #C44D58
- Secondary (Slate Blue): #475C6C
- Error: #BA1A1A
**Typography:**
- Primary Font: Montserrat (all weights 300-700)
- Monospace Font: IBMPlexMono
- Material Design 3 text styles
**Theme Files:**
- `lib/theme.dart` - Complete Material 3 theme configuration
- Light and dark themes with high-contrast variants
- All colors defined in ColorScheme
## Core Patterns & Standards
### 1. Strict Typing (MANDATORY)
**NO `dynamic`, NO untyped `var`**
```dart
// FORBIDDEN:
var data = fetchData();
dynamic result = api.call();
// REQUIRED:
DeliveryRoute data = fetchData();
Result<DeliveryRoute> result = api.call();
```
### 2. Serializable Interface
All models must implement `Serializable`:
```dart
class Delivery implements Serializable {
final int id;
final String name;
const Delivery({required this.id, required this.name});
factory Delivery.fromJson(Map<String, dynamic> json) {
return Delivery(
id: json['id'] as int,
name: json['name'] as String,
);
}
@override
Map<String, Object?> toJson() => {
'id': id,
'name': name,
};
}
```
### 3. Error Handling with Result<T>
**NEVER use try-catch for API calls. Use Result<T> pattern:**
```dart
final result = await apiClient.executeQuery<DeliveryRoute>(
endpoint: 'deliveryRoutes',
query: GetRoutesQuery(),
fromJson: DeliveryRoute.fromJson,
);
result.when(
success: (route) => showRoute(route),
error: (error) {
switch (error.type) {
case ApiErrorType.network:
showSnackbar('No connection');
case ApiErrorType.timeout:
showSnackbar('Request timeout');
case ApiErrorType.validation:
showValidationErrors(error.details);
case ApiErrorType.http when error.statusCode == 401:
navigateToLogin();
default:
showSnackbar('Error: ${error.message}');
}
},
);
```
### 4. CQRS API Integration
**Query (Read Operations):**
```dart
final result = await client.executeQuery<Delivery>(
endpoint: 'simpleDeliveriesQueryItems',
query: GetDeliveriesQuery(routeFragmentId: 123),
fromJson: Delivery.fromJson,
);
```
**Command (Write Operations):**
```dart
final result = await client.executeCommand(
endpoint: 'completeDelivery',
command: CompleteDeliveryCommand(deliveryId: 123),
);
```
### 5. Riverpod State Management
**Providers Pattern:**
```dart
final authServiceProvider = Provider<AuthService>((ref) {
return AuthService();
});
final userProfileProvider = FutureProvider<UserProfile?>((ref) async {
final authService = ref.watch(authServiceProvider);
final token = await authService.getToken();
return token != null ? authService.decodeToken(token) : null;
});
// Usage in widget:
final userProfile = ref.watch(userProfileProvider);
userProfile.when(
data: (profile) => Text(profile?.fullName ?? ''),
loading: () => const CircularProgressIndicator(),
error: (error, stackTrace) => Text('Error: $error'),
);
```
### 6. Authentication Flow
1. **Login**: `AuthService.login()` triggers OAuth2/OIDC flow with Keycloak
2. **Token Storage**: Secure storage with `flutter_secure_storage`
3. **Token Validation**: Check expiration with `JwtDecoder.isExpired()`
4. **Auto Refresh**: Implement token refresh on 401 responses
5. **Logout**: Clear tokens from secure storage
**Keycloak Configuration:**
- Realm: planb-internal
- Client ID: delivery-mobile-app
- Discovery URL: https://auth.goutezplanb.com/realms/planb-internal/.well-known/openid-configuration
- Scopes: openid, profile, offline_access
### 7. No Emojis Rule
**MANDATORY: NO emojis in code, comments, or commit messages**
```dart
// FORBIDDEN:
// Bug fix for delivery issues
void completeDelivery(int id) { ... } // Done
// REQUIRED:
// Bug fix for delivery completion logic
void completeDelivery(int id) { ... }
```
## API Integration
### Base URLs
```dart
const String queryBaseUrl = 'https://api-route.goutezplanb.com/api/query';
const String commandBaseUrl = 'https://api-route.goutezplanb.com/api/command';
```
### Key Endpoints
- Query: `/api/query/simpleDeliveriesQueryItems`
- Query: `/api/query/simpleDeliveryRouteQueryItems`
- Command: `/api/command/completeDelivery`
- Command: `/api/command/markDeliveryAsUncompleted`
- Upload: `/api/delivery/uploadDeliveryPicture`
### Authorization
All requests to API base URL must include Bearer token:
```dart
final authClient = CqrsApiClient(
config: ApiClientConfig(
baseUrl: 'https://api-route.goutezplanb.com',
defaultHeaders: {'Authorization': 'Bearer $token'},
),
);
```
## Internationalization (i18n)
### File Structure
```
lib/l10n/
├── app_en.arb # English translations
└── app_fr.arb # French translations
```
### ARB Format
```json
{
"appTitle": "Plan B Logistics",
"loginButton": "Login with Keycloak",
"deliveryStatus": "Delivery #{id} is {status}",
"@deliveryStatus": {
"placeholders": {
"id": {"type": "int"},
"status": {"type": "String"}
}
}
}
```
### Usage in Code
```dart
AppLocalizations.of(context)!.appTitle
AppLocalizations.of(context)!.deliveryStatus(id: 123, status: 'completed')
```
## Native Features
### Camera Integration
- Package: `image_picker`
- Use: Photo capture for delivery proof
- Platforms: iOS, Android, Web
```dart
final picker = ImagePicker();
final pickedFile = await picker.pickImage(source: ImageSource.camera);
if (pickedFile != null) {
// Upload to server
}
```
### Phone Calls
- Package: `url_launcher`
- Use: Call customer from delivery details
```dart
final Uri phoneUri = Uri(scheme: 'tel', path: phoneNumber);
if (await canLaunchUrl(phoneUri)) {
await launchUrl(phoneUri);
}
```
### Maps Integration
- Package: `url_launcher`
- Use: Open maps app to show delivery address
```dart
final Uri mapUri = Uri(
scheme: 'https',
host: 'maps.google.com',
queryParameters: {'q': '${address.latitude},${address.longitude}'},
);
if (await canLaunchUrl(mapUri)) {
await launchUrl(mapUri);
}
```
## File Naming Conventions
- **Files**: snake_case (e.g., `delivery_route.dart`)
- **Classes**: PascalCase (e.g., `DeliveryRoute`)
- **Variables/Functions**: camelCase (e.g., `deliveryId`, `completeDelivery()`)
- **Constants**: camelCase or UPPER_SNAKE_CASE (e.g., `kPrimaryColor` or `MAX_RETRIES`)
- **Private members**: Prefix with underscore (e.g., `_secureStorage`)
## Git Conventions
- **Author**: Svrnty
- **Co-Author**: Jean-Philippe Brule <jp@svrnty.io>
- **Commits**: Clear, concise messages describing the "why"
- **NO emojis in commits**
Example:
```
Implement OAuth2/OIDC authentication with Keycloak
Adds AuthService with flutter_appauth integration, JWT token
management with secure storage, and automatic token refresh.
Co-Authored-By: Claude <noreply@anthropic.com>
```
## Common Development Tasks
### Adding a New Page
1. Create widget file in `lib/pages/[name]_page.dart`
2. Extend `ConsumerWidget` for Riverpod access
3. Use strict typing for all parameters and variables
4. Apply Svrnty colors from theme
5. Handle loading/error states with `.when()`
### Adding a New Data Model
1. Create in `lib/models/[name].dart`
2. Implement `Serializable` interface
3. Add `fromJson` factory constructor
4. Implement `toJson()` method
5. Use explicit types (no `dynamic`)
### Implementing API Call
1. Create Query/Command class implementing `Serializable`
2. Use `CqrsApiClient.executeQuery()` or `.executeCommand()`
3. Handle Result<T> with `.when()` pattern
4. Never use try-catch for API calls
5. Provide proper error messages to user
### Adding i18n Support
1. Add key to `app_en.arb` and `app_fr.arb`
2. Use `AppLocalizations.of(context)!.keyName` in widgets
3. For parameterized strings, define placeholders in ARB
4. Test both English and French text
## Testing
- Unit tests: `test/` directory
- Widget tests: `test/` directory with widget_test suffix
- Use Riverpod's testing utilities for provider testing
- Mock API client for service tests
- Maintain >80% code coverage for business logic
## Deployment Checklist
- [ ] All strict typing rules followed
- [ ] No `dynamic` or untyped `var`
- [ ] All API calls use Result<T> pattern
- [ ] i18n translations complete for both languages
- [ ] Theme colors correctly applied
- [ ] No emojis in code or commits
- [ ] Tests passing (flutter test)
- [ ] Static analysis clean (flutter analyze)
- [ ] No secrets in code (tokens, keys, credentials)
- [ ] APK/IPA builds successful
## Key Dependencies
- `flutter_riverpod`: State management
- `flutter_appauth`: OAuth2/OIDC
- `flutter_secure_storage`: Token storage
- `jwt_decoder`: JWT token parsing
- `http`: HTTP client
- `image_picker`: Camera/photo access
- `url_launcher`: Phone calls and maps
- `animate_do`: Animations (from Svrnty)
- `lottie`: Loading animations
- `iconsax`: Icon set
- `intl`: Internationalization
## Support & Documentation
- **Theme**: See `lib/theme.dart` for complete Svrnty design system
- **API Types**: See `lib/api/types.dart` for Result<T> and error handling
- **Models**: See `lib/models/` for data structure examples
- **Providers**: See `lib/providers/providers.dart` for state management setup
- **Auth**: See `lib/services/auth_service.dart` for OAuth2/OIDC flow