# Frontend API Client Update - Agent 5

## Task: Update vehicles API client to use string-based parameters and responses

**Status**: Ready for Implementation
**Dependencies**: Agent 4 (Vehicles API) must be complete
**Estimated Time**: 1 hour
**Assigned To**: Agent 5 (Frontend API)

---

## Overview

Update the frontend API client to match the new backend API contract. Change from ID-based parameters to string-based parameters, and handle string array responses instead of object arrays.

---

## Prerequisites

### Verify Agent 4 Completed
```bash
# Test backend API returns strings
curl -X GET "http://localhost:3000/api/vehicles/dropdown/makes?year=2024" \
  -H "Authorization: Bearer TOKEN"
# Should return: ["Ford", "Honda", ...] not [{id: 1, name: "Ford"}, ...]
```

### Files to Modify
```
frontend/src/features/vehicles/api/vehicles.api.ts
frontend/src/features/vehicles/types/vehicles.types.ts
```

---

## Current Implementation Analysis

### API Client

**File**: `frontend/src/features/vehicles/api/vehicles.api.ts`

**Current Methods** (ID-based parameters, object responses):
```typescript
getMakes(year: number): Promise<DropdownOption[]>
getModels(year: number, makeId: number): Promise<DropdownOption[]>
getTrims(year: number, makeId: number, modelId: number): Promise<DropdownOption[]>
getEngines(year: number, makeId: number, modelId: number, trimId: number): Promise<DropdownOption[]>
getTransmissions(year: number, makeId: number, modelId: number): Promise<DropdownOption[]>
getYears(): Promise<number[]>
```

### Type Definition

**File**: `frontend/src/features/vehicles/types/vehicles.types.ts`

**Current DropdownOption**:
```typescript
export interface DropdownOption {
  id: number;
  name: string;
}
```

---

## Step 1: Update or Remove DropdownOption Type

**File**: `frontend/src/features/vehicles/types/vehicles.types.ts`

**Option A: Remove DropdownOption entirely** (Recommended)

**Find and delete** (lines 56-59):
```typescript
export interface DropdownOption {
  id: number;
  name: string;
}
```

**Reason**: API now returns plain `string[]`, no need for wrapper type

---

**Option B: Keep as alias** (If other code depends on it)

**Replace with**:
```typescript
// Deprecated: Dropdowns now return string[] directly
// Keeping for backward compatibility during migration
export type DropdownOption = string;
```

**Recommendation**: Use Option A (delete entirely). Clean break, clear intent.

---

## Step 2: Update API Client Imports

**File**: `frontend/src/features/vehicles/api/vehicles.api.ts`

**Current** (line 6):
```typescript
import { Vehicle, CreateVehicleRequest, UpdateVehicleRequest, DropdownOption, VINDecodeResponse } from '../types/vehicles.types';
```

**New**:
```typescript
import { Vehicle, CreateVehicleRequest, UpdateVehicleRequest, VINDecodeResponse } from '../types/vehicles.types';
// DropdownOption removed - using string[] now
```

---

## Step 3: Update getMakes()

**Current** (lines 41-44):
```typescript
getMakes: async (year: number): Promise<DropdownOption[]> => {
  const response = await apiClient.get(`/vehicles/dropdown/makes?year=${year}`);
  return response.data;
},
```

**New**:
```typescript
getMakes: async (year: number): Promise<string[]> => {
  const response = await apiClient.get(`/vehicles/dropdown/makes?year=${year}`);
  return response.data;
},
```

**Changes**:
- Line 41: Return type `Promise<DropdownOption[]>` → `Promise<string[]>`
- Query parameter unchanged (already uses year)
- Response data is now string array

---

## Step 4: Update getModels()

**Current** (lines 46-49):
```typescript
getModels: async (year: number, makeId: number): Promise<DropdownOption[]> => {
  const response = await apiClient.get(`/vehicles/dropdown/models?year=${year}&make_id=${makeId}`);
  return response.data;
},
```

**New**:
```typescript
getModels: async (year: number, make: string): Promise<string[]> => {
  const response = await apiClient.get(`/vehicles/dropdown/models?year=${year}&make=${encodeURIComponent(make)}`);
  return response.data;
},
```

**Changes**:
- Line 46: Parameter `makeId: number` → `make: string`
- Line 46: Return type `Promise<DropdownOption[]>` → `Promise<string[]>`
- Line 47: Query param `make_id=${makeId}` → `make=${encodeURIComponent(make)}`
- **Important**: Use `encodeURIComponent()` to handle spaces and special chars in make names

---

## Step 5: Update getTrims()

**Current** (lines 61-64):
```typescript
getTrims: async (year: number, makeId: number, modelId: number): Promise<DropdownOption[]> => {
  const response = await apiClient.get(`/vehicles/dropdown/trims?year=${year}&make_id=${makeId}&model_id=${modelId}`);
  return response.data;
},
```

