The Addresses reusable renders the address book UI inside the Profile component.
It supports:
It’s rendered from Components/Profile/Default.liquid on the addresses tab:
{% render 'Reusables\\Addresses\\Default',
addresses: addresses,
countriesList: countriesList
%}
addressesExpected shape: array of items where each item is an object with:
address: an address objectinvoice: (optional) invoice objectisValid: boolean (only used when delivery zones are enabled)The JS normalizes input:
addresses is an array → used as-isaddresses is a single object → wrapped in a single-item array[]countriesListExpected shape: array of countries such as:
codenameUsed only when GlobalData.Settings.deliveryZonesEnabled is true.
selectedAddressIndex == null.selectedAddressIndex != null.In list view it renders:
startEditing() (note: the button is currently rendered as disabled in Liquid)addresses.slice(0, -1) (the last item is expected to be an empty draft)The reusable reads GlobalData.Settings.deliveryZonesEnabled.
When enabled:
<select> bound to addresses[selectedAddressIndex].countryCode<select> with options loaded for the selected countryisValid/profile/addresses route)When disabled:
true for all stored itemsThe root element binds:
<div
x-data='addressesreusabledefault.initComponent(addresses, countriesList, deliveryZonesEnabled, ...translations)'>
Factory signature (see Reusables/Addresses/Default.js):
addressescountriesListdeliveryZonesEnabledinValidAddressTitleinValidAddressMessageinValidAddressMessageButtonremoveAddressErrorMessagepostalCodesErrorMessageInternally the reusable keeps:
startAddresses: original input arrayaddresses: normalizedAddresses.map((a) => a.address)invoices: normalizedAddresses.map((a) => a.invoice || {})storedAddressesValidity:
normalizedAddresses.map((a) => a.isValid)true for allIt also ensures there is an empty address draft at the end (used as the “new address” form row).
The reusable exposes a global object:
const addressesreusabledefault = {
initComponent(addresses, countriesList, deliveryZonesEnabled, inValidAddressTitle, inValidAddressMessage, inValidAddressMessageButton, removeAddressErrorMessage, postalCodesErrorMessage) { ... }
}
It’s instantiated from Liquid via:
x-data='addressesreusabledefault.initComponent(...)'
initComponent(addresses, countriesList, deliveryZonesEnabled, inValidAddressTitle, inValidAddressMessage, inValidAddressMessageButton, removeAddressErrorMessage, postalCodesErrorMessage)What it does
Key behaviors
addresses into normalizedAddresses:
[addresses][]this.addresses = normalizedAddresses.map(a => a.address)this.invoices = normalizedAddresses.map(a => a.invoice || {})invoiceOption[i] = strings/non-empty check on invoice.companyNamestoredAddressesValidity = normalizedAddresses.map(a => a.isValid)trueinit() (async)What it does
Key behaviors
startNewAddress() so the last item is a blank “draft” address.clearInvalidAddressFields() to wipe postalCode/country/countryCode on invalid addresses (delivery zones mode).Alpine.store('sharedAddresses'):
sharedAddresses.addressessharedAddresses.validAddressessharedAddresses.invoices/profile/addresses, it calls controlToastVisibility().Watchers
Alpine.store('sharedAddresses').runToast:
resetToasts()true, calls controlToastVisibility()Alpine.store('profile').tabChanged:
cancelEditing(selectedAddressIndex, true) and exits edit mode.startEditing(index)What it does
Key behaviors
index == null, targets the last draft row; if it’s no longer empty, it calls startNewAddress() then uses the new last index.originalData[index] = { address: {...}, invoice: {...} }originalInvoiceOption[index] = invoiceOption[index]touched, openPopup, validity booleans).onCountryChange(index) (fetches postal codes + validates selects)cancelEditing(index, skipToastReset = false)What it does
Key behaviors
addresses[index], invoices[index], and invoiceOption[index] from originalData/originalInvoiceOption.skipToastReset is false, sets Alpine.store('sharedAddresses').runToast = true (so delivery-zones warnings can be recalculated).scrollToTop().saveAddress(index, errorMessage) (async)What it does
Key behaviors
[id^="profile-address-edit-"][id^="company-profile-address-edit-"]validateField(...) and updates errors.address: this.addressDatainvoice: invoiceOption[index] ? this.invoiceData : nullisValid: trueaddresses[index].id -> createUserAddress(...) (and removes address.id from payload)updateUserAddress(payload, id)success === false) shows a toast and triggers sharedAddresses.runToast = true.addresses[index] and invoices[index]truemoveEmptyAddressToEnd() to keep the blank draft lastsharedAddresses.runToast = trueconfirmAddressDeletion(index, modalTranslations)What it does
Alpine.store('modal') and delegates deletion to removeAddress(index).removeAddress(index) (async)What it does
servicesreusabledefault.deleteUserAddress(id) and removes it from all parallel arrays.Key behaviors
isRemoving[index], isAddressStateChanging) and disables interactions.removeAddressErrorMessage toast and restores loading flags.removeAddressData(index).setAddressAsPrimary(index, setAddressAsPrimaryErrorMessage) (async)What it does
Key behaviors
servicesreusabledefault.setUserAddressAsPrimary(id).addresses, invoices, storedAddressesValidity, invoiceOption, etc.validateField(field)What it does
errors[field.name].Key behaviors
invalidEmailinvalid / valid on inputs and .select-wrap.icon-state classes (ic-warning / ic-check-circle-fill)handleInput(event, index) / handleInvoiceInput(event, index)What it does
addressValidity[index] or invoiceValidity[index].isAddressModified(index)What it does
Key behaviors
addressData, invoiceData) with persisted arrays (addresses[index], invoices[index]).invoiceOption[index] vs originalInvoiceOption[index]).onCountryChange(index) (async)What it does
countryCode <-> country and fetches postal codes.Key behaviors
errors[postalCode] = 'wait' while fetching.servicesreusabledefault.fetchPostalCodes(countryCode).errorFetchingPostalCodes (unexpected response shape)notFound (failed request / no data)this.postalCodesoriginalData[index] if still availablecontrolToastVisibility() / resetToasts()What it does
Key behaviors
invalidToastId[index] to avoid duplicates..address-wrap-box at the same index.moveEmptyAddressToEnd()What it does
Key behaviors
"" and moves it to the end across all parallel arrays.The reusable syncs shared state to Alpine.store('sharedAddresses'):
.addresses.validAddresses.invoicesIt also uses:
.runToast flag to (re)trigger delivery-zones invalid-address toast logic.It uses Alpine.store('toast') for errors and delivery-zone warnings.
This reusable calls SDK wrappers from servicesreusabledefault:
createUserAddress(data) (when address.id is missing/empty)updateUserAddress(data, id)On non-2xx or success === false it shows an error toast.
Alpine.store('toast')Alpine.store('sharedAddresses')servicesreusabledefault.createUserAddress(...)servicesreusabledefault.updateUserAddress(...)disabled attribute in Liquid.addresses.slice(0, -1); the last address is expected to be the “draft” row.countryCode + dynamically loaded postal codes; if input data has countryCode empty, the selects may be disabled until the user chooses a country.This reusable doesn’t receive the full Profile model — only addresses and countriesList.
Sanitized example based on your Profile model:
{
"countriesList": [
{ "code": "COUNTRY_CODE", "name": "Country name" },
{ "code": "...", "name": "..." }
],
"addresses": [
{
"address": {
"id": "ADDRESS_ID",
"firstName": "FIRST_NAME",
"lastName": "LAST_NAME",
"phoneNumber": "PHONE",
"email": "EMAIL",
"address1": "ADDRESS_LINE_1",
"additionalInfo": "",
"label": "",
"city": "CITY",
"postalCode": "POSTAL_CODE",
"state": "STATE",
"country": "COUNTRY_NAME",
"countryCode": "COUNTRY_CODE"
},
"invoice": {
"companyName": "COMPANY_NAME",
"profession": "PROFESSION",
"companyPhoneNumber": "COMPANY_PHONE",
"taxOffice": "TAX_OFFICE",
"tin": "TIN"
},
"isValid": true
},
{
"address": {
"id": "...",
"firstName": "...",
"lastName": "..."
},
"invoice": null,
"isValid": true
}
]
}