18 KiB
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
# 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):
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:
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):
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:
// 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):
import { Vehicle, CreateVehicleRequest, UpdateVehicleRequest, DropdownOption, VINDecodeResponse } from '../types/vehicles.types';
New:
import { Vehicle, CreateVehicleRequest, UpdateVehicleRequest, VINDecodeResponse } from '../types/vehicles.types';
// DropdownOption removed - using string[] now
Step 3: Update getMakes()
Current (lines 41-44):
getMakes: async (year: number): Promise<DropdownOption[]> => {
const response = await apiClient.get(`/vehicles/dropdown/makes?year=${year}`);
return response.data;
},
New:
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):
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:
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):
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:
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):
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:
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):
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:
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:
/**
* @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:
// 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:
// 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:
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:
// 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(notmake_id, etc.) - Verify values are properly encoded
Handoff to Agent 6
Once complete, provide this information:
Updated API Client Contract
Methods:
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[](notDropdownOption[]) - Cascade queries use selected string values directly (not IDs)
- No need to extract
.idfrom selected options anymore - No need to find selected option by ID to get name
- Simpler state management - store strings directly
Example Usage:
// 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
# 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