# CreditFair Flutter Frontend — Claude Code Guide

## Project Context
Migration of a Laravel/PHP web app → Flutter (Dart) frontend connecting to a FastAPI Python backend.
- **Original repo (reference):** `D:\PHPAdmin\creditfairadmin`
- **Python backend:** `D:\PHPAdmin\creditfair_python_backend`
- **This repo:** `D:\PHPAdmin\creditfair_flutter_frontend\app`

---

## Architecture: Clean Architecture (3 Layers)

```
lib/
├── infrastructure/api/      ← HTTP layer (Dio). No business logic.
├── application/providers/   ← Riverpod state + business rules.
└── presentation/screens/    ← UI only. Watches providers. Never calls Dio directly.
```

**Golden Rule: Never put networking inside UI widgets. Never put UI logic inside API classes.**

---

## Layer Responsibilities

### 1. Infrastructure Layer (`lib/infrastructure/api/`)
- One Dart class per domain area (e.g., `AuthApi`, `ApplicationApi`, `LoanApi`)
- Uses `Dio` for HTTP, base URL `http://127.0.0.1:8000/api/v1`
- Returns raw `Map<String, dynamic>` or typed models
- Handles `DioException` and converts to readable exceptions
- Receives JWT token as parameter — never stores it internally

### 2. Application Layer (`lib/application/providers/`)
- Riverpod `Provider`, `FutureProvider`, `StateNotifierProvider` live here
- Coordinates calls to API classes
- Manages loading/error/success states
- Stores app-wide state (auth token, current user, etc.)

### 3. Presentation Layer (`lib/presentation/screens/`)
- One folder per screen
- Screen widgets are `ConsumerWidget` or `ConsumerStatefulWidget`
- Widgets call `ref.watch(provider)` or `ref.read(provider)`
- **Never** instantiate API classes directly in screens
- Complex screens split into `components/` subfolder

---

## How to Add a New Screen (Step-by-Step)

**Example: Adding a `DisbursementsScreen`**

### Step 1 — API Client
Create `lib/infrastructure/api/disbursement_api.dart`:
```dart
import 'package:dio/dio.dart';

class DisbursementApi {
  final Dio _dio = Dio(BaseOptions(
    baseUrl: 'http://127.0.0.1:8000/api/v1',
    connectTimeout: const Duration(seconds: 5),
    receiveTimeout: const Duration(seconds: 10),
  ));

  Future<List<Map<String, dynamic>>> getDisbursements(String token) async {
    try {
      final response = await _dio.get(
        '/disbursements',
        options: Options(headers: {'Authorization': 'Bearer $token'}),
      );
      return List<Map<String, dynamic>>.from(response.data);
    } on DioException catch (e) {
      if (e.response?.statusCode == 401) throw Exception('Unauthorized');
      throw Exception('Failed to load disbursements: ${e.message}');
    }
  }
}
```

### Step 2 — Riverpod Provider
Create `lib/application/providers/disbursement_provider.dart`:
```dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../infrastructure/api/disbursement_api.dart';

// API client provider (singleton)
final disbursementApiProvider = Provider<DisbursementApi>((ref) => DisbursementApi());

// Data provider (async, takes token as family argument)
final disbursementsProvider = FutureProvider.family<List<Map<String, dynamic>>, String>(
  (ref, token) async {
    final api = ref.read(disbursementApiProvider);
    return api.getDisbursements(token);
  },
);
```

### Step 3 — Screen
Create `lib/presentation/screens/disbursements/disbursements_screen.dart`:
```dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../application/providers/disbursement_provider.dart';
import '../../theme/app_theme.dart';

class DisbursementsScreen extends ConsumerWidget {
  final String token;
  const DisbursementsScreen({Key? key, required this.token}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final disbursementsAsync = ref.watch(disbursementsProvider(token));

    return Scaffold(
      appBar: AppBar(title: const Text('Disbursements')),
      body: disbursementsAsync.when(
        data: (items) => ListView.builder(
          itemCount: items.length,
          itemBuilder: (context, index) => ListTile(
            title: Text(items[index]['id'].toString()),
          ),
        ),
        loading: () => const Center(child: CircularProgressIndicator()),
        error: (err, _) => Center(child: Text('Error: $err')),
      ),
    );
  }
}
```

### Step 4 — Register Route in main.dart
```dart
GoRoute(
  path: '/disbursements',
  builder: (context, state) {
    final token = state.uri.queryParameters['token'] ?? '';
    return DisbursementsScreen(token: token);
  },
),
```

### Step 5 — Add to Sidebar
In `edit_application_screen.dart` `_buildSidebar()`, update the disbursements item:
```dart
_sidebarItem(Icons.account_balance_wallet_outlined, 'Disbursements',
  onTap: () => context.push('/disbursements?token=$token')),
```

---

## Widget Patterns

### Preferred Widget Types (in order of preference)

| Scenario | Widget Type |
|----------|-------------|
| Pure display, no state | `StatelessWidget` |
| Reads/watches providers | `ConsumerWidget` |
| Has local state + reads providers | `ConsumerStatefulWidget` |
| Needs hooks (animations, controllers) | `HookConsumerWidget` |

### Async Data Pattern
Always use `.when()` for `AsyncValue`:
```dart
final asyncValue = ref.watch(someProvider);
return asyncValue.when(
  data: (data) => YourWidget(data: data),
  loading: () => const Center(child: CircularProgressIndicator()),
  error: (err, _) => Center(child: Text('Error: $err')),
);
```

