Addresses is the Profile-tab reusable that manages customer addresses, including create/edit/delete flows, primary-address selection, and delivery-zone validation.
It centralizes address and invoice state synchronization with shared Alpine stores used by the Profile account area.
In Components/Profile/Default.liquid:
{% render 'Reusables\\Addresses\\Default', addresses: addresses, countriesList: countriesList %}
This reusable is rendered with the named parameters addresses and countriesList from the Profile context.
The template renders the address management UI and mounts Alpine state through addressesreusabledefault.initComponent(...).
The root element binds:
x-data='addressesreusabledefault.initComponent(addresses, countriesList, deliveryZonesEnabled, ...translations)'>
Factory signature (see `Reusables/Addresses/Default.js`):
- `addresses`
- `deliveryZonesEnabled`
- `inValidAddressMessageButton`
- `removeAddressErrorMessage`
- `postalCodesErrorMessage`
### Normalization
- `startAddresses`: original input array
- `addresses`: `normalizedAddresses.map((a) => a.address)`
- `invoices`: `normalizedAddresses.map((a) => a.invoice || {})`
- if delivery zones enabled → `normalizedAddresses.map((a) => a.isValid)`
It also ensures there is an **empty address draft at the end** (used as the “new address” form row).
## JavaScript
### Global object
const addressesreusabledefault = {
}
It’s instantiated from Liquid via:
Factory that normalizes the input addresses and returns the Alpine component state + methods.
[addresses][]this.addresses = normalizedAddresses.map(a => a.address)invoiceOption[i] = strings/non-empty check on invoice.companyNameInitializes validity:
storedAddressesValidity = normalizedAddresses.map(a => a.isValid)trueRuns when Alpine mounts.
Ensures a draft address exists and syncs state to shared stores.
Key behaviors
Publishes to Alpine.store('sharedAddresses'):
sharedAddresses.addresses
sharedAddresses.validAddresses
sharedAddresses.invoices
Calls resetToasts()
If true, calls controlToastVisibility()
Watches Alpine.store('profile').tabChanged:
cancelEditing(selectedAddressIndex, true) and exits edit mode.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)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().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 = trueAlpine.store('modal') and delegates deletion to removeAddress(index).servicesreusabledefault.deleteUserAddress(id) and removes it from all parallel arrays.Key behaviors
isRemoving[index], isAddressStateChanging) and disables interactions.The JS includes small utilities to keep the “draft” address last and to reorder parallel arrays consistently:
moveItem(array, fromIndex, toIndex) moves an entry within an array.moveToFront(array, index) moves an entry to the start of an array.Other helper/method names:
action(...) routes address-row button interactions (edit/save/cancel flows) through a shared Alpine handler.removeAddressErrorMessage toast and restores loading flags.removeAddressData(index).Key behaviors
servicesreusabledefault.setUserAddressAsPrimary(id).addresses, invoices, storedAddressesValidity, invoiceOption, etc.errors[field.name].Key behaviors
invalidEmailinvalid / valid on inputs and .select-wrap.icon-state classes (ic-warning / ic-check-circle-fill)addressValidity[index] or invoiceValidity[index].Key behaviors
addressData, invoiceData) with persisted arrays (addresses[index], invoices[index]).Builds a normalized address payload for list/state synchronization after create/update/edit operations.
Builds the invoice payload aligned with the same index used by setDataForList, so address/invoice arrays remain synchronized.
invoiceOption[index] vs originalInvoiceOption[index]).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 availableKey behaviors
invalidToastId[index] to avoid duplicates..address-wrap-box at the same index.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
}
]
}