**New**:
```typescript
getTrims: async (year: number, make: string, model: string): Promise<string[]> => {
  const response = await apiClient.get(`/vehicles/dropdown/trims?year=${year}&make=${encodeURIComponent(make)}&model=${encodeURIComponent(model)}`);
  return response.data;
},
```

**Changes**:
- Line 61: Parameters changed to `make: string, model: string`
- Line 61: Return type changed to `Promise<string[]>`
- Line 62: Query params changed to `make=...&model=...`
- Use `encodeURIComponent()` for both make and model

---

## Step 6: Update getEngines()

**Current** (lines 56-59):
```typescript
getEngines: async (year: number, makeId: number, modelId: number, trimId: number): Promise<DropdownOption[]> => {
  const response = await apiClient.get(`/vehicles/dropdown/engines?year=${year}&make_id=${makeId}&model_id=${modelId}&trim_id=${trimId}`);
  return response.data;
},
```

**New**:
```typescript
getEngines: async (year: number, make: string, model: string, trim: string): Promise<string[]> => {
  const response = await apiClient.get(`/vehicles/dropdown/engines?year=${year}&make=${encodeURIComponent(make)}&model=${encodeURIComponent(model)}&trim=${encodeURIComponent(trim)}`);
  return response.data;
},
```

**Changes**:
- Line 56: Parameters changed to strings: `make: string, model: string, trim: string`
- Line 56: Return type changed to `Promise<string[]>`
- Line 57: Query params changed to use strings with proper encoding
- Use `encodeURIComponent()` for all string parameters

---

## Step 7: Update getTransmissions()

**Current** (lines 51-54):
```typescript
getTransmissions: async (year: number, makeId: number, modelId: number): Promise<DropdownOption[]> => {
  const response = await apiClient.get(`/vehicles/dropdown/transmissions?year=${year}&make_id=${makeId}&model_id=${modelId}`);
  return response.data;
},
```

**New**:
```typescript
getTransmissions: async (year: number, make: string, model: string): Promise<string[]> => {
  const response = await apiClient.get(`/vehicles/dropdown/transmissions?year=${year}&make=${encodeURIComponent(make)}&model=${encodeURIComponent(model)}`);
  return response.data;
},
```

**Changes**:
- Line 51: Parameters changed to `make: string, model: string`
- Line 51: Return type changed to `Promise<string[]>`
- Line 52: Query params changed to strings
- Use `encodeURIComponent()` for encoding

---

## Complete Updated File

After all changes, the vehicles.api.ts file should look like:

```typescript
/**
 * @ai-summary API calls for vehicles feature
 */

import { apiClient } from '../../../core/api/client';
import { Vehicle, CreateVehicleRequest, UpdateVehicleRequest, VINDecodeResponse } from '../types/vehicles.types';

// All requests (including dropdowns) use authenticated apiClient

export const vehiclesApi = {
  getAll: async (): Promise<Vehicle[]> => {
    const response = await apiClient.get('/vehicles');
    return response.data;
  },

  getById: async (id: string): Promise<Vehicle> => {
    const response = await apiClient.get(`/vehicles/${id}`);
    return response.data;
  },

  create: async (data: CreateVehicleRequest): Promise<Vehicle> => {
    const response = await apiClient.post('/vehicles', data);
    return response.data;
  },

  update: async (id: string, data: UpdateVehicleRequest): Promise<Vehicle> => {
    const response = await apiClient.put(`/vehicles/${id}`, data);
    return response.data;
  },

  delete: async (id: string): Promise<void> => {
    await apiClient.delete(`/vehicles/${id}`);
  },

  // Dropdown API methods - now return string[] and accept string parameters
  getYears: async (): Promise<number[]> => {
    const response = await apiClient.get('/vehicles/dropdown/years');
    return response.data;
  },

  getMakes: async (year: number): Promise<string[]> => {
    const response = await apiClient.get(`/vehicles/dropdown/makes?year=${year}`);
    return response.data;
  },

  getModels: async (year: number, make: string): Promise<string[]> => {
    const response = await apiClient.get(`/vehicles/dropdown/models?year=${year}&make=${encodeURIComponent(make)}`);
    return response.data;
  },

  getTrims: async (year: number, make: string, model: string): Promise<string[]> => {
    const response = await apiClient.get(`/vehicles/dropdown/trims?year=${year}&make=${encodeURIComponent(make)}&model=${encodeURIComponent(model)}`);
    return response.data;
  },

  getEngines: async (year: number, make: string, model: string, trim: string): Promise<string[]> => {
    const response = await apiClient.get(`/vehicles/dropdown/engines?year=${year}&make=${encodeURIComponent(make)}&model=${encodeURIComponent(model)}&trim=${encodeURIComponent(trim)}`);
    return response.data;
  },

  getTransmissions: async (year: number, make: string, model: string): Promise<string[]> => {
    const response = await apiClient.get(`/vehicles/dropdown/transmissions?year=${year}&make=${encodeURIComponent(make)}&model=${encodeURIComponent(model)}`);
    return response.data;
  },

  // VIN decode method - using unified platform endpoint
  decodeVIN: async (vin: string): Promise<VINDecodeResponse> => {
    const response = await apiClient.get(`/platform/vehicle?vin=${vin}`);
    return response.data;
  },
};
```