---

## Theme & Styling

Always use `AppTheme` constants. Never hardcode colors.

Key constants from `lib/presentation/theme/app_theme.dart`:
```dart
AppTheme.primaryColor      // Corporate blue
AppTheme.surfaceWhite      // #FFFFFF
AppTheme.borderLight       // Light border color
AppTheme.textSecondary     // Secondary text grey
AppTheme.error             // Error red
AppTheme.infoLight         // Light blue background
```

Responsive breakpoint:
```dart
final isDesktop = MediaQuery.of(context).size.width > 900;
```

Sidebar width: `220px` fixed.

---

## Coding Conventions

- **File names:** `snake_case.dart` (e.g., `disbursements_screen.dart`)
- **Classes:** `PascalCase` (e.g., `DisbursementsScreen`)
- **Variables/functions:** `camelCase`
- **Constants:** `lowerCamelCase` for providers (`disbursementsProvider`)
- **Imports:** Flutter SDK → package imports → local imports
- **No print():** Use `debugPrint()` for debug output
- **const constructors:** Use wherever possible for performance

---

## Naming Conventions for Providers

```dart
// API client singleton
final disbursementApiProvider = Provider<DisbursementApi>((ref) => ...);

// Async list fetch (with token arg)
final disbursementsProvider = FutureProvider.family<List<...>, String>((ref, token) => ...);

// Async single item fetch (with id arg)
final disbursementProvider = FutureProvider.family<Map<...>, int>((ref, id) => ...);

// Mutable state (use StateNotifier)
final authStateProvider = StateNotifierProvider<AuthNotifier, AuthState>((ref) => ...);
```

---

## What Has Been Migrated

- [x] Login screen (Microsoft SSO + email/password)
- [x] Dashboard screen
- [x] Applications list screen
- [x] Application editor (7-tab: Personal, Address, Employment, Loan, Bank, Documents, Logs)
- [x] Roles & Permissions screen (RBAC)
- [x] Theme system (AppTheme)
- [x] Router setup (GoRouter — Riverpod `routerProvider`, `/admin/*` path convention)
- [x] API clients: AuthApi, ApplicationApi, RbacApi, MerchantApi
- [x] Sidebar navigation (desktop) via shared `AppSidebar` widget
- [x] Responsive layout (desktop/mobile)
- [x] Merchants list screen (`/admin/merchants`)
- [x] Merchant locations screen + add/edit (`/admin/merchants/:id/locations`)
- [x] Products screen per merchant (`/admin/merchants/:id/products`)
- [x] **Merchant Offers screen** (`/admin/merchants/:merchantId/offers`) — list all offers with audit log, disbursement phases; soft-delete via confirm dialog

## Implemented Routes

| Route | Screen | Notes |
|-------|--------|-------|
| `/login` | `LoginScreen` | |
| `/admin/home` | `DashboardScreen` | |
| `/admin/application/edit/:id` | `EditApplicationScreen` | |
| `/admin/merchants` | `MerchantsScreen` | |
| `/admin/merchants/:id/locations` | `MerchantLocationsScreen` | |
| `/admin/merchants/:id/locations/add` | `AddMerchantLocationScreen` | |
| `/admin/merchants/:id/locations/edit/:locationId` | `EditMerchantLocationScreen` | |
| `/admin/merchants/:merchantId/products` | `ProductsScreen` | |
| `/admin/merchants/:merchantId/offers` | `MerchantOffersScreen` | Added in merchant-offers merge |
| `/admin/merchants/:merchantId/offers/add` | stub scaffold | Migration pending |
| `/admin/merchants/:merchantId/offers/:offerId/edit` | stub scaffold | Migration pending |

## What Still Needs Migration

Reference original Laravel views: `/Users/creditfair/Sites/creditfair/resources/views/`

- [ ] Auth token storage & persistence (shared_preferences or flutter_secure_storage)
- [ ] Disbursements screen
- [ ] Analytics / Reports screen
- [ ] Document upload UI (file picker + upload to backend)
- [ ] OTP verification flow
- [ ] Payment/EMI schedule view
- [ ] Loan agreement generation view
- [ ] Notification system
- [ ] Search and filtering on applications list
- [ ] Export to Excel/PDF
- [ ] Merchant Offer — Add screen (`/admin/merchants/:id/offers/add`)
- [ ] Merchant Offer — Edit screen (`/admin/merchants/:id/offers/:offerId/edit`)

---

## Running the App

```bash
cd /Users/creditfair/Sites/creditfair_flutter_frontend/app
flutter pub get
flutter run -d chrome          # Web (development)
flutter run -d macos           # macOS native
flutter run                    # Default device
```

Backend must be running at `http://127.0.0.1:8000`.

---

## Key Reference Files

| What to look at | Path |
|-----------------|------|
| Existing screen example | `lib/presentation/screens/application_edit/edit_application_screen.dart` |
| Merchant Offers screen (latest) | `lib/presentation/screens/merchants/merchant_offers_screen.dart` |
| API client example | `lib/infrastructure/api/application_api.dart` |
| Merchant API client | `lib/infrastructure/api/merchant_api.dart` |
| Merchant providers | `lib/application/providers/merchant_provider.dart` |
| Theme constants | `lib/presentation/theme/app_theme.dart` |
| Router config | `lib/main.dart` |
| Original PHP views | `/Users/creditfair/Sites/creditfair/resources/views/` |
| Original routes | `/Users/creditfair/Sites/creditfair/routes/` |
