Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 9 additions & 11 deletions addon/components/utils/address-form.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,14 @@
{{t "upf_utils.address_form.line_1"}}
</label>
{{#if this.useGoogleAutocomplete}}
<div class="fx-col google-autocomplete-input-container" data-control-name="address-form-address1">
<Input
@value={{get @address (concat this.addressKey "1")}}
class="upf-input"
placeholder={{t "upf_utils.address_form.placeholder.line1"}}
{{did-insert this.initAutoCompletion}}
{{on "keyup" (fn this.onFieldUpdate (concat this.addressKey "1"))}}
{{(if (eq @focus "line1") (modifier "enable-input-autofocus"))}}
/>
</div>
<OSS::InputContainer
@value={{get @address (concat this.addressKey "1")}}
@onChange={{fn this.onFieldUpdate (concat this.addressKey "1")}}
@placeholder={{t "upf_utils.address_form.placeholder.line1"}}
data-control-name="address-form-address1"
{{(if (eq @focus "line1") (modifier "enable-input-autofocus"))}}
{{setup-autocomplete callback=this.onAddressSelected}}
/>
{{else}}
<OSS::InputContainer
@value={{get @address (concat this.addressKey "1")}}
Expand Down Expand Up @@ -157,4 +155,4 @@
/>
</div>
</div>
</div>
</div>
122 changes: 15 additions & 107 deletions addon/components/utils/address-form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@ import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action, get, set } from '@ember/object';
import { isEmpty } from '@ember/utils';
import { isTesting } from '@embroider/macros';
import { getOwner } from '@ember/application';

import { Loader } from '@googlemaps/js-api-loader';
import { CountryData, countries } from '@upfluence/oss-components/utils/country-codes';
import { next } from '@ember/runloop';
import { type AutocompletionAddress } from '@upfluence/ember-upf-utils/modifiers/setup-autocomplete';

type FocusableInput =
| 'first-name'
Expand All @@ -32,9 +29,6 @@ interface UtilsAddressFormArgs {
}

type ProvinceData = { code: string; name: string };
type GAddressComponent = google.maps.GeocoderAddressComponent;
type GPlaceResult = google.maps.places.PlaceResult;
type GAutoComplete = google.maps.places.Autocomplete;
type AddressKey = 'address' | 'line';

const BASE_VALIDATED_ADDRESS_FIELDS: string[] = ['city', 'countryCode', 'zipcode'];
Expand Down Expand Up @@ -63,27 +57,6 @@ export default class extends Component<UtilsAddressFormArgs> {
return this.args.useGoogleAutocomplete ?? true;
}

@action
initAutoCompletion(): void {
if (isTesting()) return;
this.appendContainerLocally();
const loader = new Loader({
apiKey: getOwner(this).resolveRegistration('config:environment').google_map_api_key,
version: 'weekly'
});

loader.importLibrary('places').then(({ Autocomplete }) => {
const input = document.querySelector('[data-control-name="address-form-address1"] input') as HTMLInputElement;
const options = {
fields: ['address_components'],
strictBounds: false,
types: ['address']
};
const autocomplete = new Autocomplete(input, options);
this.initInputListeners(autocomplete, input);
});
}

@action
selectCountryCode(code: { id: string }): void {
set(this.args.address, 'countryCode', code.id);
Expand Down Expand Up @@ -123,6 +96,20 @@ export default class extends Component<UtilsAddressFormArgs> {
this.args.onChange?.(this.args.address, this.checkAddressValidity());
}

@action
onAddressSelected(address: AutocompletionAddress): void {
set(this.args.address, `${this.addressKey}1`, address.address1);
if (address.address2) {
set(this.args.address, `${this.addressKey}2`, address.address2);
}
this.applyCountry(address.country);
set(this.args.address, 'city', address.city);
set(this.args.address, 'state', address.state);
set(this.args.address, 'zipcode', address.zipcode);

this.onFieldUpdate();
}

private checkAddressValidity(): boolean {
if (!isEmpty(this.provincesForCountry) && isEmpty(get(this.args.address, 'state'))) return false;

Expand All @@ -146,70 +133,6 @@ export default class extends Component<UtilsAddressFormArgs> {
});
}

private initInputListeners(autocomplete: GAutoComplete, input: HTMLElement): void {
autocomplete.addListener('place_changed', () => {
const place = autocomplete.getPlace();
this.fillInAddress(place);
});
input.addEventListener('focusout', (event) => {
if ((<HTMLInputElement>event.target).value !== get(this.args.address, `${this.addressKey}1`)) {
(<HTMLInputElement>event.target).value = get(this.args.address, `${this.addressKey}1`) ?? '';
}
});
}

private fillInAddress(place: GPlaceResult): void {
let address1: string = '';
let address2: string = '';
let zipcode: string = '';
let city: string = '';

const mapper: { [key: string]: (comp: GAddressComponent) => void } = {
street_number: (comp) => {
address1 = `${comp.long_name} ${address1}`;
},
route: (comp) => {
address1 += comp.long_name;
},
subpremise: (comp) => {
address2 = comp.long_name;
},
postal_code: (comp) => {
zipcode = `${comp.long_name}${zipcode}`;
},
postal_code_suffix: (comp) => {
zipcode = `${zipcode}-${comp.long_name}`;
},
locality: (comp) => {
city = comp.long_name;
},
postal_town: (comp) => {
city = comp.long_name;
},
administrative_area_level_1: (comp) => {
set(this.args.address, 'state', comp.long_name || '');
},
country: (comp) => {
const selectedCountry = this.countries.find((country) => country.alpha2 === comp.short_name);

if (!selectedCountry) return;
this.applyCountry(selectedCountry);
}
};

(place.address_components ?? []).reverse().forEach((component) => {
const componentType: string = component.types[0];

mapper[componentType]?.(component);
});

set(this.args.address, `${this.addressKey}1`, address1);
set(this.args.address, `${this.addressKey}2`, address2);
set(this.args.address, 'zipcode', zipcode);
set(this.args.address, 'city', city);
this.onFieldUpdate();
}

private validatedAddressFieldsHandler(): void {
this.validatedAddressFields.push(`${this.addressKey}1`);

Expand All @@ -222,21 +145,6 @@ export default class extends Component<UtilsAddressFormArgs> {
}
}

private appendContainerLocally(): void {
const observer = new MutationObserver((mutationList: any) => {
for (const mutation of mutationList) {
if (mutation.type === 'childList') {
const pacContainer = mutation.addedNodes[0];
if (!pacContainer?.classList.contains('pac-container')) return;

document.querySelector('[data-control-name="address-form-address1"]')?.append(pacContainer);
observer.disconnect();
}
}
});
observer.observe(document.body, { childList: true });
}

private openCountrySelect(): void {
next(() => {
(document.querySelector('[data-control-name="address-form-country"] .upf-input') as HTMLInputElement)?.click();
Expand Down
18 changes: 7 additions & 11 deletions addon/components/utils/address-inline.hbs
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
<div class="fx-col fx-gap-px-6 fx-1" ...attributes>
{{#if this.useGoogleAutocomplete}}
<div class="fx-col fx-1 google-autocomplete-input-container">
<OSS::InputContainer
@onChange={{this.onChange}}
@value={{@value.address}}
{{did-insert this.initAutoCompletion}}
data-control-name="address-inline"
/>
</div>
<OSS::InputContainer
@onChange={{this.onChange}}
@value={{@value.address}}
data-control-name="address-inline"
{{setup-autocomplete callback=this.onAddressSelected}}
/>
{{else}}
<div class="fx-col fx-1">
<OSS::InputContainer @value={{@value.address}} @onChange={{this.onChange}} data-control-name="address-inline" />
</div>
<OSS::InputContainer @value={{@value.address}} @onChange={{this.onChange}} data-control-name="address-inline" />
{{/if}}
</div>
111 changes: 11 additions & 100 deletions addon/components/utils/address-inline.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { isTesting } from '@embroider/macros';
import { getOwner } from '@ember/application';

import { Loader } from '@googlemaps/js-api-loader';
import { type AutocompletionAddress } from '@upfluence/ember-upf-utils/modifiers/setup-autocomplete';

interface UtilsAddressInlineArgs {
value: ShippingAddress;
Expand All @@ -22,113 +19,27 @@ export type ShippingAddress = {
} | null;
};

type GAddressComponent = google.maps.GeocoderAddressComponent;
type GAutoComplete = google.maps.places.Autocomplete;
type GPlaceResult = google.maps.places.PlaceResult;

export default class extends Component<UtilsAddressInlineArgs> {
get useGoogleAutocomplete(): boolean {
return this.args.useGoogleAutocomplete ?? true;
}

@action
initAutoCompletion(): void {
if (isTesting()) return;
this.appendContainerLocally();
const loader = new Loader({
// @ts-ignore
apiKey: getOwner(this).resolveRegistration('config:environment').google_map_api_key,
version: 'weekly'
});

loader.importLibrary('places').then(({ Autocomplete }) => {
const input = document.querySelector('[data-control-name="address-inline"] input') as HTMLInputElement;
const options = {
fields: ['address_components'],
strictBounds: false,
types: ['address']
};
const autocomplete = new Autocomplete(input, options);
this.initInputListeners(autocomplete, input);
onAddressSelected(address: AutocompletionAddress): void {
this.args.onChange({
address: address.formattedAddress,
resolved_address: {
line_1: address.address1,
zipcode: address.zipcode,
city: address.city,
state: address.state,
country_code: address.country.alpha2
}
});
}

@action
onChange(value: string): void {
this.args.onChange({ address: value, resolved_address: null });
}

private appendContainerLocally(): void {
const observer = new MutationObserver((mutationList: any) => {
for (const mutation of mutationList) {
if (mutation.type === 'childList') {
const pacContainer = mutation.addedNodes[0];
if (!pacContainer?.classList.contains('pac-container')) return;

document.querySelector('[data-control-name="address-inline"]')?.append(pacContainer);
observer.disconnect();
}
}
});
observer.observe(document.body, { childList: true });
}

private initInputListeners(autocomplete: GAutoComplete, input: HTMLInputElement): void {
autocomplete.addListener('place_changed', () => {
const place = autocomplete.getPlace();
this.updateAddress(place, input);
});
}

private updateAddress(place: GPlaceResult, input: HTMLInputElement): void {
let address1: string = '';
let zipcode: string = '';
let city: string = '';
let state: string = '';
let country: string = '';

const mapper: { [key: string]: (comp: GAddressComponent) => void } = {
street_number: (comp) => {
address1 = `${comp.long_name} ${address1}`;
},
route: (comp) => {
address1 += comp.long_name;
},
postal_code: (comp) => {
zipcode = `${comp.long_name}${zipcode}`;
},
postal_code_suffix: (comp) => {
zipcode = `${zipcode}-${comp.long_name}`;
},
locality: (comp) => {
city = comp.long_name;
},
postal_town: (comp) => {
city = comp.long_name;
},
administrative_area_level_1: (comp) => {
state = comp.long_name ?? '';
},
country: (comp) => {
country = comp.short_name;
}
};

(place.address_components ?? []).reverse().forEach((component) => {
const componentType: string = component.types[0];

mapper[componentType]?.(component);
});

this.args.onChange({
address: input.value,
resolved_address: {
line_1: address1,
zipcode,
city,
state,
country_code: country
}
});
}
}
Loading