---

## Why encodeURIComponent()?

**Problem**: Vehicle makes/models can contain spaces and special characters:
- "Land Rover"
- "Mercedes-Benz"
- "Alfa Romeo"

**Without encoding**: Query string breaks
```
/vehicles/dropdown/models?year=2024&make=Land Rover
                                              ^ space breaks URL
```

**With encoding**: Query string is valid
```
/vehicles/dropdown/models?year=2024&make=Land%20Rover
```

**Rule**: Always encode user-provided strings in URLs

---

## Testing & Verification

### Manual Testing

Test the API client methods in browser console:

```typescript
// In browser console (after logging in)
import { vehiclesApi } from './src/features/vehicles/api/vehicles.api';

// Test getMakes - should return string[]
const makes = await vehiclesApi.getMakes(2024);
console.log(makes); // Expected: ["Acura", "Audi", "BMW", ...]
console.log(typeof makes[0]); // Expected: "string"

// Test getModels with string parameter
const models = await vehiclesApi.getModels(2024, 'Ford');
console.log(models); // Expected: ["Bronco", "Edge", "F-150", ...]

// Test with space in name
const landRoverModels = await vehiclesApi.getModels(2024, 'Land Rover');
console.log(landRoverModels); // Should work - no URL errors

// Test getTrims
const trims = await vehiclesApi.getTrims(2024, 'Ford', 'F-150');
console.log(trims); // Expected: ["XLT", "Lariat", ...]

// Test getEngines
const engines = await vehiclesApi.getEngines(2024, 'Ford', 'F-150', 'XLT');
console.log(engines); // Expected: ["V6 2.7L Turbo", "V8 5.0L", ...]

// Test getTransmissions - should return REAL data now
const transmissions = await vehiclesApi.getTransmissions(2024, 'Ford', 'F-150');
console.log(transmissions); // Expected: ["10-Speed Automatic", ...] NOT ["Automatic", "Manual"]
```

### Unit Tests

Create tests for the API client:

```typescript
// frontend/src/features/vehicles/api/__tests__/vehicles.api.test.ts

import { vehiclesApi } from '../vehicles.api';
import { apiClient } from '../../../../core/api/client';

jest.mock('../../../../core/api/client');

describe('vehiclesApi', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe('getMakes', () => {
    it('should return string array', async () => {
      const mockMakes = ['Ford', 'Honda', 'Toyota'];
      (apiClient.get as jest.Mock).mockResolvedValue({ data: mockMakes });

      const result = await vehiclesApi.getMakes(2024);

      expect(result).toEqual(mockMakes);
      expect(apiClient.get).toHaveBeenCalledWith('/vehicles/dropdown/makes?year=2024');
    });
  });

  describe('getModels', () => {
    it('should use make string parameter', async () => {
      const mockModels = ['F-150', 'Mustang'];
      (apiClient.get as jest.Mock).mockResolvedValue({ data: mockModels });

      const result = await vehiclesApi.getModels(2024, 'Ford');

      expect(result).toEqual(mockModels);
      expect(apiClient.get).toHaveBeenCalledWith('/vehicles/dropdown/models?year=2024&make=Ford');
    });

    it('should encode make parameter with spaces', async () => {
      const mockModels = ['Range Rover', 'Defender'];
      (apiClient.get as jest.Mock).mockResolvedValue({ data: mockModels });

      await vehiclesApi.getModels(2024, 'Land Rover');

      expect(apiClient.get).toHaveBeenCalledWith('/vehicles/dropdown/models?year=2024&make=Land%20Rover');
    });

    it('should encode special characters', async () => {
      const mockModels = ['C-Class'];
      (apiClient.get as jest.Mock).mockResolvedValue({ data: mockModels });

      await vehiclesApi.getModels(2024, 'Mercedes-Benz');

      expect(apiClient.get).toHaveBeenCalledWith(expect.stringContaining('Mercedes-Benz'));
    });
  });

  describe('getTransmissions', () => {
    it('should return real transmission data', async () => {
      const mockTransmissions = ['10-Speed Automatic', '6-Speed Manual'];
      (apiClient.get as jest.Mock).mockResolvedValue({ data: mockTransmissions });

      const result = await vehiclesApi.getTransmissions(2024, 'Ford', 'F-150');

      expect(result).toEqual(mockTransmissions);
      // Verify not getting old hardcoded data
      expect(result).not.toEqual([{id: 1, name: 'Automatic'}, {id: 2, name: 'Manual'}]);
    });
  });
});
```

