From 8a5d54f19fac21b7e32ff02fe160d302a26efc1b Mon Sep 17 00:00:00 2001 From: Jerry Smidt Date: Wed, 12 Nov 2025 10:43:02 +0100 Subject: [PATCH 1/8] Don't export .github/ dir --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index 5966153..aa27342 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ .gitattributes export-ignore .gitignore export-ignore +.github/ export-ignore From ca1684b772956ff04e1c8fb49a029583775b9dad Mon Sep 17 00:00:00 2001 From: Jerry Smidt Date: Wed, 14 Jan 2026 12:58:58 +0100 Subject: [PATCH 2/8] Add address autocomplete to admin order form - Update autocomplete library to 1.4.2. - Fix street lines logic in NL components. refs postcode-nl#60, postcode-nl#104 --- Data/Form/Element/AddressAutofill.php | 144 +++++++++++ Helper/StoreConfigHelper.php | 1 + .../AdminAddressAutocompleteBehavior.php | 24 ++ Model/Config/Source/NlInputBehavior.php | 4 +- .../AddAddressAutofillToOrderCreateForm.php | 91 +++++++ Plugin/SortSalesOrderAddressFields.php | 57 +++++ etc/adminhtml/di.xml | 8 + etc/adminhtml/system.xml | 9 +- etc/config.xml | 1 + etc/module.xml | 1 + i18n/en_US.csv | 13 + i18n/nl_BE.csv | 15 +- i18n/nl_NL.csv | 15 +- .../layout/sales_order_create_index.xml | 7 + .../form/element/address-autofill.phtml | 53 ++++ view/adminhtml/web/css/source/_module.less | 15 ++ .../adminhtml/web/js/view/address/autofill.js | 65 +++++ .../order_create/address-autofill-intl.js | 47 ++++ .../sales/order_create/address-autofill-nl.js | 69 ++++++ .../form/element/address-autofill-field.html | 13 + .../lib/postcode-eu-autocomplete-address.css | 22 +- view/base/web/css/source/_shared.less | 23 ++ .../js/form/components/address-autofill-nl.js | 229 ++++++++++++++++++ .../js/form/element/address-autofill-intl.js | 30 ++- .../js/ko/bindings/init-intl-autocomplete.js | 2 + .../lib/postcode-eu-autocomplete-address.js | 42 +++- .../web/js/model/address-nl.js | 0 .../form/element/address-autofill-intl.html | 13 + view/frontend/layout/checkout_index_index.xml | 7 +- view/frontend/web/css/source/_module.less | 24 +- .../js/form/components/address-autofill-nl.js | 205 +--------------- .../checkout/address-autofill-nl.js | 20 +- .../customer/address/address-autofill-nl.js | 15 -- .../element/checkout/address-autofill-intl.js | 4 +- .../customer/address/address-autofill-intl.js | 17 +- 35 files changed, 998 insertions(+), 307 deletions(-) create mode 100644 Data/Form/Element/AddressAutofill.php create mode 100644 Model/Config/Source/AdminAddressAutocompleteBehavior.php create mode 100644 Plugin/AddAddressAutofillToOrderCreateForm.php create mode 100644 Plugin/SortSalesOrderAddressFields.php create mode 100644 view/adminhtml/layout/sales_order_create_index.xml create mode 100644 view/adminhtml/templates/form/element/address-autofill.phtml create mode 100644 view/adminhtml/web/css/source/_module.less create mode 100644 view/adminhtml/web/js/view/address/autofill.js create mode 100644 view/adminhtml/web/js/view/form/sales/order_create/address-autofill-intl.js create mode 100644 view/adminhtml/web/js/view/form/sales/order_create/address-autofill-nl.js create mode 100644 view/adminhtml/web/template/form/element/address-autofill-field.html rename view/{frontend => base}/web/css/lib/postcode-eu-autocomplete-address.css (97%) create mode 100644 view/base/web/css/source/_shared.less create mode 100644 view/base/web/js/form/components/address-autofill-nl.js rename view/{frontend => base}/web/js/form/element/address-autofill-intl.js (72%) rename view/{frontend => base}/web/js/ko/bindings/init-intl-autocomplete.js (98%) rename view/{frontend => base}/web/js/lib/postcode-eu-autocomplete-address.js (96%) rename view/{frontend => base}/web/js/model/address-nl.js (100%) create mode 100644 view/base/web/template/form/element/address-autofill-intl.html mode change 100644 => 120000 view/frontend/web/js/form/components/address-autofill-nl.js diff --git a/Data/Form/Element/AddressAutofill.php b/Data/Form/Element/AddressAutofill.php new file mode 100644 index 0000000..68c2eb6 --- /dev/null +++ b/Data/Form/Element/AddressAutofill.php @@ -0,0 +1,144 @@ +setType('postcode-eu-address-autofill'); + $this->_layout = $layout; + } + + /** + * Get element HTML + * + * @return string + */ + public function getElementHtml(): string + { + $block = $this->_layout->createBlock( + \Magento\Backend\Block\Template::class, + $this->getId(), + [ + 'data' => [ + 'template' => 'Flekto_Postcode::form/element/address-autofill.phtml', + 'jsLayout' => $this->getJsLayout(), + ], + ], + ); + + return $block->toHtml(); + } + + /** + * Get JS layout + * + * @return array + */ + public function getJsLayout(): array + { + return [ + 'components' => [ + $this->getId() => [ + 'component' => 'Flekto_Postcode/js/view/address/autofill', + 'config' => [ + 'settings' => $this->getData('settings'), + 'htmlIdPrefix' => $this->getData('htmlIdPrefix'), + 'addressType' => $this->getData('addressType'), + 'countryCode' => $this->getData('countryCode'), + 'visible' => $this->getData('visible'), + ], + 'children' => [ + 'address_autofill_nl' => [ + 'component' => 'Flekto_Postcode/js/view/form/sales/order_create/address-autofill-nl', + 'config' => [ + 'componentDisabled' => $this->getData('isNlComponentDisabled'), + ], + 'children' => [ + 'postcode' => [ + 'component' => 'Magento_Ui/js/form/element/abstract', + 'label' => __('Zip/Postal Code'), + 'config' => [ + 'template' => 'ui/form/field', + 'elementTmpl' => 'Flekto_Postcode/form/element/address-autofill-field', + 'placeholder' => '1234 AB', + 'imports' => [ + 'visible' => '${ $.parentName }:visible', + ], + ], + ], + 'house_number' => [ + 'component' => 'Magento_Ui/js/form/element/abstract', + 'label' => __('House number and addition'), + 'additionalClasses' => [ + 'address-autofill-nl-house-number' => true, + ], + 'config' => [ + 'template' => 'ui/form/field', + 'elementTmpl' => 'Flekto_Postcode/form/element/address-autofill-field', + 'imports' => [ + 'visible' => '${ $.parentName }:visible', + ], + ], + ], + 'house_number_select' => [ + 'component' => 'Magento_Ui/js/form/element/select', + 'label' => __('Which house number do you mean?'), + 'config' => [ + 'caption' => __('- Select house number -'), + 'template' => 'ui/form/field', + 'visible' => false, + ], + ], + ], + ], + 'address_autofill_intl' => [ + 'component' => 'Flekto_Postcode/js/view/form/sales/order_create/address-autofill-intl', + 'label' => __('Find an address'), + 'placeholder' => __('City, street or postcode') + ], + 'address_autofill_error' => [ + 'component' => 'Magento_Ui/js/form/components/html', + 'config' => [ + 'visible' => false, + 'listens' => [ + '${$.parentName}:error' => 'content', + 'content' => 'visible', + ], + 'additionalClasses' => [ + 'admin__field-note' => true, + 'address-autofill-warning' => true, + ], + ], + ], + ], + ], + ], + ]; + } +} diff --git a/Helper/StoreConfigHelper.php b/Helper/StoreConfigHelper.php index f83f6a2..d7ff7cf 100644 --- a/Helper/StoreConfigHelper.php +++ b/Helper/StoreConfigHelper.php @@ -38,6 +38,7 @@ class StoreConfigHelper extends AbstractHelper 'disabled_countries' => 'postcodenl_api/advanced_config/disabled_countries', 'allow_pobox_shipping' => 'postcodenl_api/advanced_config/allow_pobox_shipping', 'split_street_values' => 'postcodenl_api/advanced_config/split_street_values', + 'admin_address_autocomplete_behavior' => 'postcodenl_api/advanced_config/admin_address_autocomplete_behavior', ]; /** diff --git a/Model/Config/Source/AdminAddressAutocompleteBehavior.php b/Model/Config/Source/AdminAddressAutocompleteBehavior.php new file mode 100644 index 0000000..cd3bca8 --- /dev/null +++ b/Model/Config/Source/AdminAddressAutocompleteBehavior.php @@ -0,0 +1,24 @@ + static::DEFAULT, 'label' => __('Equal to configuration for checkout')], + ['value' => static::SINGLE_INPUT, 'label' => __('Use free address input for Dutch address')], + ['value' => static::DUTCH_LOOKUP, 'label' => __('Use zip code and house number inputs for Dutch address')], + ['value' => static::DISABLE, 'label' => __('Disabled')], + ]; + } +} diff --git a/Model/Config/Source/NlInputBehavior.php b/Model/Config/Source/NlInputBehavior.php index 4b7db46..8c16b3d 100644 --- a/Model/Config/Source/NlInputBehavior.php +++ b/Model/Config/Source/NlInputBehavior.php @@ -13,8 +13,8 @@ class NlInputBehavior implements \Magento\Framework\Data\OptionSourceInterface public function toOptionArray(): array { return [ - ['value' => 'zip_house', 'label' => __('Only zip code and house number (default)')], - ['value' => 'free', 'label' => __('Free address input')], + ['value' => static::ZIP_HOUSE, 'label' => __('Only zip code and house number (default)')], + ['value' => static::FREE, 'label' => __('Free address input')], ]; } } diff --git a/Plugin/AddAddressAutofillToOrderCreateForm.php b/Plugin/AddAddressAutofillToOrderCreateForm.php new file mode 100644 index 0000000..eed7f52 --- /dev/null +++ b/Plugin/AddAddressAutofillToOrderCreateForm.php @@ -0,0 +1,91 @@ +_storeConfigHelper = $storeConfigHelper; + $this->_dataHelper = $dataHelper; + $this->_directoryHelper = $directoryHelper; + } + + /** + * @param AddressBlock $subject + * @param Form $form + * @return Form + */ + public function afterGetForm(AddressBlock $subject, Form $form) + { + $fieldset = $form->getElement('main'); + $autocompleteBehavior = $this->_storeConfigHelper->getValue('admin_address_autocomplete_behavior'); + + if ( + $fieldset === null + || $this->_dataHelper->isDisabled() + || $autocompleteBehavior === AdminAddressAutocompleteBehavior::DISABLE + ) { + return $form; + } + + $fieldset->addType( + 'postcode-eu-address-autofill', + \Flekto\Postcode\Data\Form\Element\AddressAutofill::class, + ); + $addressType = $subject->getIsShipping() ? 'shipping' : 'billing'; + $fieldId = $addressType . '_address_autofill'; + $countryId = $subject->getAddress()->getCountryId() ?? $this->_directoryHelper->getDefaultCountry(); + $isVisible = in_array($countryId, $this->_storeConfigHelper->getEnabledCountries()); + + if ($autocompleteBehavior !== AdminAddressAutocompleteBehavior::DEFAULT) { + $isNlComponentDisabled = $autocompleteBehavior === AdminAddressAutocompleteBehavior::SINGLE_INPUT; + } + + if ($form->getElement($fieldId) === null) { + $fieldset->addField( + $fieldId, + 'postcode-eu-address-autofill', + [ + 'settings' => $this->_storeConfigHelper->getJsinit(), + 'htmlIdPrefix' => $form->getHtmlIdPrefix(), + 'addressType' => $addressType, + 'label' => __('Address autocomplete'), + 'countryCode' => $countryId, + 'visible' => $isVisible, + 'css_class' => $isVisible ? '' : 'hidden', + 'isNlComponentDisabled' => $isNlComponentDisabled ?? $this->_dataHelper->isNlComponentDisabled(), + ], + 'country_id', + ); + } + + return $form; + } +} diff --git a/Plugin/SortSalesOrderAddressFields.php b/Plugin/SortSalesOrderAddressFields.php new file mode 100644 index 0000000..4e937fa --- /dev/null +++ b/Plugin/SortSalesOrderAddressFields.php @@ -0,0 +1,57 @@ +_request = $request; + $this->_storeConfigHelper = $storeConfigHelper; + } + + /** + * Reorder address fields. + * + * @param Form $subject + * @param array $result + * @return array + */ + public function afterGetAttributes(Form $subject, array $result) + { + if ( + $this->_storeConfigHelper->isSetFlag('change_fields_position') + && isset($result['country_id']) + && strpos($this->_request->getFullActionName(), 'sales_order_create') !== false + ) { + $result['country_id']->setSortOrder(70); + $result['street']->setSortOrder(80); + $result['postcode']->setSortOrder(90); + $result['city']->setSortOrder(100); + $result['region']->setSortOrder(110); + $result['region_id']->setSortOrder(110); + } + + return $result; + } +} diff --git a/etc/adminhtml/di.xml b/etc/adminhtml/di.xml index ce566c1..8921a90 100644 --- a/etc/adminhtml/di.xml +++ b/etc/adminhtml/di.xml @@ -7,4 +7,12 @@ + + + + + + + + diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index f9e622f..de518b9 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -67,12 +67,17 @@ Allow shipping to PO boxes. Magento\Config\Model\Config\Source\Yesno - + + + Control address autocomplete behavior for the admin order form. + Flekto\Postcode\Model\Config\Source\AdminAddressAutocompleteBehavior + + Distribute street address values to available street fields. Magento\Config\Model\Config\Source\Yesno - + Magento\Config\Model\Config\Source\Yesno diff --git a/etc/config.xml b/etc/config.xml index c44ce0c..acd406f 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -13,6 +13,7 @@ 1 0 + default diff --git a/etc/module.xml b/etc/module.xml index dea8afe..6450aef 100644 --- a/etc/module.xml +++ b/etc/module.xml @@ -5,6 +5,7 @@ + diff --git a/i18n/en_US.csv b/i18n/en_US.csv index b529886..fa192ec 100644 --- a/i18n/en_US.csv +++ b/i18n/en_US.csv @@ -51,3 +51,16 @@ "Sorry, we cannot ship to a PO Box address.","Sorry, we cannot ship to a PO Box address." "Split street address values","Split street address values" "Distribute street address values to available street fields.","Distribute street address values to available street fields." +"Address autocomplete","Address autocomplete" +"Find an address","Find an address" +"City, street or postcode","City, street or postcode" +"This address is a PO box","This address is a PO box" +"Countries to exclude","Countries to exclude" +"Selected countries will have Address API disabled.","Selected countries will have Address API disabled." +"Address autocomplete in admin","Address autocomplete in admin" +"Control address autocomplete behavior for the admin order form.","Control address autocomplete behavior for the admin order form." +"Equal to configuration for checkout","Equal to configuration for checkout" +"Use free address input for Dutch address","Use free address input for Dutch address" +"Use zip code and house number inputs for Dutch address","Use zip code and house number inputs for Dutch address" +"Disabled","Disabled" +"Loading…","Loading…" diff --git a/i18n/nl_BE.csv b/i18n/nl_BE.csv index 825e133..ce183bf 100644 --- a/i18n/nl_BE.csv +++ b/i18n/nl_BE.csv @@ -27,7 +27,7 @@ "Debug","Debuggen" "Change address fields position","Verander adres velden positie" "Only zip code and house number (default)","Alleen postcode en huisnummer (standaard)" -"Free address input","Adres handmatig invoeren" +"Free address input","Vrije adresinvoer" "Show fields","Velden weergeven" "Disable fields before autocomplete finished","Schakel velden uit voordat het automatisch aanvullen is voltooid" "Hide fields before autocomplete finished","Verberg velden voordat het automatisch aanvullen is voltooid" @@ -51,3 +51,16 @@ "Sorry, we cannot ship to a PO Box address.","Sorry, we versturen niet naar een postbus adres." "Split street address values","Straatadreswaarden splitsen" "Distribute street address values to available street fields.","Verdeel de straatadreswaarden over de beschikbare straatvelden." +"Address autocomplete","Adres autocomplete" +"Find an address","Zoek een adres" +"City, street or postcode","Plaats, straat of postcode" +"This address is a PO box","Dit adres is een postbus" +"Countries to exclude","Uitgeschakelde landen" +"Selected countries will have Address API disabled.","Voor de geselecteerde landen wordt de Adres API uitgeschakeld." +"Address autocomplete in admin","Adres autocomplete in admin" +"Control address autocomplete behavior for the admin order form.","Beheer het gedrag van adres-autocomplete voor het admin bestelformulier." +"Equal to configuration for checkout","Gelijk aan configuratie voor checkout" +"Use free address input for Dutch address","Gebruik vrije adresinvoer voor Nederlandse adressen" +"Use zip code and house number inputs for Dutch address","Gebruik postcode- en huisnummervelden voor Nederlandse adressen" +"Disabled","Uitgeschakeld" +"Loading…","Laden…" diff --git a/i18n/nl_NL.csv b/i18n/nl_NL.csv index 825e133..ce183bf 100644 --- a/i18n/nl_NL.csv +++ b/i18n/nl_NL.csv @@ -27,7 +27,7 @@ "Debug","Debuggen" "Change address fields position","Verander adres velden positie" "Only zip code and house number (default)","Alleen postcode en huisnummer (standaard)" -"Free address input","Adres handmatig invoeren" +"Free address input","Vrije adresinvoer" "Show fields","Velden weergeven" "Disable fields before autocomplete finished","Schakel velden uit voordat het automatisch aanvullen is voltooid" "Hide fields before autocomplete finished","Verberg velden voordat het automatisch aanvullen is voltooid" @@ -51,3 +51,16 @@ "Sorry, we cannot ship to a PO Box address.","Sorry, we versturen niet naar een postbus adres." "Split street address values","Straatadreswaarden splitsen" "Distribute street address values to available street fields.","Verdeel de straatadreswaarden over de beschikbare straatvelden." +"Address autocomplete","Adres autocomplete" +"Find an address","Zoek een adres" +"City, street or postcode","Plaats, straat of postcode" +"This address is a PO box","Dit adres is een postbus" +"Countries to exclude","Uitgeschakelde landen" +"Selected countries will have Address API disabled.","Voor de geselecteerde landen wordt de Adres API uitgeschakeld." +"Address autocomplete in admin","Adres autocomplete in admin" +"Control address autocomplete behavior for the admin order form.","Beheer het gedrag van adres-autocomplete voor het admin bestelformulier." +"Equal to configuration for checkout","Gelijk aan configuratie voor checkout" +"Use free address input for Dutch address","Gebruik vrije adresinvoer voor Nederlandse adressen" +"Use zip code and house number inputs for Dutch address","Gebruik postcode- en huisnummervelden voor Nederlandse adressen" +"Disabled","Uitgeschakeld" +"Loading…","Laden…" diff --git a/view/adminhtml/layout/sales_order_create_index.xml b/view/adminhtml/layout/sales_order_create_index.xml new file mode 100644 index 0000000..348438f --- /dev/null +++ b/view/adminhtml/layout/sales_order_create_index.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/view/adminhtml/templates/form/element/address-autofill.phtml b/view/adminhtml/templates/form/element/address-autofill.phtml new file mode 100644 index 0000000..269c9f6 --- /dev/null +++ b/view/adminhtml/templates/form/element/address-autofill.phtml @@ -0,0 +1,53 @@ + +
+ +
+ escapeHtml(__('Loading…')) ?> +
+ +
+escapeJs($block->getNameInLayout())}'; + + if (Registry.has(rootName)) { + // Remove each component from the JS layout hierarchy. + Registry.filter('ns = ' + rootName).forEach(component => { + if (typeof component.destroy === 'function') { + component.destroy(); // Avoids memory leaks. + } + + Registry.remove(component.name); + }); + + const container = document.getElementById('{$escaper->escapeJs($block->getJsId())}'); + + // Manually trigger component application. + mageApply.apply(container); + + // Clean node to allow fresh binding. + ko.cleanNode(container); + + // Force a re-evaluation of the scope. + Registry.get(rootName, (newComponent) => ko.applyBindings(newComponent, container)); + } +}); +js; + +echo $secureRenderer->renderTag('script', [], $script, false); +?> diff --git a/view/adminhtml/web/css/source/_module.less b/view/adminhtml/web/css/source/_module.less new file mode 100644 index 0000000..97bdea1 --- /dev/null +++ b/view/adminhtml/web/css/source/_module.less @@ -0,0 +1,15 @@ +@import '_shared.less'; + +.order-address-autofill { + min-height: 97px; + padding: 2rem; + background-color: @color-white-smoke; +} + +.address-autofill-warning { + color: @color-tomato-brick; +} + +.admin__field.address-autofill-intl-input { + margin-top: 0; +} diff --git a/view/adminhtml/web/js/view/address/autofill.js b/view/adminhtml/web/js/view/address/autofill.js new file mode 100644 index 0000000..67e29a7 --- /dev/null +++ b/view/adminhtml/web/js/view/address/autofill.js @@ -0,0 +1,65 @@ +define([ + 'uiCollection', + 'uiRegistry', +], function (Collection, Registry) { + 'use strict'; + + return Collection.extend({ + + defaults: { + container: null, + inputs: null, + error: '', + listens: { + visible: 'onVisible', + countryCode: 'onChangeCountry', + }, + }, + + initialize: function () { + this._super(); + + this.container = document.getElementById(`order-${this.addressType}_address_fields`); + this.inputs = { + country: document.getElementById(this.htmlIdPrefix + 'country_id'), + street: this.container.querySelectorAll('.field-street .input-text'), + postcode: document.getElementById(this.htmlIdPrefix + 'postcode'), + city: document.getElementById(this.htmlIdPrefix + 'city'), + region: document.getElementById(this.htmlIdPrefix + 'region'), + regionId: document.getElementById(this.htmlIdPrefix + 'region_id'), + }; + + this.inputs.country.addEventListener('change', (e) => { + this.countryCode(e.target.value); + }); + + return this; + }, + + initElement: function (elem) { + if (this.addressType === 'shipping') { + this.delegate('disabled', document.forms.edit_form.shipping_same_as_billing.checked); + } + }, + + initObservable: function () { + this._super(); + this.observe('countryCode error visible'); + return this; + }, + + onVisible: function (isVisible) { + this.container.querySelector('.field-' + this.name).classList.toggle('hidden', !isVisible); + }, + + onChangeCountry: function (countryCode) { + this.visible(this.settings.enabled_countries.includes(countryCode)); + + // Update shipping component if same as billing. + if (document.forms.edit_form.shipping_same_as_billing.checked && this.addressType === 'billing') { + Registry.get('shipping_address_autofill').set('countryCode', countryCode); + } + }, + + }); +}); diff --git a/view/adminhtml/web/js/view/form/sales/order_create/address-autofill-intl.js b/view/adminhtml/web/js/view/form/sales/order_create/address-autofill-intl.js new file mode 100644 index 0000000..46c68e2 --- /dev/null +++ b/view/adminhtml/web/js/view/form/sales/order_create/address-autofill-intl.js @@ -0,0 +1,47 @@ +define([ + 'Flekto_Postcode/js/form/element/address-autofill-intl', + 'mage/translate', +], function (AddressAutofillIntl, $t) { + 'use strict'; + + return AddressAutofillIntl.extend({ + defaults: { + showLogo: false, + imports: { + settings: '${$.parentName}:settings', + inputs: '${$.parentName}:inputs', + countryCode: '${$.parentName}:countryCode', + onChangeCountry: '${$.parentName}:countryCode', + }, + modules: { + parent: '${$.parentName}', + }, + }, + + initialize: function () { + this._super(); + this.required(false); + return this; + }, + + validateAddress: function (address) { + if ( + this.settings.allow_pobox_shipping === false + && address.isPoBox + && this.parent().addressType === 'shipping' + ) { + this.parent().error($t('This address is a PO box')); + return false; + } + + return this._super(address); + }, + + resetInputAddress: function () { + this.parent().error(false); + }, + + toggleFields: function () { /* Ignore */ }, + + }); +}); diff --git a/view/adminhtml/web/js/view/form/sales/order_create/address-autofill-nl.js b/view/adminhtml/web/js/view/form/sales/order_create/address-autofill-nl.js new file mode 100644 index 0000000..4259ea7 --- /dev/null +++ b/view/adminhtml/web/js/view/form/sales/order_create/address-autofill-nl.js @@ -0,0 +1,69 @@ +define([ + 'Flekto_Postcode/js/form/components/address-autofill-nl', + 'Flekto_Postcode/js/model/address-nl', + 'mage/translate', +], function (Collection, AddressNlModel, $t) { + 'use strict'; + + return Collection.extend({ + defaults: { + error: '', + listens: { + status: 'onStatus', + visible: 'toggleHouseNumberSelect', + '${$.name}.house_number_select:options': 'toggleHouseNumberSelect', + }, + imports: { + addressType: '${$.parentName}:addressType', + countryCode: '${$.parentName}:countryCode', + inputs: '${$.parentName}:inputs', + isCountryChanged: '${$.parentName}:isCountryChanged', + onChangeCountry: '${$.parentName}:countryCode', + settings: '${$.parentName}:settings', + }, + exports: { + error: '${$.parentName}:error', + }, + }, + + initObservable: function () { + this._super(); + this.observe('error'); + return this; + }, + + onStatus: function (status) { + if (status === AddressNlModel.status.NOT_FOUND) { + this.error($t('Address not found')); + } + }, + + validateAddress: function (address) { + if ( + this.settings.allow_pobox_shipping === false + && this.status() === AddressNlModel.status.VALID + && this.addressType === 'shipping' + && address.addressType === 'PO box' + ) { + this.error($t('This address is a PO box')); + return false; + } + + return this._super(address); + }, + + toggleHouseNumberSelect: function () { + this.childHouseNumberSelect((component) => { + component.visible(this.visible() && component.options().length > 0); + }); + }, + + resetInputAddress: function () { + this.error(false); + }, + + toggleFields: function () { /* Ignore */ }, + + }); + +}); diff --git a/view/adminhtml/web/template/form/element/address-autofill-field.html b/view/adminhtml/web/template/form/element/address-autofill-field.html new file mode 100644 index 0000000..44c020e --- /dev/null +++ b/view/adminhtml/web/template/form/element/address-autofill-field.html @@ -0,0 +1,13 @@ + diff --git a/view/frontend/web/css/lib/postcode-eu-autocomplete-address.css b/view/base/web/css/lib/postcode-eu-autocomplete-address.css similarity index 97% rename from view/frontend/web/css/lib/postcode-eu-autocomplete-address.css rename to view/base/web/css/lib/postcode-eu-autocomplete-address.css index 1cc40cc..01fbf52 100644 --- a/view/frontend/web/css/lib/postcode-eu-autocomplete-address.css +++ b/view/base/web/css/lib/postcode-eu-autocomplete-address.css @@ -1,10 +1,8 @@ .postcodenl-autocomplete-menu { display: none; position: absolute; - z-index: 9999; - padding-bottom: 1.75em; + z-index: 99; background-position: right .85em bottom .3em; - border-top: 1px solid #ddd; box-shadow: 0 .5em .75em rgba(0, 0, 0, .15); background-color: #fff; } @@ -16,6 +14,7 @@ .postcodenl-autocomplete-menu-items { margin: 0; padding: 0; + border-top: 1px solid #ddd; border-bottom: 1px solid #ddd; max-height: 19em; overflow-y: auto; @@ -30,17 +29,22 @@ input[class].postcodenl-autocomplete-address-input { transition: none; } -.postcodenl-autocomplete-menu, -input[class].postcodenl-autocomplete-address-input.postcodenl-autocomplete-address-input-blank { +.postcodenl-autocomplete-menu.postcodenl-autocomplete-logo { + padding-bottom: 1.75em; + border-top: 1px solid #ddd; +} + +.postcodenl-autocomplete-logo .postcodenl-autocomplete-menu-items { + border-top: none; +} + +.postcodenl-autocomplete-menu.postcodenl-autocomplete-logo, +input[class].postcodenl-autocomplete-address-input.postcodenl-autocomplete-address-input-blank.postcodenl-autocomplete-logo { background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAyMjYuMTcgNDAiIHdpZHRoPSIyMjYiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+Cgk8ZyBmaWxsPSIjOTk5Ij4KCQk8cGF0aCBkPSJtOC4zODMgMS43NjlhNy40NjYgNy40NjYgMCAwIDEgNy42ODYgNy42NDIgNy4zNDEgNy4zNDEgMCAwIDEtMi4yIDUuNDQ3IDcuNDczIDcuNDczIDAgMCAxLTUuNSAyLjJoLTYuNXY5LjM2N2gtMS44Njl2LTI0LjY1NnptMCAxMy40OTJhNS42MyA1LjYzIDAgMCAwIDQuMTc3LTEuNjUxIDUuNzE4IDUuNzE4IDAgMCAwIDEuNjM3LTQuMiA1Ljc3IDUuNzcgMCAwIDAtMS42MzctNC4yMjggNS42MyA1LjYzIDAgMCAwLTQuMTc3LTEuNjUxaC02LjUxOHYxMS43NDV6Ii8+CgkJPHBhdGggZD0ibTQwLjQ4NSAyMy4xMDFhMTIuMjMgMTIuMjMgMCAwIDEtOS4wMjIgMy43MjIgMTIuMjMgMTIuMjMgMCAwIDEtOS4wMTQtMy43MjIgMTIuMjgxIDEyLjI4MSAwIDAgMS0zLjY3LTkgMTIuMyAxMi4zIDAgMCAxIDMuNjctOS4wMDcgMTIuMjUyIDEyLjI1MiAwIDAgMSA5LjAxNC0zLjcxNCAxMi4yNTIgMTIuMjUyIDAgMCAxIDkuMDIyIDMuNzE0IDEyLjMgMTIuMyAwIDAgMSAzLjY3IDkuMDA3IDEyLjI4MSAxMi4yODEgMCAwIDEtMy42NyA5em0tMTYuNzE1LTEuMjdhMTAuNCAxMC40IDAgMCAwIDcuNjkzIDMuMTkzIDEwLjQgMTAuNCAwIDAgMCA3LjctMy4xOTMgMTAuNTU2IDEwLjU1NiAwIDAgMCAzLjE1Ny03LjczIDEwLjU4NSAxMC41ODUgMCAwIDAtMy4xNTctNy43MzcgMTAuNDI0IDEwLjQyNCAwIDAgMC03LjctMy4xODYgMTAuNDI0IDEwLjQyNCAwIDAgMC03LjY5MyAzLjE4NiAxMC41ODUgMTAuNTg1IDAgMCAwLTMuMTU3IDcuNzM3IDEwLjU1NiAxMC41NTYgMCAwIDAgMy4xNTcgNy43M3oiLz4KCQk8cGF0aCBkPSJtNTUuMjE4IDI2LjgyM2ExMC4zMjEgMTAuMzIxIDAgMCAxLTUuOC0xLjU3MSA3LjczNyA3LjczNyAwIDAgMS0zLjIyMy00LjI0M2wxLjYyMi0wLjk1NGE2LjUgNi41IDAgMCAwIDIuNTU1IDMuNjI2IDguMTQ4IDguMTQ4IDAgMCAwIDQuODQ1IDEuMzQzIDYuODU2IDYuODU2IDAgMCAwIDQuNi0xLjM4IDQuNDQxIDQuNDQxIDAgMCAwIDEuNi0zLjU1MyAzLjYxMiAzLjYxMiAwIDAgMC0xLjU1MS0zLjE0MiAxOC43ODUgMTguNzg1IDAgMCAwLTQuODEtMi4xNDhsLTEuNzQtMC42MzEtMS41NzEtMC41NzNhOC42MTEgOC42MTEgMCAwIDEtMS41Mi0wLjY5Yy0wLjMzLTAuMjEzLTAuNzM0LTAuNS0xLjE4Mi0wLjg1MmEzLjg2OCAzLjg2OCAwIDAgMS0xLjAyLTEuMDQyIDcuMDU1IDcuMDU1IDAgMCAxLTAuNTU3LTEuMzEyIDUuMiA1LjIgMCAwIDEtMC4yNS0xLjY3NCA2LjAxMiA2LjAxMiAwIDAgMSAyLjEyOS00LjgwOCA3Ljk1IDcuOTUgMCAwIDEgNS4zNDQtMS44NDMgOC4zNDYgOC4zNDYgMCAwIDEgNC45MzMgMS40NjggOC41MyA4LjUzIDAgMCAxIDMgMy43MTRsLTEuNTg2IDAuODgxYTYuMzUgNi4zNSAwIDAgMC02LjM0Mi00LjI2NSA1Ljg3MyA1Ljg3MyAwIDAgMC00LjAzNyAxLjMzNiA0LjM2IDQuMzYgMCAwIDAtMS41MiAzLjQyMSAzLjM5MSAzLjM5MSAwIDAgMCAxLjQwOSAyLjk4OCAxOC42ODIgMTguNjgyIDAgMCAwIDQuNjE3IDIuMDQ4bDMuMTc5IDEuMTgyYTE0LjQxOCAxNC40MTggMCAwIDEgMi40NjcgMS4yNjMgNS4yNzggNS4yNzggMCAwIDEgMS45MTYgMS45NzUgNS43IDUuNyAwIDAgMSAwLjYgMi42NjUgNi4wODYgNi4wODYgMCAwIDEtMi4yIDQuOTE4IDguODgyIDguODgyIDAgMCAxLTUuOTA3IDEuODUzeiIvPgoJCTxwYXRoIGQ9Im04MS44MiAxLjc2OXYxLjc2MmgtNy43NTJ2MjIuOWgtMS45MDh2LTIyLjloLTcuNzUydi0xLjc2MnoiLz4KCQk8cGF0aCBkPSJtOTUuMjM5IDI2LjkyNmExMi41MzEgMTIuNTMxIDAgMCAxLTEyLjgzMi0xMi44MjQgMTIuNTE2IDEyLjUxNiAwIDAgMSAxMi44MzItMTIuODI0IDEyLjY0OCAxMi42NDggMCAwIDEgNi4xMyAxLjUyNyAxMS42OTQgMTEuNjk0IDAgMCAxIDQuNCA0LjE3N2wtNC44NjcgMi44MTlhNS41MjggNS41MjggMCAwIDAtMi4zMjctMi4yIDcuMTU3IDcuMTU3IDAgMCAwLTMuMzc3LTAuNzkzIDYuOTc0IDYuOTc0IDAgMCAwLTUuMiAyIDcuMjg5IDcuMjg5IDAgMCAwLTEuOTYgNS4zMjIgNy4zIDcuMyAwIDAgMCAxLjk2IDUuMzIyIDYuOTY2IDYuOTY2IDAgMCAwIDUuMjM0IDIgNy4yMTYgNy4yMTYgMCAwIDAgMy40LTAuODA3IDUuNDg0IDUuNDg0IDAgMCAwIDIuMzA1LTIuMmw0Ljg2NyAyLjgxOWExMS4zMzQgMTEuMzM0IDAgMCAxLTQuNCA0LjE3NyAxMi43MTQgMTIuNzE0IDAgMCAxLTYuMTY1IDEuNDg1eiIvPgoJCTxwYXRoIGQ9Im0xMzEuODcgMTYuMjUzLTExLjI2OSAyMy43NDctMTEuMjY4LTIzLjc0N2E5Ljg4OCA5Ljg4OCAwIDAgMS0wLjc3MS00LjIxNCAxMi4wMzkgMTIuMDM5IDAgMSAxIDI0LjA3OCAwIDkuODg4IDkuODg4IDAgMCAxLTAuNzcxIDQuMjE0em0tMTEuMjY5LTEwLjIzNGE2LjAxOSA2LjAxOSAwIDEgMCA2LjAxOSA2LjAxOSA2LjAxOSA2LjAxOSAwIDAgMC02LjAxOS02LjAxOXoiLz4KCQk8cGF0aCBkPSJtMTM1Ljk2IDI2LjQzNXYtMjQuNjY2aDkuODU1OXEyLjUxMTMgMCA0LjczMDEgMC45NjMyMyAyLjIzNjEgMC45NDYwMyAzLjgxODUgMi41ODAxIDEuNTgyNCAxLjYzNCAyLjUxMTMgMy45MjE3IDAuOTI4ODMgMi4yODc3IDAuOTI4ODMgNC44Njc3dC0wLjkyODgzIDQuODY3Ny0yLjUxMTMgMy45MjE3cS0xLjU4MjQgMS42MzQtMy44MTg1IDIuNTk3My0yLjIxODkgMC45NDYwMy00LjczMDEgMC45NDYwM3ptNS42NDE4LTUuNDE4Mmg0LjIxNDFxMi45MjQxIDAgNC43MzAxLTEuODc0OSAxLjgyMzMtMS44OTIxIDEuODIzMy01LjAzOTggMC0zLjE0NzctMS44MjMzLTUuMDIyNi0xLjgwNi0xLjg3NDktNC43MzAxLTEuODc0OWgtNC4yMTQxeiIvPgoJCTxwYXRoIGQ9Im0xNjAuNjYgMjYuNDM1di0yNC42NjZoMTUuMzI2djUuNDM1NGgtOS42ODM5djQuMDc2NWg4LjgwNjd2NS4zNjY2aC04LjgwNjd2NC4zNjg5aDkuODU1OXY1LjQxODJ6Ii8+CgkJPHBhdGggZD0ibTE3OC40MiAyMy41OHEwLTEuMzU4OCAwLjk4MDQzLTIuMzM5MyAwLjk5NzYzLTAuOTk3NjMgMi4zNzM3LTAuOTk3NjMgMS4zNTg4IDAgMi4zMzkzIDAuOTgwNDMgMC45OTc2MyAwLjk4MDQzIDAuOTk3NjMgMi4zNTY1IDAgMS4zNzYtMC45OTc2MyAyLjM3MzctMC45ODA0MyAwLjk4MDQzLTIuMzM5MyAwLjk4MDQzdC0yLjM1NjUtMC45ODA0M3EtMC45OTc2My0wLjk5NzYzLTAuOTk3NjMtMi4zNzM3eiIvPgoJCTxwYXRoIGQ9Im0xODguNDQgMjYuNDM1di0yNC42NjZoMTUuMzI2djUuNDM1NGgtOS42ODM5djQuMDc2NWg4LjgwNjd2NS4zNjY2aC04LjgwNjd2NC4zNjg5aDkuODU1OXY1LjQxODJ6Ii8+CgkJPHBhdGggZD0ibTIwNi41NyAxOC40NzF2LTE2LjcwMmg1LjYyNDZ2MTYuMjAzcTAgMy40NTczIDMuODg3MyAzLjQ1NzMgMy44NzAxIDAgMy44NzAxLTMuNDU3M3YtMTYuMjAzaDUuNjQxOHYxNi43MDJxMCAyLjU4MDEtMS4yNTU2IDQuNTIzNy0xLjI1NTYgMS45NDM3LTMuNDA1NyAyLjk0MTMtMi4xMzI5IDAuOTk3NjMtNC44NTA1IDAuOTk3NjMtMi43MTc3IDAtNC44Njc4LTAuOTk3NjMtMi4xMzI5LTAuOTk3NjMtMy4zODg1LTIuOTQxMy0xLjI1NTYtMS45NDM3LTEuMjU1Ni00LjUyMzd6Ii8+Cgk8L2c+Cjwvc3ZnPgo=); background-repeat: no-repeat; background-size: 5.0em; } -input[class].input-autocomplete-no-logo.postcodenl-autocomplete-address-input-blank { - background-image: none; -} - input[class].postcodenl-autocomplete-address-input.postcodenl-autocomplete-address-input-blank { background-position: calc(100% - .85em) calc(50% + .1em); } diff --git a/view/base/web/css/source/_shared.less b/view/base/web/css/source/_shared.less new file mode 100644 index 0000000..3ec24c4 --- /dev/null +++ b/view/base/web/css/source/_shared.less @@ -0,0 +1,23 @@ +.postcodenl-autocomplete-menu { + z-index: 9999; +} + +.postcodenl-autocomplete-menu, +.address-autofill-intl-input input[class].input-text.postcodenl-autocomplete-address-input-blank { + background-size: 100px; +} + +.address-autofill-nl-house-number.loading input[type=text], +.address-autofill-intl-input.loading input[class].input-text, +.address-autofill-intl-input input[class].input-text.postcodenl-autocomplete-loading { + background-image: url(data:image/gif;base64,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); + background-position: center right .5em; + background-repeat: no-repeat; + background-size: auto; +} + +.address-autofill-formatted-output address { + padding: 1em; + font-size: 1.8rem; + background-color: @color-white-smoke; +} diff --git a/view/base/web/js/form/components/address-autofill-nl.js b/view/base/web/js/form/components/address-autofill-nl.js new file mode 100644 index 0000000..3790d41 --- /dev/null +++ b/view/base/web/js/form/components/address-autofill-nl.js @@ -0,0 +1,229 @@ +define([ + 'uiCollection', + 'jquery', + 'Flekto_Postcode/js/model/address-nl', +], function (Collection, $, AddressNlModel) { + 'use strict'; + + return Collection.extend({ + defaults: { + listens: { + '${$.name}.postcode:value': 'onInputPostcode', + '${$.name}.house_number:value': 'onInputHouseNumber', + '${$.name}.house_number_select:value': 'onChangeHouseNumberAddition', + visible: 'onVisible', + }, + modules: { + childPostcode: '${$.name}.postcode', + childHouseNumber: '${$.name}.house_number', + childHouseNumberSelect: '${$.name}.house_number_select', + }, + address: null, + lookupTimeout: null, + loading: false, + status: null, + settings: {}, + visible: false, + inputs: null, + }, + + initialize: function () { + this._super(); + + // The "loading" class will be added to the house number element based on loading's observable value. + // I.e. when looking up an address. + this.childHouseNumber((component) => { component.additionalClasses['loading'] = this.loading; }); + + this.address.subscribe((address) => { + if (address !== null) { + this.setInputAddress(address); + } + }); + + return this; + }, + + initObservable: function () { + this._super(); + this.observe('address loading status visible'); + return this; + }, + + onVisible: function (isVisible) { + this.toggleFields(isVisible && this.status() === AddressNlModel.status.VALID); + }, + + onChangeCountry: function (countryCode) { + if (countryCode !== 'NL') { + this.visible(false); + return; + } + + if (this.address() !== null) { + this.setInputAddress(this.address()); + } else { + this.resetInputAddress(); + } + + this.visible(true); + }, + + onInputPostcode: function () { + clearTimeout(this.lookupTimeout); + + if ( + !this.childPostcode().valueChangedByUser + || !this.childPostcode().visible() + || this.childPostcode().checkInvalid() !== null + ) { + return; + } + + this.resetHouseNumberSelect(); + + this.lookupTimeout = setTimeout(() => { + if (this.isPostcodeValid() && this.isHouseNumberValid()) { + this.getAddress(); + } + }, AddressNlModel.lookupDelay); + }, + + onInputHouseNumber: function (value) { + clearTimeout(this.lookupTimeout); + + if ( + !this.childHouseNumber().valueChangedByUser + || !this.childHouseNumber().visible() + || value === '' + ) { + return; + } + + this.resetHouseNumberSelect(); + + this.lookupTimeout = setTimeout(() => { + if (this.isHouseNumberValid() && this.isPostcodeValid()) { + this.getAddress(); + } + }, AddressNlModel.lookupDelay); + }, + + isPostcodeValid: function () { + return AddressNlModel.postcodeRegex.test(this.childPostcode().value()); + }, + + isHouseNumberValid: function () { + return AddressNlModel.houseNumberRegex.test(this.childHouseNumber().value()); + }, + + getAddress: function () { + const postcode = encodeURIComponent( + AddressNlModel.postcodeRegex.exec(this.childPostcode().value())[0].replace(/\s/g, '') + ), + houseNumber = encodeURIComponent( + AddressNlModel.houseNumberRegex.exec(this.childHouseNumber().value())[0].trim() + ), + url = `${this.settings.api_actions.dutchAddressLookup}/${postcode}/${houseNumber}`; + + this.resetInputAddress(); + this.address(null); + this.status(null); + this.loading(true); + this.childHouseNumber().error(false); + + $.get({ + url: url, + cache: true, + dataType: 'json', + success: ([response]) => { + if (response.error) { + return this.childHouseNumber().error(response.message); + } + + this.status(response.status); + + if ( + this.status() === AddressNlModel.status.NOT_FOUND + || !this.validateAddress(response.address) + ) { + return; + } + + this.address(response.address); + + if (this.status() === AddressNlModel.status.ADDITION_INCORRECT) { + this.childHouseNumberSelect().setOptions(response.address.houseNumberAdditions); + } else { + this.toggleFields(true); + } + } + }).always(this.loading.bind(this, false)); + }, + + validateAddress: function () { + return true; + }, + + onChangeHouseNumberAddition: function (value) { + if (!this.childHouseNumberSelect().visible()) { + return; + } + + const option = this.childHouseNumberSelect().getOption(value), + isValid = typeof option !== 'undefined' && typeof option.houseNumberAddition !== 'undefined'; + + this.address().houseNumberAddition = isValid ? option.houseNumberAddition : null; + this.status(isValid ? AddressNlModel.status.VALID : AddressNlModel.status.ADDITION_INCORRECT); + this.address.valueHasMutated(); + this.toggleFields(isValid); + }, + + resetHouseNumberSelect: function () { + this.childHouseNumberSelect(component => component.setOptions([])); + }, + + getAddressParts: function (address) { + return { + ...address, + houseNumberAddition: address.houseNumberAddition ?? '', + house: `${address.houseNumber} ${address.houseNumberAddition ?? ''}`.trim(), + streetParts: [address.street, address.houseNumber, address.houseNumberAddition ?? ''], + }; + }, + + setInputAddress: function (address) { + if (this.inputs === null) { + return; + } + + const addressParts = this.getAddressParts(address), + setValue = (input, value) => { + input.value = value; + input.dispatchEvent(new Event('change', {bubbles: true})); + }; + + let streetLines; + + if (this.settings.split_street_values) { + const lastChildIndex = this.inputs.street.length - 1; + + streetLines = addressParts.streetParts.slice(0, lastChildIndex); + streetLines.push(addressParts.streetParts.slice(lastChildIndex).join(' ').trim()); + } else { + streetLines = [addressParts.streetParts.join(' ').trim()]; + } + + for (let i = 0; i < streetLines.length; i++) { + setValue(this.inputs.street[i], streetLines[i]); + } + + setValue(this.inputs.city, addressParts.city); + setValue(this.inputs.postcode, addressParts.postcode); + + if (this.inputs.region) { + setValue(this.inputs.region, addressParts.province); + } + }, + + }); +}); diff --git a/view/frontend/web/js/form/element/address-autofill-intl.js b/view/base/web/js/form/element/address-autofill-intl.js similarity index 72% rename from view/frontend/web/js/form/element/address-autofill-intl.js rename to view/base/web/js/form/element/address-autofill-intl.js index 1da2974..d3b3421 100644 --- a/view/frontend/web/js/form/element/address-autofill-intl.js +++ b/view/base/web/js/form/element/address-autofill-intl.js @@ -1,8 +1,9 @@ define([ 'Magento_Ui/js/form/element/abstract', 'mage/translate', + 'uiRegistry', 'Flekto_Postcode/js/ko/bindings/init-intl-autocomplete', -], function (Abstract, $t) { +], function (Abstract, $t, Registry) { 'use strict'; return Abstract.extend({ @@ -17,6 +18,7 @@ define([ additionalClasses: { 'address-autofill-intl-input': true, }, + inputs: null, }, initialize: function () { @@ -81,7 +83,7 @@ define([ isEnabledCountry: function (countryCode) { return ( this.settings.enabled_countries.includes(countryCode) - && !(countryCode === 'NL' && this.settings.nl_input_behavior === 'zip_house') + && !(countryCode === 'NL' && Registry.has(this.parentName + '.address_autofill_nl')) ); }, @@ -98,5 +100,29 @@ define([ return true; }, + setInputAddress: function (result) { + if (this.inputs === null) { + return; + } + + const setValue = (input, value) => { + input.value = value; + input.dispatchEvent(new Event('change', {bubbles: true})); + }; + + for (let i = 0; i < result.streetLines.length; i++) { + setValue(this.inputs.street[i], result.streetLines[i]); + } + + setValue(this.inputs.city, result.address.locality); + setValue(this.inputs.postcode, result.address.postcode); + + if (this.inputs.regionId && this.inputs.regionId.style.display !== 'none') { + setValue(this.inputs.regionId, result.region.id ?? ''); + } else if (this.inputs.region && this.inputs.region.style.display !== 'none') { + setValue(this.inputs.region, result.region.name ?? ''); + } + }, + }); }); diff --git a/view/frontend/web/js/ko/bindings/init-intl-autocomplete.js b/view/base/web/js/ko/bindings/init-intl-autocomplete.js similarity index 98% rename from view/frontend/web/js/ko/bindings/init-intl-autocomplete.js rename to view/base/web/js/ko/bindings/init-intl-autocomplete.js index b1b1559..c4a5477 100644 --- a/view/frontend/web/js/ko/bindings/init-intl-autocomplete.js +++ b/view/base/web/js/ko/bindings/init-intl-autocomplete.js @@ -18,6 +18,7 @@ define([ autocompleteUrl: viewModel.settings.api_actions.autocomplete, addressDetailsUrl: viewModel.settings.api_actions.addressDetails, context: viewModel.countryCode || 'NL', + showLogo: viewModel.showLogo ?? true, }); viewModel.inputElement = element; @@ -71,3 +72,4 @@ define([ renderer.addAttribute('initIntlAutocomplete'); }); + diff --git a/view/frontend/web/js/lib/postcode-eu-autocomplete-address.js b/view/base/web/js/lib/postcode-eu-autocomplete-address.js similarity index 96% rename from view/frontend/web/js/lib/postcode-eu-autocomplete-address.js rename to view/base/web/js/lib/postcode-eu-autocomplete-address.js index 70eac97..f676dd2 100644 --- a/view/frontend/web/js/lib/postcode-eu-autocomplete-address.js +++ b/view/base/web/js/lib/postcode-eu-autocomplete-address.js @@ -8,10 +8,10 @@ * https://www.tldrlegal.com/license/apple-mit-license-aml * * @author Postcode.nl - * @version 1.4.1 + * @version 1.4.2 */ -(function (global, factory) { +(function (root, factory) { 'use strict'; // eslint-disable-next-line no-undef @@ -22,8 +22,8 @@ } else { - global.PostcodeNl = global.PostcodeNl || {}; - global.PostcodeNl.AutocompleteAddress = factory(global); + root.PostcodeNl = root.PostcodeNl || {}; + root.PostcodeNl.AutocompleteAddress = factory(); } }(typeof self !== 'undefined' ? self : this, function () { 'use strict'; @@ -31,7 +31,7 @@ const document = window.document, $ = function (selector) { return document.querySelectorAll(selector); }, elementData = new WeakMap(), - VERSION = '1.4.1', + VERSION = '1.4.2', EVENT_NAMESPACE = 'autocomplete-', PRECISION_ADDRESS = 'Address', KEY_ESC = 'Escape', @@ -149,6 +149,15 @@ writable: true, }, + /** + * Show the Postcode logo on the input + * @type {boolean} + */ + showLogo: { + value: true, + writable: true, + }, + /** * Get screen reader text for a successful response with at least one match. * Override this function to translate the message. @@ -223,6 +232,7 @@ classNames = { menuOpen: options.cssPrefix + 'menu-open', itemFocus: options.cssPrefix + 'item-focus', + logo: options.cssPrefix + 'logo', }, /** @@ -415,6 +425,8 @@ }); wrapper.classList.add(options.cssPrefix + 'menu'); + wrapper.classList.toggle(classNames.logo, options.showLogo); + ul.classList.add(options.cssPrefix + 'menu-items'); wrapper.appendChild(ul); @@ -452,7 +464,7 @@ }); // Add the menu to the page. - if (HTMLElement.prototype.isPrototypeOf(options.appendTo)) + if (Object.prototype.isPrototypeOf.call(HTMLElement.prototype, options.appendTo)) { options.appendTo.appendChild(wrapper); } @@ -728,10 +740,10 @@ { if (arguments.length > 1) { - return (this[arguments[1]? 'add' : 'remove'](value), !!arguments[1]); + return (this[arguments[1] ? 'add' : 'remove'](value), !!arguments[1]); } - return (this[this.contains(value)? 'remove' : 'add'](value), this.contains(value)); + return (this[this.contains(value) ? 'remove' : 'add'](value), this.contains(value)); } } @@ -748,11 +760,11 @@ { inputElements = $(elementsOrSelector); } - else if (HTMLElement.prototype.isPrototypeOf(elementsOrSelector)) + else if (Object.prototype.isPrototypeOf.call(HTMLElement.prototype, elementsOrSelector)) { inputElements = [elementsOrSelector]; } - else if (NodeList.prototype.isPrototypeOf(elementsOrSelector)) + else if (Object.prototype.isPrototypeOf.call(NodeList.prototype, elementsOrSelector)) { inputElements = elementsOrSelector; } @@ -796,7 +808,7 @@ const liveRegion = document.createElement('div'), liveRegionId = 'aria-live-region'; liveRegion.id = getUniqueId(liveRegionId); - liveRegion.setAttribute('aria-role', 'status'); + liveRegion.setAttribute('role', 'status'); liveRegion.setAttribute('aria-live', 'assertive'); liveRegion.classList.add(options.cssPrefix + liveRegionId); document.body.appendChild(liveRegion); @@ -1154,7 +1166,12 @@ } elementData.delete(element); - element.classList.remove(options.cssPrefix + 'address-input', options.cssPrefix + 'loading', inputBlankClassName); + element.classList.remove( + options.cssPrefix + 'address-input', + options.cssPrefix + 'loading', + inputBlankClassName, + options.cssPrefix + 'logo' + ); } } @@ -1184,6 +1201,7 @@ element.setAttribute('aria-controls', liveRegion.id); element.classList.add(options.cssPrefix + 'address-input'); element.classList.toggle(inputBlankClassName, element.value === ''); + element.classList.toggle(options.cssPrefix + 'logo', options.showLogo); element.addEventListener('keydown', eventHandlers.keydown = function (e) { isKeyEvent = true; diff --git a/view/frontend/web/js/model/address-nl.js b/view/base/web/js/model/address-nl.js similarity index 100% rename from view/frontend/web/js/model/address-nl.js rename to view/base/web/js/model/address-nl.js diff --git a/view/base/web/template/form/element/address-autofill-intl.html b/view/base/web/template/form/element/address-autofill-intl.html new file mode 100644 index 0000000..bc297c4 --- /dev/null +++ b/view/base/web/template/form/element/address-autofill-intl.html @@ -0,0 +1,13 @@ + diff --git a/view/frontend/layout/checkout_index_index.xml b/view/frontend/layout/checkout_index_index.xml index 41d81b9..8d58445 100644 --- a/view/frontend/layout/checkout_index_index.xml +++ b/view/frontend/layout/checkout_index_index.xml @@ -22,6 +22,7 @@ Flekto_Postcode/js/form/components/checkout/address-autofill-nl checkoutProvider + shippingAddress.address_autofill_nl checkoutProvider @@ -37,7 +38,7 @@ shippingAddress checkoutProvider - shippingAddress.address_autofill_nl.postcode + postcode Flekto_Postcode/js/form/element/house-number @@ -46,7 +47,7 @@ shippingAddress checkoutProvider - shippingAddress.address_autofill_nl.house_number + house_number Flekto_Postcode/js/form/element/house-number-select @@ -56,7 +57,7 @@ shippingAddress checkoutProvider - shippingAddress.address_autofill_nl.house_number_select + house_number_select diff --git a/view/frontend/web/css/source/_module.less b/view/frontend/web/css/source/_module.less index f076b26..ca7152e 100644 --- a/view/frontend/web/css/source/_module.less +++ b/view/frontend/web/css/source/_module.less @@ -1,26 +1,4 @@ -.postcodenl-autocomplete-menu { - z-index: 9999; -} - -.postcodenl-autocomplete-menu, -.address-autofill-intl-input input[class].input-text.postcodenl-autocomplete-address-input-blank { - background-size: 100px; -} - -.address-autofill-nl-house-number.loading .input-text, -.address-autofill-intl-input.loading input[class].input-text, -.address-autofill-intl-input input[class].input-text.postcodenl-autocomplete-loading { - background-image: url(data:image/gif;base64,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); - background-position: center right .5em; - background-repeat: no-repeat; - background-size: auto; -} - -.address-autofill-formatted-output address { - padding: 1em; - font-size: 1.8rem; - background-color: @color-white-smoke; -} +@import '_shared.less'; .form-address-edit, .form-address-edit > .fieldset { diff --git a/view/frontend/web/js/form/components/address-autofill-nl.js b/view/frontend/web/js/form/components/address-autofill-nl.js deleted file mode 100644 index 7b5c989..0000000 --- a/view/frontend/web/js/form/components/address-autofill-nl.js +++ /dev/null @@ -1,204 +0,0 @@ -define([ - 'uiCollection', - 'jquery', - 'Flekto_Postcode/js/model/address-nl', -], function (Collection, $, AddressNlModel) { - 'use strict'; - - return Collection.extend({ - defaults: { - listens: { - '${$.name}.postcode:value': 'onInputPostcode', - '${$.name}.house_number:value': 'onInputHouseNumber', - '${$.name}.house_number_select:value': 'onChangeHouseNumberAddition', - visible: 'onVisible', - }, - modules: { - childPostcode: '${$.name}.postcode', - childHouseNumber: '${$.name}.house_number', - childHouseNumberSelect: '${$.name}.house_number_select', - }, - address: null, - lookupTimeout: null, - loading: false, - status: null, - settings: {}, - visible: false, - }, - - initialize: function () { - this._super(); - - // The "loading" class will be added to the house number element based on loading's observable value. - // I.e. when looking up an address. - this.childHouseNumber((component) => { component.additionalClasses['loading'] = this.loading; }); - - this.address.subscribe((address) => { - if (address !== null) { - this.setInputAddress(address); - } - }); - - return this; - }, - - initObservable: function () { - this._super(); - this.observe('address loading status visible'); - return this; - }, - - onVisible: function (isVisible) { - this.toggleFields(isVisible && this.status() === AddressNlModel.status.VALID); - }, - - onChangeCountry: function (countryCode) { - if (countryCode !== 'NL') { - this.visible(false); - return; - } - - if (this.address() !== null) { - this.setInputAddress(this.address()); - } else { - this.resetInputAddress(); - } - - this.visible(true); - }, - - onInputPostcode: function () { - clearTimeout(this.lookupTimeout); - - if ( - !this.childPostcode().valueChangedByUser - || !this.childPostcode().visible() - || this.childPostcode().checkInvalid() !== null - ) { - return; - } - - this.resetHouseNumberSelect(); - - this.lookupTimeout = setTimeout(() => { - if (this.isPostcodeValid() && this.isHouseNumberValid()) { - this.getAddress(); - } - }, AddressNlModel.lookupDelay); - }, - - onInputHouseNumber: function (value) { - clearTimeout(this.lookupTimeout); - - if ( - !this.childHouseNumber().valueChangedByUser - || !this.childHouseNumber().visible() - || value === '' - ) { - return; - } - - this.resetHouseNumberSelect(); - - this.lookupTimeout = setTimeout(() => { - if (this.isHouseNumberValid() && this.isPostcodeValid()) { - this.getAddress(); - } - }, AddressNlModel.lookupDelay); - }, - - isPostcodeValid: function () { - return AddressNlModel.postcodeRegex.test(this.childPostcode().value()); - }, - - isHouseNumberValid: function () { - return AddressNlModel.houseNumberRegex.test(this.childHouseNumber().value()); - }, - - getAddress: function () { - const postcode = encodeURIComponent( - AddressNlModel.postcodeRegex.exec(this.childPostcode().value())[0].replace(/\s/g, '') - ), - houseNumber = encodeURIComponent( - AddressNlModel.houseNumberRegex.exec(this.childHouseNumber().value())[0].trim() - ), - url = `${this.settings.api_actions.dutchAddressLookup}/${postcode}/${houseNumber}`; - - this.resetInputAddress(); - this.address(null); - this.status(null); - this.loading(true); - this.childHouseNumber().error(false); - - $.get({ - url: url, - cache: true, - dataType: 'json', - success: ([response]) => { - if (response.error) { - return this.childHouseNumber().error(response.message); - } - - this.status(response.status); - - if ( - this.status() === AddressNlModel.status.NOT_FOUND - || !this.validateAddress(response.address) - ) { - return; - } - - this.address(response.address); - - if (this.status() === AddressNlModel.status.ADDITION_INCORRECT) { - this.childHouseNumberSelect().setOptions(response.address.houseNumberAdditions); - } else { - this.toggleFields(true); - } - } - }).always(this.loading.bind(this, false)); - }, - - validateAddress: function () { - return true; - }, - - onChangeHouseNumberAddition: function (value) { - if (!this.childHouseNumberSelect().visible()) { - return; - } - - const option = this.childHouseNumberSelect().getOption(value), - isValid = typeof option !== 'undefined' && typeof option.houseNumberAddition !== 'undefined'; - - this.address().houseNumberAddition = isValid ? option.houseNumberAddition : null; - this.status(isValid ? AddressNlModel.status.VALID : AddressNlModel.status.ADDITION_INCORRECT); - this.address.valueHasMutated(); - this.toggleFields(isValid); - }, - - resetHouseNumberSelect: function () { - this.childHouseNumberSelect(component => component.setOptions([])); - }, - - getAddressParts: function (address) { - const streetParts = [address.street, address.houseNumber, address.houseNumberAddition ?? '']; - - if (this.settings.split_street_values) { - const lastChildIndex = this.initChildCount - 1; - - address.streetLines = streetParts.slice(0, lastChildIndex); - address.streetLines.push(streetParts.slice(lastChildIndex).join(' ').trim()); - } else { - address.streetLines = [streetParts.join(' ').trim()]; - } - - return { - ...address, - houseNumberAddition: address.houseNumberAddition ?? '', - house: `${address.houseNumber} ${address.houseNumberAddition ?? ''}`.trim(), - }; - }, - - }); -}); diff --git a/view/frontend/web/js/form/components/address-autofill-nl.js b/view/frontend/web/js/form/components/address-autofill-nl.js new file mode 120000 index 0000000..976a1d1 --- /dev/null +++ b/view/frontend/web/js/form/components/address-autofill-nl.js @@ -0,0 +1 @@ +/var/www/html/app/code/Flekto/Postcode/view/base/web/js/form/components/address-autofill-nl.js \ No newline at end of file diff --git a/view/frontend/web/js/form/components/checkout/address-autofill-nl.js b/view/frontend/web/js/form/components/checkout/address-autofill-nl.js index 745a2ae..c36a595 100644 --- a/view/frontend/web/js/form/components/checkout/address-autofill-nl.js +++ b/view/frontend/web/js/form/components/checkout/address-autofill-nl.js @@ -63,22 +63,14 @@ define([ setInputAddress: function (address) { const addressParts = this.getAddressParts(address); + let streetLines = addressParts.streetParts; - // Result could be an old address from localStorage, without streetLines. - if (typeof addressParts.streetLines === 'undefined') { - addressParts.streetLines = [ - addressParts.street, - addressParts.houseNumber, - addressParts.houseNumberAddition, - ]; - - if (!this.settings.split_street_values) { - addressParts.streetLines = [addressParts.streetLines.join(' ')]; - } + if (!this.settings.split_street_values) { + streetLines = [streetLines.join(' ')]; } // Street children may not yet be available at this point, so value needs to be set asynchronously. - this.street().asyncSetValues(...addressParts.streetLines); + this.street().asyncSetValues(...streetLines); this.city().value(addressParts.city); this.postcode().value(addressParts.postcode); @@ -120,12 +112,10 @@ define([ }, validateAddress: function (address) { - const houseNumber = this.childHouseNumber(); - if ( this.settings.allow_pobox_shipping === false && address.addressType === 'PO box' - && houseNumber.parentScope.split('.')[0] === 'shippingAddress' + && this.parentScope === 'shippingAddress' ) { this.status(AddressNlModel.status.PO_BOX_SHIPPING_NOT_ALLOWED); return false; diff --git a/view/frontend/web/js/form/components/customer/address/address-autofill-nl.js b/view/frontend/web/js/form/components/customer/address/address-autofill-nl.js index d635f63..42aaf76 100644 --- a/view/frontend/web/js/form/components/customer/address/address-autofill-nl.js +++ b/view/frontend/web/js/form/components/customer/address/address-autofill-nl.js @@ -120,21 +120,6 @@ define([ } }, - setInputAddress: function (address) { - const addressParts = this.getAddressParts(address); - - for (let i = 0; i < address.streetLines.length; i++) { - this.inputs.street[i].value = address.streetLines[i]; - } - - this.inputs.city.value = addressParts.city; - this.inputs.postcode.value = addressParts.postcode; - - if (this.inputs.region) { - this.inputs.region.value = addressParts.province; - } - }, - resetInputAddress: function () { this.inputs.toArray().forEach(input => { input.value = ''; }); }, diff --git a/view/frontend/web/js/form/element/checkout/address-autofill-intl.js b/view/frontend/web/js/form/element/checkout/address-autofill-intl.js index 7582969..4a9c245 100644 --- a/view/frontend/web/js/form/element/checkout/address-autofill-intl.js +++ b/view/frontend/web/js/form/element/checkout/address-autofill-intl.js @@ -111,13 +111,13 @@ define([ if ( this.settings.allow_pobox_shipping === false && address.isPoBox - && this.parentScope.split('.')[0] === 'shippingAddress' + && this.parentScope === 'shippingAddress' ) { this.error($t('Sorry, we cannot ship to a PO Box address.')); return false; } - return this._super(); + return this._super(address); }, }); diff --git a/view/frontend/web/js/form/element/customer/address/address-autofill-intl.js b/view/frontend/web/js/form/element/customer/address/address-autofill-intl.js index 4544cbd..4e1540e 100644 --- a/view/frontend/web/js/form/element/customer/address/address-autofill-intl.js +++ b/view/frontend/web/js/form/element/customer/address/address-autofill-intl.js @@ -22,7 +22,7 @@ define([ this._super(); this.visible(this.isEnabledCountry(this.countryCode)); - this.toggleFields(!this.visible()); + this.toggleFields(!this.visible(), true); if (this.visible() && this.value() === '') { const postcode = this.inputs.postcode.value, @@ -65,21 +65,6 @@ define([ } }, - setInputAddress: function (result) { - for (let i = 0; i < result.streetLines.length; i++) { - this.inputs.street[i].value = result.streetLines[i]; - } - - this.inputs.city.value = result.address.locality; - this.inputs.postcode.value = result.address.postcode; - - if (this.inputs.regionId && this.inputs.regionId.style.display !== 'none') { - this.inputs.regionId.value = result.region.id ?? ''; - } else if (this.inputs.region && this.inputs.region.style.display !== 'none') { - this.inputs.region.value = result.region.name ?? ''; - } - }, - resetInputAddress: function () { this.inputs.toArray().forEach(input => { input.value = ''; }); }, From 03cada2a2087c3b259f0b3d937e327d254764679 Mon Sep 17 00:00:00 2001 From: Jerry Smidt Date: Wed, 14 Jan 2026 15:27:01 +0100 Subject: [PATCH 3/8] Improve error handling when retrieving package data - Set default timeout of 30 seconds in Curl class. - Only get cached data when the Status block is actually being rendered. --- Block/System/Config/Status.php | 7 +++---- HTTP/Client/Curl.php | 5 +++++ Helper/Data.php | 24 ++++++++++++++++++++---- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/Block/System/Config/Status.php b/Block/System/Config/Status.php index 832a09f..d819128 100644 --- a/Block/System/Config/Status.php +++ b/Block/System/Config/Status.php @@ -69,10 +69,6 @@ public function __construct( $this->_dataHelper = $dataHelper; $this->_updateNotifier = $updateNotifier; - $this->_cachedData = $this->_getCachedData(); - - $this->_notifyUpdate(); - parent::__construct($context, $data); } @@ -85,6 +81,9 @@ public function __construct( */ public function render(AbstractElement $element): string { + $this->_cachedData = $this->_getCachedData(); + $this->_notifyUpdate(); + /** @noinspection PhpUndefinedMethodInspection */ $this->setElement($element); diff --git a/HTTP/Client/Curl.php b/HTTP/Client/Curl.php index 791c5df..761d72e 100644 --- a/HTTP/Client/Curl.php +++ b/HTTP/Client/Curl.php @@ -7,4 +7,9 @@ */ class Curl extends \Magento\Framework\HTTP\Client\Curl { + /** + * Request timeout + * @var int type + */ + protected $_timeout = 30; } diff --git a/Helper/Data.php b/Helper/Data.php index 51d7693..3d68942 100644 --- a/Helper/Data.php +++ b/Helper/Data.php @@ -10,7 +10,8 @@ use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Filesystem\DirectoryList; use Magento\Framework\Filesystem\DriverInterface; -use Magento\Framework\HTTP\Client\Curl; +use Flekto\Postcode\HTTP\Client\Curl; +use Flekto\Postcode\Service\Exception\CurlException; class Data extends AbstractHelper { @@ -166,7 +167,12 @@ private function _getPackageData(): array } } - $this->_curl->get(self::PACKAGIST_URL); + try { + $this->_curl->get(self::PACKAGIST_URL); + } catch (CurlException $e) { + throw new LocalizedException(__('Failed to fetch package data: %1', $e->getMessage())); + } + $status = $this->_curl->getStatus(); if ($status == 200) { $response = $this->_curl->getBody(); @@ -175,7 +181,12 @@ private function _getPackageData(): array throw new LocalizedException(__('Failed to write package data to %1.', $filePath)); } - return json_decode($response, true); + $result = json_decode($response, true); + if (json_last_error() !== JSON_ERROR_NONE) { + throw new LocalizedException(__('Invalid JSON response from Packagist.')); + } + + return $result; } elseif ($status == 304) { // Not modified, use cached file. $data = $this->_fs->fileGetContents($filePath); @@ -184,7 +195,12 @@ private function _getPackageData(): array throw new LocalizedException(__('Failed to read package data from %1.', $filePath)); } - return json_decode($data, true); + $result = json_decode($data, true); + if (json_last_error() !== JSON_ERROR_NONE) { + throw new LocalizedException(__('Invalid cached JSON data.')); + } + + return $result; } throw new LocalizedException(__('Unexpected status code %1 while fetching package data.', $status)); From 7463e1a3c3a951deb3aa75a3fadb71d27770b81e Mon Sep 17 00:00:00 2001 From: Jerry Smidt Date: Thu, 15 Jan 2026 14:35:18 +0100 Subject: [PATCH 4/8] Fully translate admin interface --- Block/System/Config/Status.php | 37 +++++++++++--- Helper/StoreConfigHelper.php | 30 ++++++++++- Model/Config/Source/DisabledCountries.php | 7 +-- i18n/en_US.csv | 22 ++++++++ i18n/nl_BE.csv | 23 +++++++++ i18n/nl_NL.csv | 23 +++++++++ .../templates/system/config/status.phtml | 50 +++++++++++-------- 7 files changed, 158 insertions(+), 34 deletions(-) diff --git a/Block/System/Config/Status.php b/Block/System/Config/Status.php index d819128..e5d8ff3 100644 --- a/Block/System/Config/Status.php +++ b/Block/System/Config/Status.php @@ -100,7 +100,7 @@ public function getConfig(): array return [ 'enabled' => $this->_storeConfigHelper->isEnabled(), 'module_version' => $this->_storeConfigHelper->getModuleVersion(), - 'supported_countries' => $this->_storeConfigHelper->getSupportedCountries(), + 'supported_countries' => $this->_storeConfigHelper->getSupportedCountryNames(), 'account_name' => $this->_storeConfigHelper->getValue('account_name'), 'account_status' => $this->_storeConfigHelper->getValue('account_status'), // Defaults to "new", see etc/config.xml. 'has_credentials' => $this->_storeConfigHelper->hasCredentials(), @@ -137,19 +137,42 @@ public function getApiStatusDescription(): string $status = $this->_storeConfigHelper->getValue('account_status'); switch ($status) { - case \Flekto\Postcode\Helper\ApiClientHelper::API_ACCOUNT_STATUS_NEW: - return __('not connected'); - case \Flekto\Postcode\Helper\ApiClientHelper::API_ACCOUNT_STATUS_ACTIVE: + case ApiClientHelper::API_ACCOUNT_STATUS_NEW: + return __('new'); + case ApiClientHelper::API_ACCOUNT_STATUS_ACTIVE: return __('active'); - case \Flekto\Postcode\Helper\ApiClientHelper::API_ACCOUNT_STATUS_INVALID_CREDENTIALS: + case ApiClientHelper::API_ACCOUNT_STATUS_INVALID_CREDENTIALS: return __('invalid key and/or secret'); - case \Flekto\Postcode\Helper\ApiClientHelper::API_ACCOUNT_STATUS_INACTIVE: + case ApiClientHelper::API_ACCOUNT_STATUS_INACTIVE: return __('inactive'); default: throw new Status\Exception(__('Invalid account status value.')); } } + public function getApiStatusHint(): string + { + $status = $this->_storeConfigHelper->getValue('account_status'); + + switch ($status) { + case ApiClientHelper::API_ACCOUNT_STATUS_NEW: + return __('Enter your Postcode.eu API key and secret to connect.'); + case ApiClientHelper::API_ACCOUNT_STATUS_ACTIVE: + return __('The Postcode.eu API is successfully connected.'); + case ApiClientHelper::API_ACCOUNT_STATUS_INVALID_CREDENTIALS: + return __('The API key or secret is incorrect. Please check your credentials.'); + case ApiClientHelper::API_ACCOUNT_STATUS_INACTIVE: + return __('Your Postcode.eu subscription is inactive. Please log in to your account to resolve this.'); + default: + throw new Status\Exception(__('Invalid account status value.')); + } + } + + public function isStatusActive(): bool + { + return $this->_storeConfigHelper->getValue('account_status') === ApiClientHelper::API_ACCOUNT_STATUS_ACTIVE; + } + /** * Get cached data. * @@ -179,7 +202,7 @@ private function _getCachedData(): array private function _getAccountInfo(): array { $status = $this->_storeConfigHelper->getValue('account_status'); - if ($status === \Flekto\Postcode\Helper\ApiClientHelper::API_ACCOUNT_STATUS_ACTIVE) { + if ($status === ApiClientHelper::API_ACCOUNT_STATUS_ACTIVE) { return $this->_apiClientHelper->getAccountInfo(); } diff --git a/Helper/StoreConfigHelper.php b/Helper/StoreConfigHelper.php index d7ff7cf..1de1066 100644 --- a/Helper/StoreConfigHelper.php +++ b/Helper/StoreConfigHelper.php @@ -8,6 +8,8 @@ use Magento\Store\Model\StoreManagerInterface; use Magento\Developer\Helper\Data as DeveloperHelperData; use Magento\Framework\Encryption\EncryptorInterface; +use Magento\Directory\Model\ResourceModel\Country\CollectionFactory as CountryCollectionFactory; +use Magento\Framework\Locale\ResolverInterface; use Flekto\Postcode\Model\Config\Source\NlInputBehavior; use Flekto\Postcode\Model\Config\Source\ShowHideAddressFields; @@ -16,6 +18,8 @@ class StoreConfigHelper extends AbstractHelper protected $_storeManager; protected $_developerHelper; protected $_encryptor; + protected $_countryCollectionFactory; + protected $_localeResolver; public const PATH = [ // Status @@ -44,18 +48,24 @@ class StoreConfigHelper extends AbstractHelper /** * @param Context $context * @param StoreManagerInterface $storeManager - * @param Data $developerHelper + * @param DeveloperHelperData $developerHelper * @param EncryptorInterface $encryptor + * @param CountryCollectionFactory $countryCollectionFactory + * @param ResolverInterface $localeResolver */ public function __construct( Context $context, StoreManagerInterface $storeManager, DeveloperHelperData $developerHelper, - EncryptorInterface $encryptor + EncryptorInterface $encryptor, + CountryCollectionFactory $countryCollectionFactory, + ResolverInterface $localeResolver ) { $this->_storeManager = $storeManager; $this->_developerHelper = $developerHelper; $this->_encryptor = $encryptor; + $this->_countryCollectionFactory = $countryCollectionFactory; + $this->_localeResolver = $localeResolver; parent::__construct($context); } @@ -123,6 +133,22 @@ public function getEnabledCountries(): array return array_values(array_diff($supported, explode(',', $disabled))); } + /** + * Get supported country names. + * + * @return array + */ + public function getSupportedCountryNames(): array + { + $isoCodes = array_map(fn($country) => $country->iso2, $this->getSupportedCountries()); + $collection = $this->_countryCollectionFactory->create()->addFieldToFilter('country_id', ['in' => $isoCodes]); + $locale = $this->_localeResolver->getLocale(); + $names = array_map(fn($country) => $country->getName(), $collection->getItems()); + \Collator::create($locale)->asort($names); + + return $names; + } + /** * Check if API credentials are set. * diff --git a/Model/Config/Source/DisabledCountries.php b/Model/Config/Source/DisabledCountries.php index 5aa855e..811eddc 100644 --- a/Model/Config/Source/DisabledCountries.php +++ b/Model/Config/Source/DisabledCountries.php @@ -28,11 +28,8 @@ public function toOptionArray(): array { $options = []; - foreach ($this->_storeConfigHelper->getSupportedCountries() as $country) { - $options[] = [ - 'value' => $country->iso2, - 'label' => $country->name, - ]; + foreach ($this->_storeConfigHelper->getSupportedCountryNames() as $iso2 => $name) { + $options[] = ['value' => $iso2, 'label' => $name]; } return $options; diff --git a/i18n/en_US.csv b/i18n/en_US.csv index fa192ec..5325443 100644 --- a/i18n/en_US.csv +++ b/i18n/en_US.csv @@ -64,3 +64,25 @@ "Use zip code and house number inputs for Dutch address","Use zip code and house number inputs for Dutch address" "Disabled","Disabled" "Loading…","Loading…" +"Module version","Module version" +"Please update to version %1","Please update to version %1" +"update available","update available" +"↻ Refresh API data","↻ Refresh API data" +"Module is disabled via configuration.","Module is disabled via configuration." +"API connection","API connection" +"Account name","Account name" +"Subscription status","Subscription status" +"Supported countries","Supported countries" +"euro","euro" +"Address API","Address API" +"Usage at %1%","Usage at %1%" +"new","new" +"active","active" +"invalid key and/or secret","invalid key and/or secret" +"inactive","inactive" +"Enter your Postcode.eu API key and secret to connect.","Enter your Postcode.eu API key and secret to connect." +"The Postcode.eu API is successfully connected.","The Postcode.eu API is successfully connected." +"The API key or secret is incorrect. Please check your credentials.","The API key or secret is incorrect. Please check your credentials." +"Your Postcode.eu subscription is inactive. Please log in to your account to resolve this.","Your Postcode.eu subscription is inactive. Please log in to your account to resolve this." +"Add manual entry link","Add manual entry link" +"Allows users to skip the autocomplete field and manually enter an address. Enabling this option may lead to invalid addresses. Applicable to free address input only.","Allows users to skip the autocomplete field and manually enter an address. Enabling this option may lead to invalid addresses. Applicable to free address input only." diff --git a/i18n/nl_BE.csv b/i18n/nl_BE.csv index ce183bf..04577cd 100644 --- a/i18n/nl_BE.csv +++ b/i18n/nl_BE.csv @@ -64,3 +64,26 @@ "Use zip code and house number inputs for Dutch address","Gebruik postcode- en huisnummervelden voor Nederlandse adressen" "Disabled","Uitgeschakeld" "Loading…","Laden…" +"Module version","Moduleversie" +"Please update to version %1","Update aub naar versie %1" +"update available","update beschikbaar" +"↻ Refresh API data","↻ API gegevens vernieuwen" +"Module is disabled via configuration.","Module is uitgeschakeld via configuratie." +"API connection","API verbinding" +"Account name","Accountnaam" +"Subscription status","Abonnementsstatus" +"Supported countries","Ondersteunde landen" +"euro","euro" +"Address API","Adres API" +"Usage at %1%","Verbruik op %1%" +"new","nieuw" +"active","actief" +"invalid key and/or secret","ongeldige key en/of secret" +"inactive","inactief" +"Enter your Postcode.eu API key and secret to connect.","Voer uw Postcode.eu API key en secret in om verbinding te maken." +"The Postcode.eu API is successfully connected.","De Postcode.eu API is succesvol verbonden." +"The API key or secret is incorrect. Please check your credentials.","De API key of secret is onjuist. Controleer uw inloggegevens." +"Your Postcode.eu subscription is inactive. Please log in to your account to resolve this.","Uw Postcode.eu abonnement is inactief. Log in op uw account om dit op te lossen." +"Add manual entry link","Link voor handmatige invoer toevoegen" +"Allows users to skip the autocomplete field and manually enter an address. Enabling this option may lead to invalid addresses. Applicable to free address input only.","Hiermee kunnen gebruikers het veld voor automatisch aanvullen overslaan en handmatig een adres invoeren. Het inschakelen van deze optie kan leiden tot ongeldige adressen. Alleen van toepassing op vrije adresinvoer." + diff --git a/i18n/nl_NL.csv b/i18n/nl_NL.csv index ce183bf..04577cd 100644 --- a/i18n/nl_NL.csv +++ b/i18n/nl_NL.csv @@ -64,3 +64,26 @@ "Use zip code and house number inputs for Dutch address","Gebruik postcode- en huisnummervelden voor Nederlandse adressen" "Disabled","Uitgeschakeld" "Loading…","Laden…" +"Module version","Moduleversie" +"Please update to version %1","Update aub naar versie %1" +"update available","update beschikbaar" +"↻ Refresh API data","↻ API gegevens vernieuwen" +"Module is disabled via configuration.","Module is uitgeschakeld via configuratie." +"API connection","API verbinding" +"Account name","Accountnaam" +"Subscription status","Abonnementsstatus" +"Supported countries","Ondersteunde landen" +"euro","euro" +"Address API","Adres API" +"Usage at %1%","Verbruik op %1%" +"new","nieuw" +"active","actief" +"invalid key and/or secret","ongeldige key en/of secret" +"inactive","inactief" +"Enter your Postcode.eu API key and secret to connect.","Voer uw Postcode.eu API key en secret in om verbinding te maken." +"The Postcode.eu API is successfully connected.","De Postcode.eu API is succesvol verbonden." +"The API key or secret is incorrect. Please check your credentials.","De API key of secret is onjuist. Controleer uw inloggegevens." +"Your Postcode.eu subscription is inactive. Please log in to your account to resolve this.","Uw Postcode.eu abonnement is inactief. Log in op uw account om dit op te lossen." +"Add manual entry link","Link voor handmatige invoer toevoegen" +"Allows users to skip the autocomplete field and manually enter an address. Enabling this option may lead to invalid addresses. Applicable to free address input only.","Hiermee kunnen gebruikers het veld voor automatisch aanvullen overslaan en handmatig een adres invoeren. Het inschakelen van deze optie kan leiden tot ongeldige adressen. Alleen van toepassing op vrije adresinvoer." + diff --git a/view/adminhtml/templates/system/config/status.phtml b/view/adminhtml/templates/system/config/status.phtml index 8ef1d1f..12889da 100644 --- a/view/adminhtml/templates/system/config/status.phtml +++ b/view/adminhtml/templates/system/config/status.phtml @@ -10,17 +10,18 @@ $moduleInfo = $block->getModuleInfo(); ?>
-

