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

11 KiB

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

flutter pub get
flutter pub upgrade
flutter pub run build_runner build --delete-conflicting-outputs

Development

flutter run -d chrome          # Web
flutter run -d macos           # macOS
flutter run -d ios             # iOS simulator
flutter run -d android         # Android emulator

Testing & Analysis

flutter test
flutter analyze
flutter test --coverage

Build

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

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

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

NEVER use try-catch for API calls. Use Result pattern:

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

final result = await client.executeQuery<Delivery>(
  endpoint: 'simpleDeliveriesQueryItems',
  query: GetDeliveriesQuery(routeFragmentId: 123),
  fromJson: Delivery.fromJson,
);

Command (Write Operations):

final result = await client.executeCommand(
  endpoint: 'completeDelivery',
  command: CompleteDeliveryCommand(deliveryId: 123),
);

5. Riverpod State Management

Providers Pattern:

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:

7. No Emojis Rule

MANDATORY: NO emojis in code, comments, or commit messages

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

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:

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

{
  "appTitle": "Plan B Logistics",
  "loginButton": "Login with Keycloak",
  "deliveryStatus": "Delivery #{id} is {status}",
  "@deliveryStatus": {
    "placeholders": {
      "id": {"type": "int"},
      "status": {"type": "String"}
    }
  }
}

Usage in Code

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