Run tests:
```bash
cd frontend
npm test -- vehicles.api.test.ts
```

---

## Completion Checklist

Before signaling completion:

- [ ] DropdownOption type removed (or deprecated)
- [ ] Import statement updated (removed DropdownOption)
- [ ] getMakes() returns string[]
- [ ] getModels() accepts make string, returns string[]
- [ ] getTrims() accepts make/model strings, returns string[]
- [ ] getEngines() accepts make/model/trim strings, returns string[]
- [ ] getTransmissions() accepts make/model strings, returns string[]
- [ ] All string parameters use encodeURIComponent()
- [ ] Query parameters changed: make_id→make, model_id→model, trim_id→trim
- [ ] Manual tests pass (no type errors, correct responses)
- [ ] Unit tests pass
- [ ] TypeScript compiles with no errors
- [ ] No console errors in browser

---

## Common Issues

### Issue: TypeScript error "Type 'string[]' is not assignable to 'DropdownOption[]'"
**Cause**: Form component (Agent 6) not yet updated, still expects DropdownOption[]

**Solution**:
- This is expected - Agent 6 will update the form component
- Verify API client changes are correct
- TypeScript errors will resolve once Agent 6 completes

### Issue: URL encoding breaks query
**Cause**: Forgot `encodeURIComponent()` on string parameters

**Solution**:
```typescript
// Wrong
`/models?make=${make}`  // Breaks with "Land Rover"

// Correct
`/models?make=${encodeURIComponent(make)}`  // Handles all cases
```

### Issue: Backend returns 400 "make parameter required"
**Cause**: Frontend sending wrong parameter name or encoding issue

**Solution**:
- Check browser Network tab for actual request
- Verify parameter names: `make`, `model`, `trim` (not `make_id`, etc.)
- Verify values are properly encoded

---

## Handoff to Agent 6

Once complete, provide this information:

### Updated API Client Contract

**Methods**:
```typescript
getYears(): Promise<number[]>
getMakes(year: number): Promise<string[]>
getModels(year: number, make: string): Promise<string[]>
getTrims(year: number, make: string, model: string): Promise<string[]>
getEngines(year: number, make: string, model: string, trim: string): Promise<string[]>
getTransmissions(year: number, make: string, model: string): Promise<string[]>
decodeVIN(vin: string): Promise<VINDecodeResponse>
```

**Key Changes for Agent 6**:
- All dropdown methods return `string[]` (not `DropdownOption[]`)
- Cascade queries use selected string values directly (not IDs)
- No need to extract `.id` from selected options anymore
- No need to find selected option by ID to get name
- Simpler state management - store strings directly

**Example Usage**:
```typescript
// OLD (ID-based)
const [selectedMake, setSelectedMake] = useState<DropdownOption | null>(null);
const models = await vehiclesApi.getModels(year, selectedMake.id);  // Use ID
// Store name: vehicle.make = selectedMake.name

// NEW (String-based)
const [selectedMake, setSelectedMake] = useState<string>('');
const models = await vehiclesApi.getModels(year, selectedMake);  // Use string directly
// Store directly: vehicle.make = selectedMake
```

### Verification Command
```bash
# Agent 6 can verify API client is ready:
cd frontend && npm run build
# Should compile (may have type errors in form component - Agent 6 will fix)
```

---

## Completion Message Template

```
Agent 5 (Frontend API Client): COMPLETE

Files Modified:
- frontend/src/features/vehicles/api/vehicles.api.ts
- frontend/src/features/vehicles/types/vehicles.types.ts

Changes Made:
- Removed DropdownOption interface (using string[] now)
- Updated all dropdown methods to return string[]
- Changed parameters from IDs to strings (make, model, trim)
- Added encodeURIComponent() for URL encoding
- Query parameters updated: make_id→make, model_id→model, trim_id→trim

Verification:
✓ TypeScript compiles (with expected form component errors)
✓ Manual API tests return correct string arrays
✓ Unit tests pass
✓ URL encoding handles spaces and special characters
✓ Transmissions return real data (not hardcoded)

Agent 6 (Frontend Forms) can now update VehicleForm component to use new API contract.

Breaking Changes for Agent 6:
- API methods return string[] not DropdownOption[]
- No more .id property - use selected strings directly
- Simplified cascade logic - no ID lookups needed
- Form can store selected values directly as strings
```

---

**Document Version**: 1.0
**Last Updated**: 2025-11-10
**Status**: Ready for Implementation
