# 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 getModels(year: number, makeId: number): Promise getTrims(year: number, makeId: number, modelId: number): Promise getEngines(year: number, makeId: number, modelId: number, trimId: number): Promise getTransmissions(year: number, makeId: number, modelId: number): Promise getYears(): Promise ``` ### 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 => { const response = await apiClient.get(`/vehicles/dropdown/makes?year=${year}`); return response.data; }, ``` **New**: ```typescript getMakes: async (year: number): Promise => { const response = await apiClient.get(`/vehicles/dropdown/makes?year=${year}`); return response.data; }, ``` **Changes**: - Line 41: Return type `Promise` → `Promise` - 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 => { 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 => { 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` → `Promise` - 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 => { 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 => { 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` - 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 => { 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 => { 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` - 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 => { 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 => { 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` - 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 => { const response = await apiClient.get('/vehicles'); return response.data; }, getById: async (id: string): Promise => { const response = await apiClient.get(`/vehicles/${id}`); return response.data; }, create: async (data: CreateVehicleRequest): Promise => { const response = await apiClient.post('/vehicles', data); return response.data; }, update: async (id: string, data: UpdateVehicleRequest): Promise => { const response = await apiClient.put(`/vehicles/${id}`, data); return response.data; }, delete: async (id: string): Promise => { await apiClient.delete(`/vehicles/${id}`); }, // Dropdown API methods - now return string[] and accept string parameters getYears: async (): Promise => { const response = await apiClient.get('/vehicles/dropdown/years'); return response.data; }, getMakes: async (year: number): Promise => { const response = await apiClient.get(`/vehicles/dropdown/makes?year=${year}`); return response.data; }, getModels: async (year: number, make: string): Promise => { 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 => { 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 => { 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 => { 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 => { 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 getMakes(year: number): Promise getModels(year: number, make: string): Promise getTrims(year: number, make: string, model: string): Promise getEngines(year: number, make: string, model: string, trim: string): Promise getTransmissions(year: number, make: string, model: string): Promise decodeVIN(vin: string): Promise ``` **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(null); const models = await vehiclesApi.getModels(year, selectedMake.id); // Use ID // Store name: vehicle.make = selectedMake.name // NEW (String-based) const [selectedMake, setSelectedMake] = useState(''); 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