Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c537cb6934 | ||
|
|
9cb3c7e67a | ||
|
|
4e1d0ef1a2 | ||
|
|
ed48fd96e7 | ||
|
|
71abe38520 | ||
|
|
984dc3cf6a | ||
|
|
8b128d7646 |
@@ -2580,6 +2580,7 @@ type Location {
|
||||
}
|
||||
|
||||
type PlaceDetailsResult {
|
||||
street: String
|
||||
state: String
|
||||
postcode: String
|
||||
city: String
|
||||
|
||||
@@ -2293,6 +2293,7 @@ export interface Location {
|
||||
}
|
||||
|
||||
export interface PlaceDetailsResult {
|
||||
street?: Scalars['String']
|
||||
state?: Scalars['String']
|
||||
postcode?: Scalars['String']
|
||||
city?: Scalars['String']
|
||||
@@ -5608,6 +5609,7 @@ export interface LocationGenqlSelection{
|
||||
}
|
||||
|
||||
export interface PlaceDetailsResultGenqlSelection{
|
||||
street?: boolean | number
|
||||
state?: boolean | number
|
||||
postcode?: boolean | number
|
||||
city?: boolean | number
|
||||
|
||||
@@ -5158,6 +5158,9 @@ export default {
|
||||
]
|
||||
},
|
||||
"PlaceDetailsResult": {
|
||||
"street": [
|
||||
1
|
||||
],
|
||||
"state": [
|
||||
1
|
||||
],
|
||||
|
||||
@@ -4298,6 +4298,7 @@ export type PlaceDetailsResult = {
|
||||
location?: Maybe<Location>;
|
||||
postcode?: Maybe<Scalars['String']>;
|
||||
state?: Maybe<Scalars['String']>;
|
||||
street?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type PostgresCredentials = {
|
||||
|
||||
+3
-1
@@ -132,18 +132,20 @@ describe('geo-map GraphQL queries', () => {
|
||||
queryDefinition.selectionSet.selections[0].selectionSet;
|
||||
const fields = selectionSet.selections.map((s: any) => s.name.value);
|
||||
|
||||
expect(fields).toContain('street');
|
||||
expect(fields).toContain('state');
|
||||
expect(fields).toContain('postcode');
|
||||
expect(fields).toContain('city');
|
||||
expect(fields).toContain('country');
|
||||
expect(fields).toContain('location');
|
||||
expect(fields).toHaveLength(5);
|
||||
expect(fields).toHaveLength(6);
|
||||
});
|
||||
|
||||
it('should match the expected query string', () => {
|
||||
const expectedQuery = gql`
|
||||
query GetAddressDetails($placeId: String!, $token: String!) {
|
||||
getAddressDetails(placeId: $placeId, token: $token) {
|
||||
street
|
||||
state
|
||||
postcode
|
||||
city
|
||||
|
||||
@@ -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;
|
||||
|
||||
+3
-2
@@ -7,6 +7,7 @@ import { RecordFieldComponentInstanceContext } from '@/object-record/record-fiel
|
||||
import { FieldInputEventContext } from '@/object-record/record-field/ui/contexts/FieldInputEventContext';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useContext } from 'react';
|
||||
import { castAsNumberOrNull } from '~/utils/cast-as-number-or-null';
|
||||
|
||||
export const AddressFieldInput = () => {
|
||||
const { draftValue, setDraftValue, fieldDefinition } = useAddressField();
|
||||
@@ -25,8 +26,8 @@ export const AddressFieldInput = () => {
|
||||
addressState: newAddress?.addressState ?? null,
|
||||
addressCountry: newAddress?.addressCountry ?? null,
|
||||
addressPostcode: newAddress?.addressPostcode ?? null,
|
||||
addressLat: newAddress?.addressLat ?? null,
|
||||
addressLng: newAddress?.addressLng ?? null,
|
||||
addressLat: castAsNumberOrNull(newAddress?.addressLat),
|
||||
addressLng: castAsNumberOrNull(newAddress?.addressLng),
|
||||
};
|
||||
};
|
||||
const settings = fieldDefinition.metadata.settings;
|
||||
|
||||
+39
@@ -140,6 +140,45 @@ describe('useAddressAutocomplete', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should use place street over full autocomplete text', async () => {
|
||||
const mockOnChange = jest.fn();
|
||||
|
||||
mockGetPlaceDetailsData.mockResolvedValue({
|
||||
street: '123 Main St',
|
||||
city: 'Springfield',
|
||||
state: 'IL',
|
||||
country: 'US',
|
||||
postcode: '62704',
|
||||
});
|
||||
mockFindCountryNameByCountryCode.mockReturnValue('United States');
|
||||
|
||||
const { result } = renderHook(() => useAddressAutocomplete(mockOnChange));
|
||||
|
||||
await act(async () => {
|
||||
await result.current.autoFillInputsFromPlaceDetails(
|
||||
'place123',
|
||||
'token123',
|
||||
'123 Main St, Springfield, IL 62704, USA',
|
||||
{
|
||||
addressStreet1: '',
|
||||
addressStreet2: null,
|
||||
addressCity: null,
|
||||
addressState: null,
|
||||
addressCountry: null,
|
||||
addressPostcode: null,
|
||||
addressLat: null,
|
||||
addressLng: null,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
addressStreet1: '123 Main St',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should preserve existing values when place data is missing', async () => {
|
||||
const mockOnChange = jest.fn();
|
||||
const mockPlaceData = {
|
||||
|
||||
@@ -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),
|
||||
|
||||
+3
@@ -11,6 +11,9 @@ export class LocationDTO {
|
||||
|
||||
@ObjectType('PlaceDetailsResult')
|
||||
export class PlaceDetailsResultDTO {
|
||||
@Field({ nullable: true })
|
||||
street?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
state?: string;
|
||||
|
||||
|
||||
+10
-14
@@ -3,14 +3,10 @@ import { Injectable } from '@nestjs/common';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
type AutocompleteSanitizedResult,
|
||||
sanitizeAutocompleteResults,
|
||||
} from 'src/engine/core-modules/geo-map/utils/sanitize-autocomplete-results.util';
|
||||
import {
|
||||
type AddressFields,
|
||||
sanitizePlaceDetailsResults,
|
||||
} from 'src/engine/core-modules/geo-map/utils/sanitize-place-details-results.util';
|
||||
import { type GeoMapAddressFields } from 'src/engine/core-modules/geo-map/types/geo-map-address-fields.type';
|
||||
import { type GeoMapAutocompleteSanitizedResult } from 'src/engine/core-modules/geo-map/types/geo-map-autocomplete-sanitized-result.type';
|
||||
import { sanitizeAutocompleteResults } from 'src/engine/core-modules/geo-map/utils/sanitize-autocomplete-results.util';
|
||||
import { sanitizePlaceDetailsResults } from 'src/engine/core-modules/geo-map/utils/sanitize-place-details-results.util';
|
||||
import { SecureHttpClientService } from 'src/engine/core-modules/secure-http-client/secure-http-client.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
|
||||
@@ -37,7 +33,7 @@ export class GeoMapService {
|
||||
token: string,
|
||||
country?: string,
|
||||
isFieldCity?: boolean,
|
||||
): Promise<AutocompleteSanitizedResult[] | undefined> {
|
||||
): Promise<GeoMapAutocompleteSanitizedResult[] | undefined> {
|
||||
if (!isNonEmptyString(address?.trim())) {
|
||||
return [];
|
||||
}
|
||||
@@ -64,7 +60,7 @@ export class GeoMapService {
|
||||
public async getAddressDetails(
|
||||
placeId: string,
|
||||
token: string,
|
||||
): Promise<AddressFields | undefined> {
|
||||
): Promise<GeoMapAddressFields | undefined> {
|
||||
const httpClient = this.secureHttpClientService.getHttpClient();
|
||||
|
||||
const result = await httpClient.get(
|
||||
@@ -72,10 +68,10 @@ export class GeoMapService {
|
||||
);
|
||||
|
||||
if (result.data.status === 'OK') {
|
||||
return sanitizePlaceDetailsResults(
|
||||
result.data.result?.address_components,
|
||||
result.data.result?.geometry?.location,
|
||||
);
|
||||
return sanitizePlaceDetailsResults({
|
||||
addressComponents: result.data.result?.address_components,
|
||||
location: result.data.result?.geometry?.location,
|
||||
});
|
||||
}
|
||||
|
||||
return {};
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
export type GeoMapAddressComponent = {
|
||||
long_name: string;
|
||||
short_name: string;
|
||||
types: string[];
|
||||
};
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { type GeoMapLocationFields } from 'src/engine/core-modules/geo-map/types/geo-map-location-fields.type';
|
||||
|
||||
export type GeoMapAddressFields = {
|
||||
street?: string;
|
||||
state?: string;
|
||||
postcode?: string;
|
||||
city?: string;
|
||||
country?: string;
|
||||
location?: GeoMapLocationFields;
|
||||
};
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export type GeoMapAutocompleteSanitizedResult = {
|
||||
text: string;
|
||||
placeId: string;
|
||||
};
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export type GeoMapGooglePrediction = {
|
||||
description: string;
|
||||
place_id: string;
|
||||
};
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export type GeoMapLocationFields = {
|
||||
lat?: number;
|
||||
lng?: number;
|
||||
};
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
import { sanitizeAutocompleteResults } from 'src/engine/core-modules/geo-map/utils/sanitize-autocomplete-results.util';
|
||||
|
||||
// Real Google Places Autocomplete API prediction format
|
||||
const GOOGLE_PREDICTIONS = [
|
||||
{
|
||||
description: '48 Pirrama Road, Pyrmont NSW 2009, Australia',
|
||||
place_id: 'ChIJN1t_tDeuEmsRUsoyG83frY4',
|
||||
},
|
||||
{
|
||||
description: '111 8th Avenue, New York, NY 10011, USA',
|
||||
place_id: 'ChIJaXQRs6lZwokRY6EFpJnhNNE',
|
||||
},
|
||||
{
|
||||
description: 'Eiffel Tower, Avenue Anatole France, Paris, France',
|
||||
place_id: 'ChIJLU7jZClu5kcR4PcOOO6p3I0',
|
||||
},
|
||||
];
|
||||
|
||||
describe('sanitizeAutocompleteResults', () => {
|
||||
it('should return empty array for empty input', () => {
|
||||
expect(sanitizeAutocompleteResults([])).toEqual([]);
|
||||
});
|
||||
|
||||
it('should map predictions to text and placeId', () => {
|
||||
const result = sanitizeAutocompleteResults(GOOGLE_PREDICTIONS);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
text: '48 Pirrama Road, Pyrmont NSW 2009, Australia',
|
||||
placeId: 'ChIJN1t_tDeuEmsRUsoyG83frY4',
|
||||
},
|
||||
{
|
||||
text: '111 8th Avenue, New York, NY 10011, USA',
|
||||
placeId: 'ChIJaXQRs6lZwokRY6EFpJnhNNE',
|
||||
},
|
||||
{
|
||||
text: 'Eiffel Tower, Avenue Anatole France, Paris, France',
|
||||
placeId: 'ChIJLU7jZClu5kcR4PcOOO6p3I0',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should map a single prediction', () => {
|
||||
const result = sanitizeAutocompleteResults([GOOGLE_PREDICTIONS[0]]);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].text).toBe('48 Pirrama Road, Pyrmont NSW 2009, Australia');
|
||||
expect(result[0].placeId).toBe('ChIJN1t_tDeuEmsRUsoyG83frY4');
|
||||
});
|
||||
});
|
||||
+281
@@ -0,0 +1,281 @@
|
||||
import { type GeoMapAddressComponent } from 'src/engine/core-modules/geo-map/types/geo-map-address-component.type';
|
||||
import { sanitizePlaceDetailsResults } from 'src/engine/core-modules/geo-map/utils/sanitize-place-details-results.util';
|
||||
|
||||
// Real Google Places API response for "48 Pirrama Road, Pyrmont NSW 2009, Australia"
|
||||
const GOOGLE_OFFICE_SYDNEY: GeoMapAddressComponent[] = [
|
||||
{ long_name: '48', short_name: '48', types: ['street_number'] },
|
||||
{
|
||||
long_name: 'Pirrama Road',
|
||||
short_name: 'Pirrama Rd',
|
||||
types: ['route'],
|
||||
},
|
||||
{
|
||||
long_name: 'Pyrmont',
|
||||
short_name: 'Pyrmont',
|
||||
types: ['locality', 'political'],
|
||||
},
|
||||
{
|
||||
long_name: 'City of Sydney',
|
||||
short_name: 'City of Sydney',
|
||||
types: ['administrative_area_level_2', 'political'],
|
||||
},
|
||||
{
|
||||
long_name: 'New South Wales',
|
||||
short_name: 'NSW',
|
||||
types: ['administrative_area_level_1', 'political'],
|
||||
},
|
||||
{
|
||||
long_name: 'Australia',
|
||||
short_name: 'AU',
|
||||
types: ['country', 'political'],
|
||||
},
|
||||
{ long_name: '2009', short_name: '2009', types: ['postal_code'] },
|
||||
];
|
||||
|
||||
// Real Google Places API response for "111 8th Avenue, New York, NY 10011, USA"
|
||||
const NYC_ADDRESS: GeoMapAddressComponent[] = [
|
||||
{ long_name: '111', short_name: '111', types: ['street_number'] },
|
||||
{
|
||||
long_name: '8th Avenue',
|
||||
short_name: '8th Ave',
|
||||
types: ['route'],
|
||||
},
|
||||
{
|
||||
long_name: 'New York',
|
||||
short_name: 'New York',
|
||||
types: ['locality', 'political'],
|
||||
},
|
||||
{
|
||||
long_name: 'New York County',
|
||||
short_name: 'New York County',
|
||||
types: ['administrative_area_level_2', 'political'],
|
||||
},
|
||||
{
|
||||
long_name: 'New York',
|
||||
short_name: 'NY',
|
||||
types: ['administrative_area_level_1', 'political'],
|
||||
},
|
||||
{
|
||||
long_name: 'United States',
|
||||
short_name: 'US',
|
||||
types: ['country', 'political'],
|
||||
},
|
||||
{ long_name: '10011', short_name: '10011', types: ['postal_code'] },
|
||||
{
|
||||
long_name: '1305',
|
||||
short_name: '1305',
|
||||
types: ['postal_code_suffix'],
|
||||
},
|
||||
];
|
||||
|
||||
// UK address using postal_town (London addresses often lack locality)
|
||||
const UK_ADDRESS: GeoMapAddressComponent[] = [
|
||||
{ long_name: '221B', short_name: '221B', types: ['street_number'] },
|
||||
{
|
||||
long_name: 'Baker Street',
|
||||
short_name: 'Baker St',
|
||||
types: ['route'],
|
||||
},
|
||||
{
|
||||
long_name: 'London',
|
||||
short_name: 'London',
|
||||
types: ['postal_town'],
|
||||
},
|
||||
{
|
||||
long_name: 'Greater London',
|
||||
short_name: 'Greater London',
|
||||
types: ['administrative_area_level_2', 'political'],
|
||||
},
|
||||
{
|
||||
long_name: 'England',
|
||||
short_name: 'England',
|
||||
types: ['administrative_area_level_1', 'political'],
|
||||
},
|
||||
{
|
||||
long_name: 'United Kingdom',
|
||||
short_name: 'GB',
|
||||
types: ['country', 'political'],
|
||||
},
|
||||
{ long_name: 'NW1 6XE', short_name: 'NW1 6XE', types: ['postal_code'] },
|
||||
];
|
||||
|
||||
// Landmark without street_number or route
|
||||
const LANDMARK: GeoMapAddressComponent[] = [
|
||||
{
|
||||
long_name: 'Paris',
|
||||
short_name: 'Paris',
|
||||
types: ['locality', 'political'],
|
||||
},
|
||||
{
|
||||
long_name: 'Paris',
|
||||
short_name: 'Paris',
|
||||
types: ['administrative_area_level_2', 'political'],
|
||||
},
|
||||
{
|
||||
long_name: 'Île-de-France',
|
||||
short_name: 'IDF',
|
||||
types: ['administrative_area_level_1', 'political'],
|
||||
},
|
||||
{
|
||||
long_name: 'France',
|
||||
short_name: 'FR',
|
||||
types: ['country', 'political'],
|
||||
},
|
||||
{ long_name: '75007', short_name: '75007', types: ['postal_code'] },
|
||||
];
|
||||
|
||||
describe('sanitizePlaceDetailsResults', () => {
|
||||
it('should return empty object for empty array', () => {
|
||||
expect(sanitizePlaceDetailsResults({ addressComponents: [] })).toEqual({});
|
||||
});
|
||||
|
||||
it('should parse a full US address (Google Sydney office)', () => {
|
||||
const result = sanitizePlaceDetailsResults({
|
||||
addressComponents: GOOGLE_OFFICE_SYDNEY,
|
||||
location: { lat: -33.866489, lng: 151.1958561 },
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
street: '48 Pirrama Road',
|
||||
city: 'Pyrmont',
|
||||
state: 'New South Wales',
|
||||
country: 'AU',
|
||||
postcode: '2009',
|
||||
location: { lat: -33.866489, lng: 151.1958561 },
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse US address with postal_code_suffix', () => {
|
||||
const result = sanitizePlaceDetailsResults({
|
||||
addressComponents: NYC_ADDRESS,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
street: '111 8th Avenue',
|
||||
city: 'New York',
|
||||
state: 'New York',
|
||||
country: 'US',
|
||||
postcode: '10011-1305',
|
||||
location: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should use postal_town as city fallback (UK address)', () => {
|
||||
const result = sanitizePlaceDetailsResults({
|
||||
addressComponents: UK_ADDRESS,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
street: '221B Baker Street',
|
||||
city: 'London',
|
||||
state: 'England',
|
||||
country: 'GB',
|
||||
postcode: 'NW1 6XE',
|
||||
location: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not set street when no street_number or route', () => {
|
||||
const result = sanitizePlaceDetailsResults({
|
||||
addressComponents: LANDMARK,
|
||||
});
|
||||
|
||||
expect(result.street).toBeUndefined();
|
||||
expect(result.city).toBe('Paris');
|
||||
expect(result.country).toBe('FR');
|
||||
});
|
||||
|
||||
it('should set street with only route (no street_number)', () => {
|
||||
const result = sanitizePlaceDetailsResults({
|
||||
addressComponents: [
|
||||
{
|
||||
long_name: 'Broadway',
|
||||
short_name: 'Broadway',
|
||||
types: ['route'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(result.street).toBe('Broadway');
|
||||
});
|
||||
|
||||
it('should use administrative_area_level_2 as state fallback', () => {
|
||||
const result = sanitizePlaceDetailsResults({
|
||||
addressComponents: [
|
||||
{
|
||||
long_name: 'Some County',
|
||||
short_name: 'Some County',
|
||||
types: ['administrative_area_level_2', 'political'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(result.state).toBe('Some County');
|
||||
});
|
||||
|
||||
it('should prefer administrative_area_level_1 over level_2 for state', () => {
|
||||
const result = sanitizePlaceDetailsResults({
|
||||
addressComponents: [
|
||||
{
|
||||
long_name: 'Some County',
|
||||
short_name: 'Some County',
|
||||
types: ['administrative_area_level_2', 'political'],
|
||||
},
|
||||
{
|
||||
long_name: 'California',
|
||||
short_name: 'CA',
|
||||
types: ['administrative_area_level_1', 'political'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(result.state).toBe('California');
|
||||
});
|
||||
|
||||
it('should prefer locality over postal_town for city', () => {
|
||||
const result = sanitizePlaceDetailsResults({
|
||||
addressComponents: [
|
||||
{
|
||||
long_name: 'Westminster',
|
||||
short_name: 'Westminster',
|
||||
types: ['locality', 'political'],
|
||||
},
|
||||
{
|
||||
long_name: 'London',
|
||||
short_name: 'London',
|
||||
types: ['postal_town'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(result.city).toBe('Westminster');
|
||||
});
|
||||
|
||||
it('should use administrative_area_level_3 as city fallback', () => {
|
||||
const result = sanitizePlaceDetailsResults({
|
||||
addressComponents: [
|
||||
{
|
||||
long_name: 'Small Town',
|
||||
short_name: 'Small Town',
|
||||
types: ['administrative_area_level_3', 'political'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(result.city).toBe('Small Town');
|
||||
});
|
||||
|
||||
it('should use short_name for country', () => {
|
||||
const result = sanitizePlaceDetailsResults({
|
||||
addressComponents: [
|
||||
{
|
||||
long_name: 'United States',
|
||||
short_name: 'US',
|
||||
types: ['country', 'political'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(result.country).toBe('US');
|
||||
});
|
||||
});
|
||||
+7
-12
@@ -1,17 +1,12 @@
|
||||
export type AutocompleteSanitizedResult = {
|
||||
text: string;
|
||||
placeId: string;
|
||||
};
|
||||
import { isNonEmptyArray } from 'twenty-shared/utils';
|
||||
|
||||
import { type GeoMapAutocompleteSanitizedResult } from 'src/engine/core-modules/geo-map/types/geo-map-autocomplete-sanitized-result.type';
|
||||
import { type GeoMapGooglePrediction } from 'src/engine/core-modules/geo-map/types/geo-map-google-prediction.type';
|
||||
|
||||
type GooglePrediction = {
|
||||
description: string;
|
||||
place_id: string;
|
||||
};
|
||||
export const sanitizeAutocompleteResults = (
|
||||
autocompleteResults: GooglePrediction[],
|
||||
): AutocompleteSanitizedResult[] => {
|
||||
if (!Array.isArray(autocompleteResults) || autocompleteResults.length === 0)
|
||||
return [];
|
||||
autocompleteResults: GeoMapGooglePrediction[],
|
||||
): GeoMapAutocompleteSanitizedResult[] => {
|
||||
if (!isNonEmptyArray(autocompleteResults)) return [];
|
||||
|
||||
return autocompleteResults.map((result) => ({
|
||||
text: result.description,
|
||||
|
||||
+65
-62
@@ -1,77 +1,80 @@
|
||||
export type AddressComponent = {
|
||||
long_name: string;
|
||||
short_name: string;
|
||||
types: string[];
|
||||
};
|
||||
export type AddressFields = {
|
||||
state?: string;
|
||||
postcode?: string;
|
||||
city?: string;
|
||||
country?: string;
|
||||
location?: locationFields;
|
||||
};
|
||||
export type locationFields = {
|
||||
lat?: number;
|
||||
lng?: number;
|
||||
};
|
||||
export const sanitizePlaceDetailsResults = (
|
||||
AddressComponents: AddressComponent[],
|
||||
location?: locationFields,
|
||||
): AddressFields => {
|
||||
if (!AddressComponents || AddressComponents.length === 0) return {};
|
||||
import { isNonEmptyArray } from 'twenty-shared/utils';
|
||||
|
||||
const address: AddressFields = {};
|
||||
import { type GeoMapAddressComponent } from 'src/engine/core-modules/geo-map/types/geo-map-address-component.type';
|
||||
import { type GeoMapAddressFields } from 'src/engine/core-modules/geo-map/types/geo-map-address-fields.type';
|
||||
import { type GeoMapLocationFields } from 'src/engine/core-modules/geo-map/types/geo-map-location-fields.type';
|
||||
|
||||
for (const AddressComponent of AddressComponents) {
|
||||
for (const type of AddressComponent.types) {
|
||||
switch (type) {
|
||||
case 'postal_code': {
|
||||
address.postcode =
|
||||
AddressComponent.long_name + (address.postcode ?? '');
|
||||
break;
|
||||
}
|
||||
const hasType = (
|
||||
addressComponent: GeoMapAddressComponent,
|
||||
type: string,
|
||||
): boolean => addressComponent.types.includes(type);
|
||||
|
||||
case 'postal_code_suffix': {
|
||||
address.postcode =
|
||||
(address.postcode ?? '') + '-' + AddressComponent.long_name;
|
||||
break;
|
||||
}
|
||||
export const sanitizePlaceDetailsResults = ({
|
||||
addressComponents,
|
||||
location,
|
||||
}: {
|
||||
addressComponents: GeoMapAddressComponent[];
|
||||
location?: GeoMapLocationFields;
|
||||
}): GeoMapAddressFields => {
|
||||
if (!isNonEmptyArray(addressComponents)) return {};
|
||||
|
||||
case 'locality':
|
||||
address.city = AddressComponent.long_name;
|
||||
break;
|
||||
const address: GeoMapAddressFields = {};
|
||||
|
||||
case 'postal_town':
|
||||
if (!address.city) {
|
||||
address.city = AddressComponent.long_name;
|
||||
}
|
||||
break;
|
||||
for (const addressComponent of addressComponents) {
|
||||
if (hasType(addressComponent, 'street_number')) {
|
||||
address.street =
|
||||
addressComponent.long_name + ' ' + (address.street ?? '');
|
||||
continue;
|
||||
}
|
||||
|
||||
case 'administrative_area_level_3': {
|
||||
if (!address.city) {
|
||||
address.city = AddressComponent.long_name;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (hasType(addressComponent, 'route')) {
|
||||
address.street = (address.street ?? '') + addressComponent.long_name;
|
||||
continue;
|
||||
}
|
||||
|
||||
case 'administrative_area_level_1': {
|
||||
address.state = AddressComponent.long_name;
|
||||
break;
|
||||
}
|
||||
if (hasType(addressComponent, 'postal_code')) {
|
||||
address.postcode = addressComponent.long_name + (address.postcode ?? '');
|
||||
continue;
|
||||
}
|
||||
|
||||
case 'administrative_area_level_2': {
|
||||
if (!address.state) {
|
||||
address.state = AddressComponent.long_name;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (hasType(addressComponent, 'postal_code_suffix')) {
|
||||
address.postcode =
|
||||
(address.postcode ?? '') + '-' + addressComponent.long_name;
|
||||
continue;
|
||||
}
|
||||
|
||||
case 'country':
|
||||
address.country = AddressComponent.short_name;
|
||||
break;
|
||||
if (hasType(addressComponent, 'locality')) {
|
||||
address.city = addressComponent.long_name;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
hasType(addressComponent, 'postal_town') ||
|
||||
hasType(addressComponent, 'administrative_area_level_3')
|
||||
) {
|
||||
if (!address.city) {
|
||||
address.city = addressComponent.long_name;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hasType(addressComponent, 'administrative_area_level_1')) {
|
||||
address.state = addressComponent.long_name;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hasType(addressComponent, 'administrative_area_level_2')) {
|
||||
if (!address.state) {
|
||||
address.state = addressComponent.long_name;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hasType(addressComponent, 'country')) {
|
||||
address.country = addressComponent.short_name;
|
||||
}
|
||||
}
|
||||
|
||||
address.location = location;
|
||||
|
||||
return address;
|
||||
|
||||
Reference in New Issue
Block a user