Address API

+

escapeHtml(__('Address API')) ?>

- Module version escapeHtml($moduleInfo['version']) ?> + escapeHtml(__('Module version')) ?> + escapeHtml($moduleInfo['version']) ?> - ↻ + ↻ escapeHtml(__('update available')) ?> @@ -38,18 +39,20 @@ $moduleInfo = $block->getModuleInfo();

-
Module is disabled via configuration.
+
+ escapeHtml(__('Module is disabled via configuration.')) ?> +
-

API connection

+

escapeHtml(__('API connection')) ?>

-
Account name
+
escapeHtml(__('Account name')) ?>
escapeHtml($accountInfo['name']) ?>
-
Subscription status
+
escapeHtml(__('Subscription status')) ?>
-

Supported countries

+ isStatusActive()): ?> +
+ escapeHtml($block->getApiStatusHint()) ?> +
+ + +

escapeHtml(__('Supported countries')) ?>

    - -
  • name ?>
  • + +
  • escapeHtml($name) ?>
From 5ab5d5500b48e1da408a1e99b84a892264eb2af8 Mon Sep 17 00:00:00 2001 From: Jerry Smidt Date: Thu, 15 Jan 2026 15:04:44 +0100 Subject: [PATCH 5/8] Fix potential shared reference in customer address autofill JS --- .../templates/customer/address/autofill.phtml | 6 +-- .../web/js/view/customer/address/autofill.js | 54 ++++++++++--------- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/view/frontend/templates/customer/address/autofill.phtml b/view/frontend/templates/customer/address/autofill.phtml index aeeb3cb..68b0412 100644 --- a/view/frontend/templates/customer/address/autofill.phtml +++ b/view/frontend/templates/customer/address/autofill.phtml @@ -13,9 +13,9 @@