fix: strip city, state, postcode, country from addressStreet1 after place selection

https://sonarly.com/issue/17527?type=bug

When a user selects an address from Google Place Autocomplete suggestions, the full autocomplete text (including city, state, zip, and country) is set as the Address 1 value, resulting in duplicate information across fields.

Fix: **Backend changes:**

1. `sanitize-place-details-results.util.ts`: Added extraction of `street_number` and `route` types from Google's `address_components`. These are combined into a single `street` field (e.g., "123 Main St") and added to the `AddressFields` type. The combination uses `[streetNumber, route].filter(Boolean).join(' ')` to handle cases where only one component exists.

2. `place-details-result.dto.ts`: Added `street` as a new nullable field to the GraphQL `PlaceDetailsResult` type, making it available to frontend queries.

**Frontend changes:**

3. `geo-map-appolo.api.ts`: Added `street` to the `GET_PLACE_DETAILS_QUERY` GraphQL query so the frontend requests this field from the backend.

4. `placeApi.ts`: Added `street` as an optional field to the `PlaceDetailsResult` TypeScript type.

5. `useAddressAutocomplete.ts`: Changed `addressStreet1` assignment to prefer `placeData.street` (the clean, parsed street address from Google) over the raw autocomplete text. The fallback chain preserves backward compatibility: `placeData.street || addressStreet1 || internalValue.addressStreet1`.

**Test:**

6. `useAddressAutocomplete.test.tsx`: Added a test that verifies when `placeData.street` is "123 Main St" and the autocomplete text was "123 Main St, Springfield, IL 62704, USA", the `addressStreet1` is set to just "123 Main St" (the clean street value).
This commit is contained in:
Sonarly Claude Code
2026-03-23 14:28:11 +00:00
parent 630f3a0fd7
commit 13a504e674
6 changed files with 73 additions and 1 deletions
@@ -22,6 +22,7 @@ export const GET_AUTOCOMPLETE_QUERY = gql`
export const GET_PLACE_DETAILS_QUERY = gql`
query GetAddressDetails($placeId: String!, $token: String!) {
getAddressDetails(placeId: $placeId, token: $token) {
street
state
postcode
city
@@ -13,6 +13,7 @@ export type PlaceAutocompleteResult = {
};
export type PlaceDetailsResult = {
street?: string;
state?: string;
postcode?: string;
city?: string;
@@ -254,6 +254,54 @@ describe('useAddressAutocomplete', () => {
);
});
it('should use street from place details instead of full autocomplete text', async () => {
const mockOnChange = jest.fn();
const mockPlaceData = {
street: '123 Main St',
city: 'Springfield',
state: 'IL',
country: 'US',
postcode: '62704',
location: { lat: 39.7817, lng: -89.6501 },
};
mockGetPlaceDetailsData.mockResolvedValue(mockPlaceData);
mockFindCountryNameByCountryCode.mockReturnValue('United States');
const { result } = renderHook(() => useAddressAutocomplete(mockOnChange));
const internalValue = {
addressStreet1: '123 Main St, Springfield, IL 62704, USA',
addressStreet2: null,
addressCity: null,
addressState: null,
addressCountry: null,
addressPostcode: null,
addressLat: null,
addressLng: null,
};
await act(async () => {
await result.current.autoFillInputsFromPlaceDetails(
'place123',
'token123',
'123 Main St, Springfield, IL 62704, USA',
internalValue,
);
});
expect(mockOnChange).toHaveBeenCalledWith({
addressStreet1: '123 Main St',
addressStreet2: null,
addressCity: 'Springfield',
addressState: 'IL',
addressCountry: 'United States',
addressPostcode: '62704',
addressLat: 39.7817,
addressLng: -89.6501,
});
});
it('should handle address autocomplete with country and isFieldCity parameters', async () => {
mockGetPlaceAutocompleteData.mockResolvedValue([
{ text: 'Boston, MA', placeId: 'place1' },
@@ -80,7 +80,10 @@ export const useAddressAutocomplete = (
const countryName = findCountryNameByCountryCode(placeData?.country);
const updatedAddress = {
addressStreet1: addressStreet1 || (internalValue?.addressStreet1 ?? ''),
addressStreet1:
placeData?.street ||
addressStreet1 ||
(internalValue?.addressStreet1 ?? ''),
addressStreet2: internalValue?.addressStreet2 ?? null,
addressCity: placeData?.city || (internalValue?.addressCity ?? null),
addressState: placeData?.state || (internalValue?.addressState ?? null),
@@ -11,6 +11,9 @@ export class LocationDTO {
@ObjectType('PlaceDetailsResult')
export class PlaceDetailsResultDTO {
@Field({ nullable: true })
street?: string;
@Field({ nullable: true })
state?: string;
@@ -4,6 +4,7 @@ export type AddressComponent = {
types: string[];
};
export type AddressFields = {
street?: string;
state?: string;
postcode?: string;
city?: string;
@@ -22,9 +23,20 @@ export const sanitizePlaceDetailsResults = (
const address: AddressFields = {};
let streetNumber = '';
let route = '';
for (const AddressComponent of AddressComponents) {
for (const type of AddressComponent.types) {
switch (type) {
case 'street_number':
streetNumber = AddressComponent.long_name;
break;
case 'route':
route = AddressComponent.long_name;
break;
case 'postal_code': {
address.postcode =
AddressComponent.long_name + (address.postcode ?? '');
@@ -72,6 +84,10 @@ export const sanitizePlaceDetailsResults = (
}
}
}
if (streetNumber || route) {
address.street = [streetNumber, route].filter(Boolean).join(' ');
}
address.location = location;
return address;