Fixed a critical issue where the mover registration form allowed submission with invalid default coordinates (latitude: 0, longitude: 0), which could create invalid mover records in the database.
The primary_location field in the registration form was initialized with default coordinates of (0, 0), which:
- Creates invalid database records - Movers with coordinates (0, 0) don't have a real location
- Bypasses validation - The old validation only checked
if (!latitude || !longitude)which passes for (0, 0) - Causes service issues - Invalid coordinates can't be used for distance calculations or map display
- Poor user experience - Users could submit the form without selecting a real location
File: src/pages/MoverRegistration.tsx
Before:
export interface RegistrationData {
// ...
primary_location: {
latitude: number;
longitude: number;
address: string;
};
// ...
}After:
export interface RegistrationData {
// ...
primary_location?: { // Made optional with ?
latitude: number;
longitude: number;
address: string;
};
// ...
}Why: Making it optional forces us to check for existence throughout the code and prevents accidental use of default values.
File: src/pages/MoverRegistration.tsx (lines 75-85)
Before:
const [registrationData, setRegistrationData] = useState<RegistrationData>({
business_name: '',
phone_primary: '',
vehicle_types: [],
has_helpers: false,
helper_count: 0,
service_radius_km: 10,
primary_location: {
latitude: 0, // ❌ Invalid default
longitude: 0, // ❌ Invalid default
address: ''
},
documents: {}
});After:
const [registrationData, setRegistrationData] = useState<RegistrationData>({
business_name: '',
phone_primary: '',
vehicle_types: [],
has_helpers: false,
helper_count: 0,
service_radius_km: 10,
// primary_location is undefined by default - must be set by user
documents: {}
});Why: No default coordinates means the field is truly empty until the user selects a location.
File: src/pages/MoverRegistration.tsx (lines 110-157)
Before:
const handleSubmit = async () => {
// Basic validation...
if (
!registrationData.primary_location.latitude ||
!registrationData.primary_location.longitude
) {
toast({
title: 'Validation Error',
description: 'Please set your primary location.',
variant: 'destructive'
});
return;
}
// ... rest of submission
}Problems with old validation:
- Didn't check if
primary_locationexists (would crash with undefined) !0evaluates totrue, so (0, 0) would pass validation- No type checking (could be strings or other invalid values)
- No geographic bounds checking
After:
const handleSubmit = async () => {
// Basic validation...
// Comprehensive location validation
if (!registrationData.primary_location) {
toast({
title: 'Location Required',
description: 'Please select your primary operating location before submitting.',
variant: 'destructive'
});
return;
}
const { latitude, longitude } = registrationData.primary_location;
// Check that latitude and longitude are present and valid numbers
if (
typeof latitude !== 'number' ||
typeof longitude !== 'number' ||
isNaN(latitude) ||
isNaN(longitude)
) {
toast({
title: 'Invalid Location',
description: 'The selected location coordinates are invalid. Please select a valid location.',
variant: 'destructive'
});
return;
}
// Check that coordinates are not default (0, 0) values
if (latitude === 0 && longitude === 0) {
toast({
title: 'Invalid Location',
description: 'Please select a real location. Default coordinates are not allowed.',
variant: 'destructive'
});
return;
}
// Basic validation for realistic Kenya coordinates
// Kenya roughly: lat -5 to 5, lng 34 to 42
if (latitude < -5 || latitude > 5 || longitude < 33 || longitude > 43) {
toast({
title: 'Location Out of Range',
description: 'Please select a location within Kenya.',
variant: 'destructive'
});
return;
}
// ... rest of submission with non-null assertion
primary_location: `POINT(${registrationData.primary_location!.longitude.toFixed(6)} ${registrationData.primary_location!.latitude.toFixed(6)})`,
}Validation Stages:
- ✅ Existence Check - Ensures
primary_locationis not undefined - ✅ Type Check - Ensures coordinates are actual numbers, not strings or null
- ✅ NaN Check - Catches
NaNvalues that might slip through - ✅ Zero Check - Explicitly rejects (0, 0) coordinates
- ✅ Geographic Bounds - Ensures location is within Kenya (rough bounds)
- ✅ User-Facing Errors - Clear toast messages for each error type
File: src/components/mover-registration/ServiceAreaStep.tsx (lines 62-67)
Before:
const isValid = data.primary_location?.latitude !== 0 && data.primary_location?.longitude !== 0;Problems:
undefined !== 0evaluates totrue(passes when location is missing)- Doesn't check for valid numbers
- Doesn't catch NaN values
After:
// Check that primary_location exists and has valid, non-zero coordinates
const isValid =
data.primary_location != null &&
typeof data.primary_location.latitude === 'number' &&
typeof data.primary_location.longitude === 'number' &&
!isNaN(data.primary_location.latitude) &&
!isNaN(data.primary_location.longitude) &&
!(data.primary_location.latitude === 0 && data.primary_location.longitude === 0);Comprehensive Checks:
- ✅ Object exists (
!= nullcatches bothnullandundefined) - ✅ Latitude is a number type
- ✅ Longitude is a number type
- ✅ Latitude is not NaN
- ✅ Longitude is not NaN
- ✅ Not the invalid (0, 0) coordinate pair
File: src/components/mover-registration/ServiceAreaStep.tsx (lines 170-183)
Added:
{!isValid && (
<div className="p-4 bg-red-50 dark:bg-red-950/20 border border-red-200 dark:border-red-800 rounded-lg">
<p className="text-sm text-red-600 dark:text-red-400">
<strong>Location Required:</strong> Please select your primary operating location before proceeding.
</p>
</div>
)}
<div className="flex justify-end pt-4">
<Button
onClick={onNext}
disabled={!isValid}
size="lg"
>
Next
</Button>
</div>Why:
- ✅ Users cannot proceed to the next step without selecting a valid location
- ✅ Clear visual feedback (disabled button + error message)
- ✅ Consistent UX with other registration steps (BusinessInfoStep pattern)
File: src/components/mover-registration/ServiceAreaStep.tsx (lines 113-124)
Before: Broken conditional rendering with orphaned tags
After:
{data.primary_location && (
<div className="mt-4 p-3 bg-green-50 dark:bg-green-950/20 border border-green-200 dark:border-green-800 rounded-lg flex gap-3">
<MapPin className="h-4 w-4 text-primary mt-0.5" />
<div className="flex-1">
<p className="text-sm font-medium">Selected Location</p>
<p className="text-xs text-muted-foreground">
{data.primary_location.address}
</p>
<p className="text-xs text-muted-foreground mt-1">
{data.primary_location.latitude.toFixed(6)}, {data.primary_location.longitude.toFixed(6)}
</p>
</div>
</div>
)}Why: Only shows location display when a real location has been selected.
-
Page Load
primary_locationisundefined- Location display not shown (conditional rendering)
- Next button is disabled
- Error message visible: "Location Required"
-
User Searches for Location
- Uses
LocationAutocompletecomponent - Selects a place from suggestions
handleLocationChangeupdatesprimary_locationwith real coordinates
- Uses
-
Location Selected
- Location display appears (green box with coordinates)
isValidbecomestrue- Next button becomes enabled
- Error message disappears
-
User Clicks Next
- Navigation to next step (Documents)
- Can continue through registration
-
Final Submit (Review Step)
handleSubmitruns comprehensive validation:- ✅ Checks
primary_locationexists - ✅ Validates coordinate types
- ✅ Rejects (0, 0) coordinates
- ✅ Checks geographic bounds (Kenya)
- ✅ Checks
- If valid: Creates mover record with PostGIS
POINTgeometry - If invalid: Shows user-facing error toast, prevents submission
Since we've added comprehensive validation before the database insert, we use TypeScript's non-null assertion operator (!) when building the PostGIS point:
primary_location: `POINT(${registrationData.primary_location!.longitude.toFixed(6)} ${registrationData.primary_location!.latitude.toFixed(6)})`The ! tells TypeScript: "I've validated this is not null/undefined, trust me."
Why it's safe:
- Multiple validation checks before this point
- Early returns prevent reaching this code with invalid data
- Clear user-facing errors if validation fails
- ✅ No invalid records - Cannot create movers with (0, 0) coordinates
- ✅ Geographic validation - Only Kenya locations accepted
- ✅ Type safety - Only valid numbers accepted
- ✅ Clear feedback - Users know location is required
- ✅ Progressive validation - Can't proceed without valid location
- ✅ Visual indicators - Disabled button, error messages, success display
- ✅ No silent failures - All validation errors show toast notifications
- ✅ Valid coordinates - All mover locations can be used for distance calculations
- ✅ Map display - All locations can be shown on maps
- ✅ Search accuracy - Location-based searches return correct results
- ✅ Type safety - Optional type forces existence checks
- ✅ Clear validation logic - Easy to understand and maintain
- ✅ Consistent patterns - Matches other form steps
-
Empty Location Test
- Load registration form
- Navigate to Service Area step
- Verify Next button is disabled
- Verify error message is shown
- Try to submit form - should be blocked
-
Location Selection Test
- Search for a Kenya location (e.g., "Nairobi")
- Select from autocomplete
- Verify location display appears with coordinates
- Verify Next button becomes enabled
- Verify error message disappears
-
Current Location Test
- Click "Use My Current Location" button
- Grant location permission
- Verify location updates with reverse geocoded address
- Verify coordinates are shown
- Verify can proceed to next step
-
Geographic Bounds Test
- Manually test with coordinates outside Kenya (if possible)
- Should show "Location Out of Range" error on submit
-
Complete Registration Test
- Fill all required fields
- Select valid Kenya location
- Submit registration
- Verify mover record created successfully
- Check database -
primary_locationshould be valid PostGIS POINT
describe('ServiceAreaStep - Location Validation', () => {
it('should disable Next button when location is not selected', () => {
const { getByText } = render(
<ServiceAreaStep
data={{ service_radius_km: 10 }} // No primary_location
onUpdate={jest.fn()}
onNext={jest.fn()}
/>
);
const nextButton = getByText('Next');
expect(nextButton).toBeDisabled();
});
it('should show error message when location is missing', () => {
const { getByText } = render(
<ServiceAreaStep
data={{ service_radius_km: 10 }}
onUpdate={jest.fn()}
onNext={jest.fn()}
/>
);
expect(getByText(/Location Required/)).toBeInTheDocument();
});
it('should enable Next button when valid location is selected', () => {
const { getByText } = render(
<ServiceAreaStep
data={{
service_radius_km: 10,
primary_location: {
latitude: -1.286389,
longitude: 36.817223,
address: 'Nairobi, Kenya'
}
}}
onUpdate={jest.fn()}
onNext={jest.fn()}
/>
);
const nextButton = getByText('Next');
expect(nextButton).toBeEnabled();
});
it('should reject (0, 0) coordinates as invalid', () => {
const { getByText } = render(
<ServiceAreaStep
data={{
service_radius_km: 10,
primary_location: {
latitude: 0,
longitude: 0,
address: ''
}
}}
onUpdate={jest.fn()}
onNext={jest.fn()}
/>
);
const nextButton = getByText('Next');
expect(nextButton).toBeDisabled();
});
});
describe('MoverRegistration - Location Validation', () => {
it('should prevent submission without location', async () => {
const { getByText } = render(<MoverRegistration />);
// Fill required fields
fireEvent.change(getByLabelText('Business Name'), { target: { value: 'Test Movers' } });
fireEvent.change(getByLabelText('Phone'), { target: { value: '+254712345678' } });
// Try to submit without location
fireEvent.click(getByText('Submit'));
await waitFor(() => {
expect(getByText(/Location Required/)).toBeInTheDocument();
});
});
it('should reject (0, 0) coordinates on submission', async () => {
const mockSubmit = jest.fn();
const { getByText } = render(
<MoverRegistration
initialData={{
business_name: 'Test Movers',
phone_primary: '+254712345678',
primary_location: { latitude: 0, longitude: 0, address: '' }
}}
onSubmit={mockSubmit}
/>
);
fireEvent.click(getByText('Submit'));
await waitFor(() => {
expect(getByText(/Default coordinates are not allowed/)).toBeInTheDocument();
expect(mockSubmit).not.toHaveBeenCalled();
});
});
it('should reject coordinates outside Kenya', async () => {
const mockSubmit = jest.fn();
const { getByText } = render(
<MoverRegistration
initialData={{
business_name: 'Test Movers',
phone_primary: '+254712345678',
primary_location: {
latitude: 40.7128, // New York
longitude: -74.0060,
address: 'New York, USA'
}
}}
onSubmit={mockSubmit}
/>
);
fireEvent.click(getByText('Submit'));
await waitFor(() => {
expect(getByText(/within Kenya/)).toBeInTheDocument();
expect(mockSubmit).not.toHaveBeenCalled();
});
});
it('should accept valid Kenya location', async () => {
const mockSubmit = jest.fn();
const { getByText } = render(
<MoverRegistration
initialData={{
business_name: 'Test Movers',
phone_primary: '+254712345678',
vehicle_types: ['pickup_truck'],
primary_location: {
latitude: -1.286389,
longitude: 36.817223,
address: 'Nairobi, Kenya'
}
}}
onSubmit={mockSubmit}
/>
);
fireEvent.click(getByText('Submit'));
await waitFor(() => {
expect(mockSubmit).toHaveBeenCalled();
});
});
});-
src/pages/MoverRegistration.tsx
- Lines 41-47: Made
primary_locationoptional in interface - Lines 75-85: Removed default (0, 0) coordinates from initial state
- Lines 110-157: Added comprehensive location validation in
handleSubmit - Line 221: Added non-null assertion for database insert
- Lines 41-47: Made
-
src/components/mover-registration/ServiceAreaStep.tsx
- Lines 58-61: Fixed duplicate error handler
- Lines 62-67: Enhanced
isValidcheck with comprehensive validation - Lines 113-124: Fixed conditional rendering for location display
- Lines 170-183: Added Next button with validation and error message
- ✅ No migration required - Existing records unchanged
- ✅ No breaking changes - Column type (
geography(Point, 4326)) remains the same - ✅ Future records - All new mover registrations will have valid locations
If there are existing movers with (0, 0) coordinates in the database:
-- Find invalid mover records
SELECT
id,
business_name,
ST_AsText(primary_location) as location
FROM movers
WHERE ST_X(primary_location::geometry) = 0
AND ST_Y(primary_location::geometry) = 0;
-- Mark them for manual review
UPDATE movers
SET verification_status = 'needs_review'
WHERE ST_X(primary_location::geometry) = 0
AND ST_Y(primary_location::geometry) = 0;- Service Area Configuration Guide
- PostGIS Geography Functions
- Location Services Integration
- Form Validation Patterns
✅ TypeScript Compilation: Passed with no errors
✅ Type Safety: primary_location properly optional, all accesses checked
✅ Validation Logic: Comprehensive checks at both UI and submission levels
✅ User Experience: Clear feedback, cannot proceed without valid location
✅ Data Integrity: Invalid coordinates cannot reach database
✅ Geographic Bounds: Only Kenya locations accepted
For reference, Kenya's approximate bounding box:
- Latitude: -5° to 5° (spans equator)
- Longitude: 34° to 42°
- Major Cities:
- Nairobi: -1.286389, 36.817223
- Mombasa: -4.043477, 39.668206
- Kisumu: -0.091702, 34.767956
- Nakuru: -0.303099, 36.080025
The validation uses slightly expanded bounds to avoid edge cases:
- Latitude: -5 to 5
- Longitude: 33 to 43
This ensures all of Kenya is included while rejecting clearly invalid locations.