ECOMMERCE/resources/views/account/addresses.blade.php

949 lines
44 KiB
PHP

@extends('layouts.account', ['title' => __('addresses.page_title')])
@section('content')
<!-- Addresses content -->
<div class="col-lg-9">
<div class="ps-lg-3 ps-xl-0">
<!-- Page title -->
<h1 class="h2 mb-1 mb-sm-2">{{ __('addresses.page_title') }}</h1>
@foreach ($addresses as $address)
<div class="border-bottom py-4">
<div class="nav flex-nowrap align-items-center justify-content-between pb-1 mb-3">
<div class="d-flex align-items-center gap-3 me-4">
<h2 class="h6 mb-0">{{ $address->label }}</h2>
@if ($address->is_primary)
<span class="badge text-bg-info rounded-pill">{{ __('addresses.primary') }}</span>
@endif
</div>
<a class="nav-link hiding-collapse-toggle text-decoration-underline p-0 collapsed primaryAddressEditButton"
data-address-id="{{ $address->id }}" href=".primary-address-{{ $address->id }}"
data-bs-toggle="collapse" aria-expanded="false"
aria-controls="primaryAddressPreview{{ $address->id }} primaryAddressEdit{{ $address->id }}">{{ __('addresses.edit') }}</a>
</div>
<div class="collapse primary-address-{{ $address->id }} show"
id="primaryAddressPreview{{ $address->id }}">
<ul class="list-unstyled fs-sm m-0">
<li>{{ $address->location }}</li>
<li>{{ $address->address }}</li>
</ul>
</div>
<div class="collapse primary-address-{{ $address->id }}" id="primaryAddressEdit{{ $address->id }}">
{{-- add message on success or error --}}
<div class="alert alert-danger d-none address-form-message" role="alert"></div>
<div class="alert alert-success d-none address-form-success" role="alert"></div>
<form class="row g-3 g-sm-4 needs-validation address-form" novalidate
action="{{ route('addresses.update', $address->id) }}" method="POST">
@csrf
@method('PUT')
<div class="col-sm-6">
<div class="position-relative">
<label class="form-label">{{ __('addresses.province') }}</label>
<select class="form-select province-select" required
data-address-id="{{ $address->id }}"
data-province-id="{{ $address->province_id }}"
data-city-id="{{ $address->city_id }}"
data-district-id="{{ $address->district_id }}"
data-village-id="{{ $address->subdistrict_id }}">
<option value="">{{ __('addresses.select_province') }}</option>
</select>
<div class="invalid-feedback">{{ __('addresses.please_select_province') }}</div>
</div>
</div>
<div class="col-sm-6">
<div class="position-relative">
<label class="form-label">{{ __('addresses.city') }}</label>
<select class="form-select city-select" required data-address-id="{{ $address->id }}">
<option value="">{{ __('addresses.select_city') }}</option>
</select>
<div class="invalid-feedback">{{ __('addresses.please_select_city') }}</div>
</div>
</div>
<div class="col-sm-6">
<div class="position-relative">
<label class="form-label">{{ __('addresses.district') }}</label>
<select class="form-select district-select" required
data-address-id="{{ $address->id }}">
<option value="">{{ __('addresses.select_district') }}</option>
</select>
<div class="invalid-feedback">{{ __('addresses.please_select_district') }}</div>
</div>
</div>
<div class="col-sm-6">
<div class="position-relative">
<label class="form-label">{{ __('addresses.village') }}</label>
<select class="form-select village-select" required
data-address-id="{{ $address->id }}">
<option value="">{{ __('addresses.select_village') }}</option>
</select>
<div class="invalid-feedback">{{ __('addresses.please_select_village') }}</div>
</div>
</div>
<div class="col-sm-4">
<div class="position-relative">
<label for="psa-zip-{{ $address->id }}"
class="form-label">{{ __('addresses.zip_code') }}</label>
<input type="text" class="form-control" id="psa-zip-{{ $address->id }}"
value="{{ $address->postal_code }}" required>
<div class="invalid-feedback">{{ __('addresses.please_enter_zip_code') }}</div>
</div>
</div>
<div class="col-sm-8">
<div class="position-relative">
<label for="psa-address-{{ $address->id }}"
class="form-label">{{ __('addresses.address') }}</label>
<input type="text" class="form-control" id="psa-address-{{ $address->id }}"
value="{{ $address->address }}" required>
<div class="invalid-feedback">{{ __('addresses.please_enter_address') }}</div>
</div>
</div>
<div class="col-12">
<div class="form-check mb-0">
<input type="checkbox" class="form-check-input" id="set-primary-{{ $address->id }}"
{{ $address->is_primary ? 'checked' : '' }}>
<label for="set-primary-{{ $address->id }}"
class="form-check-label">{{ __('addresses.set_as_primary_address') }}</label>
</div>
</div>
<div class="col-12 d-flex justify-content-between">
<div class="d-flex gap-3 pt-2 pt-sm-0">
<button type="submit"
class="btn btn-primary">{{ __('addresses.save_changes') }}</button>
<button type="button" class="btn btn-secondary" data-bs-toggle="collapse"
data-bs-target=".primary-address-{{ $address->id }}" aria-expanded="true"
aria-controls="primaryAddressPreview{{ $address->id }} primaryAddressEdit{{ $address->id }}">{{ __('addresses.close') }}</button>
</div>
<button type="button" class="btn btn-danger delete-address-btn"
data-address-id="{{ $address->id }}"
data-address-label="{{ $address->label }}">{{ __('addresses.delete') }}</button>
</div>
</form>
</div>
</div>
@endforeach
<!-- Add address button -->
<div class="nav pt-4">
<a class="nav-link animate-underline fs-base px-0" href="#newAddressModal" data-bs-toggle="modal">
<i class="ci-plus fs-lg ms-n1 me-2"></i>
<span class="animate-target">{{ __('addresses.add_address') }}</span>
</a>
</div>
</div>
</div>
<!-- New Address Modal -->
<div class="modal fade" id="newAddressModal" tabindex="-1" aria-labelledby="newAddressModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-lg">
<form class="needs-validation new-address-form" novalidate action="{{ route('addresses.store') }}"
method="POST">
@csrf
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="newAddressModalLabel">{{ __('addresses.add_address') }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body row g-3 g-sm-4 ">
<div class="alert alert-danger d-none new-address-form-message" role="alert"></div>
<div class="alert alert-success d-none new-address-form-success" role="alert"></div>
<div class="col-sm-6">
<div class="position-relative">
<label class="form-label">{{ __('addresses.province') }}</label>
<select class="form-select new-province-select" required>
<option value="">{{ __('addresses.select_province') }}</option>
</select>
<div class="invalid-feedback">{{ __('addresses.please_select_province') }}</div>
</div>
</div>
<div class="col-sm-6">
<div class="position-relative">
<label class="form-label">{{ __('addresses.city') }}</label>
<select class="form-select new-city-select" required>
<option value="">{{ __('addresses.select_city') }}</option>
</select>
<div class="invalid-feedback">{{ __('addresses.please_select_city') }}</div>
</div>
</div>
<div class="col-sm-6">
<div class="position-relative">
<label class="form-label">{{ __('addresses.district') }}</label>
<select class="form-select new-district-select" required>
<option value="">{{ __('addresses.select_district') }}</option>
</select>
<div class="invalid-feedback">{{ __('addresses.please_select_district') }}</div>
</div>
</div>
<div class="col-sm-6">
<div class="position-relative">
<label class="form-label">{{ __('addresses.village') }}</label>
<select class="form-select new-village-select" required>
<option value="">{{ __('addresses.select_village') }}</option>
</select>
<div class="invalid-feedback">{{ __('addresses.please_select_village') }}</div>
</div>
</div>
<div class="col-sm-4">
<div class="position-relative">
<label for="new-zip" class="form-label">{{ __('addresses.zip_code') }}</label>
<input type="text" class="form-control" id="new-zip" required>
<div class="invalid-feedback">{{ __('addresses.please_enter_zip_code') }}</div>
</div>
</div>
<div class="col-sm-8">
<div class="position-relative">
<label for="new-address" class="form-label">{{ __('addresses.address') }}</label>
<input type="text" class="form-control" id="new-address" required>
<div class="invalid-feedback">{{ __('addresses.please_enter_address') }}</div>
</div>
</div>
<div class="col-12">
<div class="form-check mb-0">
<input type="checkbox" class="form-check-input" id="set-primary-new">
<label for="set-primary-new"
class="form-check-label">{{ __('addresses.set_as_primary_address') }}</label>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">{{ __('addresses.save_changes') }}</button>
<button type="button" class="btn btn-secondary"
data-bs-dismiss="modal">{{ __('addresses.close') }}</button>
</div>
</div>
</form>
</div>
</div>
<!-- Delete Confirmation Modal -->
<div class="modal fade" id="deleteAddressModal" tabindex="-1" aria-labelledby="deleteAddressModalLabel"
aria-hidden="true">
<div class="modal fade" id="newAddressModal" tabindex="-1" aria-labelledby="newAddressModalLabel"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="deleteAddressModalLabel">{{ __('addresses.delete') }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>{{ __('addresses.confirm_delete_address') }}</p>
<div class="alert alert-warning">
<strong id="deleteAddressLabel"></strong>
</div>
<p class="text-muted small mb-0">{{ __('addresses.this_action_cannot_be_undone') }}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary"
data-bs-dismiss="modal">{{ __('addresses.cancel') }}</button>
<button type="button" class="btn btn-danger"
id="confirmDeleteBtn">{{ __('addresses.delete') }}</button>
</div>
</div>
</div>
</div>
@endsection
@section('scripts')
<script>
document.addEventListener('DOMContentLoaded', () => {
/* ===============================
* INIT EDIT BUTTON
* =============================== */
document.addEventListener('click', e => {
const btn = e.target.closest('.primaryAddressEditButton');
if (!btn) return;
const addressId = btn.dataset.addressId;
loadProvinces(addressId);
});
/* ===============================
* PROVINCE
* =============================== */
function loadProvinces(addressId) {
const province = document.querySelector(
`.province-select[data-address-id="${addressId}"]`
);
if (!province) return;
const {
provinceId,
cityId,
districtId,
villageId
} = province.dataset;
destroyChoices(province);
fetch('/addresses/provinces')
.then(r => r.json())
.then(({
data
}) => {
province.innerHTML =
`<option value="">{{ __('addresses.select_province') }}</option>`;
data.forEach(p => {
province.insertAdjacentHTML(
'beforeend',
`<option value="${p.id}" ${
String(p.id) === String(provinceId) ? 'selected' : ''
}>${p.name}</option>`
);
});
initChoices(province);
if (provinceId) {
loadCities(addressId, provinceId, cityId, districtId, villageId);
}
});
}
/* ===============================
* CITY
* =============================== */
function loadCities(addressId, provinceId, cityId = null, districtId = null, villageId = null) {
const city = document.querySelector(
`.city-select[data-address-id="${addressId}"]`
);
if (!city) return;
destroyChoices(city);
fetch(`/addresses/cities/${provinceId}`)
.then(r => r.json())
.then(({
data
}) => {
city.innerHTML =
`<option value="">{{ __('addresses.select_city') }}</option>`;
data.forEach(c => {
city.insertAdjacentHTML(
'beforeend',
`<option value="${c.id}" ${
String(c.id) === String(cityId) ? 'selected' : ''
}>${c.name}</option>`
);
});
initChoices(city);
if (cityId) {
loadDistricts(addressId, cityId, districtId, villageId);
}
});
}
/* ===============================
* DISTRICT
* =============================== */
function loadDistricts(addressId, cityId, districtId = null, villageId = null) {
const district = document.querySelector(
`.district-select[data-address-id="${addressId}"]`
);
if (!district) return;
destroyChoices(district);
fetch(`/addresses/districts/${cityId}`)
.then(r => r.json())
.then(({
data
}) => {
district.innerHTML =
`<option value="">{{ __('addresses.select_district') }}</option>`;
data.forEach(d => {
district.insertAdjacentHTML(
'beforeend',
`<option value="${d.id}" ${
String(d.id) === String(districtId) ? 'selected' : ''
}>${d.name}</option>`
);
});
initChoices(district);
if (districtId) {
loadVillages(addressId, districtId, villageId);
}
});
}
/* ===============================
* VILLAGE
* =============================== */
function loadVillages(addressId, districtId, villageId = null) {
const village = document.querySelector(
`.village-select[data-address-id="${addressId}"]`
);
if (!village) return;
destroyChoices(village);
fetch(`/addresses/villages/${districtId}`)
.then(r => r.json())
.then(({
data
}) => {
village.innerHTML =
`<option value="">{{ __('addresses.select_village') }}</option>`;
data.forEach(v => {
village.insertAdjacentHTML(
'beforeend',
`<option value="${v.id}" ${
String(v.id) === String(villageId) ? 'selected' : ''
}>${v.name}</option>`
);
});
initChoices(village);
});
}
/* ===============================
* CHANGE HANDLERS
* =============================== */
document.addEventListener('change', e => {
const el = e.target;
const addressId = el.dataset.addressId;
if (!addressId) return;
const city = document.querySelector(
`.city-select[data-address-id="${addressId}"]`
);
const district = document.querySelector(
`.district-select[data-address-id="${addressId}"]`
);
const village = document.querySelector(
`.village-select[data-address-id="${addressId}"]`
);
// 🔄 PROVINCE CHANGED
if (el.classList.contains('province-select')) {
resetSelect(city, "{{ __('addresses.select_city') }}");
resetSelect(district, "{{ __('addresses.select_district') }}");
resetSelect(village, "{{ __('addresses.select_village') }}");
if (el.value) {
loadCities(addressId, el.value);
}
}
// 🔄 CITY CHANGED
if (el.classList.contains('city-select')) {
resetSelect(district, "{{ __('addresses.select_district') }}");
resetSelect(village, "{{ __('addresses.select_village') }}");
if (el.value) {
loadDistricts(addressId, el.value);
}
}
// 🔄 DISTRICT CHANGED
if (el.classList.contains('district-select')) {
resetSelect(village, "{{ __('addresses.select_village') }}");
if (el.value) {
loadVillages(addressId, el.value);
}
}
});
function resetSelect(el, placeholder) {
if (!el) return;
// Destroy choices kalau ada
if (el._choices) {
el._choices.destroy();
el._choices = null;
}
// Reset option
el.innerHTML = `<option value="">${placeholder}</option>`;
// 🔥 JANGAN init Choices kalau belum ada
if (typeof window.Choices === 'undefined') return;
el._choices = new Choices(el, {
searchEnabled: true,
shouldSort: false,
itemSelectText: ''
});
}
/* ===============================
* NEW ADDRESS MODAL
* =============================== */
const newAddressModal = document.getElementById('newAddressModal');
if (newAddressModal) {
newAddressModal.addEventListener('show.bs.modal', () => {
loadNewProvinces();
});
}
function loadNewProvinces() {
const province = document.querySelector('.new-province-select');
if (!province) return;
destroyChoices(province);
fetch('/addresses/provinces')
.then(r => r.json())
.then(({
data
}) => {
province.innerHTML = `<option value="">{{ __('addresses.select_province') }}</option>`;
data.forEach(p => {
province.insertAdjacentHTML('beforeend',
`<option value="${p.id}">${p.name}</option>`);
});
initChoices(province);
});
}
function loadNewCities(provinceId) {
const city = document.querySelector('.new-city-select');
if (!city) return;
destroyChoices(city);
fetch(`/addresses/cities/${provinceId}`)
.then(r => r.json())
.then(({
data
}) => {
city.innerHTML = `<option value="">{{ __('addresses.select_city') }}</option>`;
data.forEach(c => {
city.insertAdjacentHTML('beforeend',
`<option value="${c.id}">${c.name}</option>`);
});
initChoices(city);
});
}
function loadNewDistricts(cityId) {
const district = document.querySelector('.new-district-select');
if (!district) return;
destroyChoices(district);
fetch(`/addresses/districts/${cityId}`)
.then(r => r.json())
.then(({
data
}) => {
district.innerHTML = `<option value="">{{ __('addresses.select_district') }}</option>`;
data.forEach(d => {
district.insertAdjacentHTML('beforeend',
`<option value="${d.id}">${d.name}</option>`);
});
initChoices(district);
});
}
function loadNewVillages(districtId) {
const village = document.querySelector('.new-village-select');
if (!village) return;
destroyChoices(village);
fetch(`/addresses/villages/${districtId}`)
.then(r => r.json())
.then(({
data
}) => {
village.innerHTML = `<option value="">{{ __('addresses.select_village') }}</option>`;
data.forEach(v => {
village.insertAdjacentHTML('beforeend',
`<option value="${v.id}">${v.name}</option>`);
});
initChoices(village);
});
}
// New address form change handlers
document.addEventListener('change', e => {
const el = e.target;
// 🔄 PROVINCE CHANGED
if (el.classList.contains('new-province-select')) {
resetSelect(document.querySelector('.new-city-select'),
"{{ __('addresses.select_city') }}");
resetSelect(document.querySelector('.new-district-select'),
"{{ __('addresses.select_district') }}");
resetSelect(document.querySelector('.new-village-select'),
"{{ __('addresses.select_village') }}");
if (el.value) {
loadNewCities(el.value);
}
}
// 🔄 CITY CHANGED
if (el.classList.contains('new-city-select')) {
resetSelect(document.querySelector('.new-district-select'),
"{{ __('addresses.select_district') }}");
resetSelect(document.querySelector('.new-village-select'),
"{{ __('addresses.select_village') }}");
if (el.value) {
loadNewDistricts(el.value);
}
}
// 🔄 DISTRICT CHANGED
if (el.classList.contains('new-district-select')) {
resetSelect(document.querySelector('.new-village-select'),
"{{ __('addresses.select_village') }}");
if (el.value) {
loadNewVillages(el.value);
}
}
});
/* ===============================
* DELETE ADDRESS
* =============================== */
let deleteAddressId = null;
let deleteModalInstance = null;
document.addEventListener('click', e => {
const deleteBtn = e.target.closest('.delete-address-btn');
if (!deleteBtn) return;
e.preventDefault(); // Prevent any default action
e.stopPropagation(); // Stop event propagation
deleteAddressId = deleteBtn.dataset.addressId;
const addressLabel = deleteBtn.dataset.addressLabel;
console.log('Delete button clicked:', {
deleteAddressId,
addressLabel
});
// Initialize modal if not already done
if (!deleteModalInstance) {
console.log('Initializing modal...');
console.log('Bootstrap available:', typeof bootstrap !== 'undefined');
console.log('Bootstrap.Modal available:', typeof bootstrap !== 'undefined' && bootstrap.Modal);
const deleteModal = document.getElementById('deleteAddressModal');
console.log('Modal element found:', !!deleteModal);
if (deleteModal && typeof bootstrap !== 'undefined' && bootstrap.Modal) {
deleteModalInstance = new bootstrap.Modal(deleteModal);
console.log('Modal instance created successfully');
} else {
console.error('Modal initialization failed:', {
modalExists: !!deleteModal,
bootstrapExists: typeof bootstrap !== 'undefined',
modalClassExists: typeof bootstrap !== 'undefined' && bootstrap.Modal
});
}
}
// Set address label in modal
const labelElement = document.getElementById('deleteAddressLabel');
if (labelElement) {
labelElement.textContent = addressLabel;
}
// Show modal
if (deleteModalInstance) {
console.log('Showing delete modal');
deleteModalInstance.show();
} else {
console.error('Modal instance not available');
// Fallback to confirm dialog
if (confirm(`{{ __('addresses.confirm_delete_address') }}\n${addressLabel}`)) {
deleteAddress(deleteAddressId);
}
}
});
// Handle confirm delete button click
document.getElementById('confirmDeleteBtn').addEventListener('click', () => {
console.log('Confirm delete clicked:', deleteAddressId);
if (deleteAddressId) {
deleteAddress(deleteAddressId);
if (deleteModalInstance) {
deleteModalInstance.hide();
}
}
});
function deleteAddress(addressId) {
const token = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
fetch(`/addresses/${addressId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': token,
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(result => {
console.log(result);
if (result.success) {
// Show success message and reload page
showToast(result.message, 'success');
setTimeout(() => {
window.location.reload();
}, 1000);
} else {
// Show error message
showToast(result.message || '{{ __('addresses.error_saving_address') }}',
'error');
}
})
.catch(error => {
console.error('Error:', error);
showToast('{{ __('addresses.error_saving_address') }}', 'error');
})
.finally(() => {
deleteAddressId = null;
});
}
/* ===============================
* FORM SUBMISSION
* =============================== */
document.addEventListener('submit', e => {
const form = e.target;
if (!form.classList.contains('address-form') && !form.classList.contains(
'new-address-form')) return;
e.preventDefault();
const formData = new FormData(form);
const submitBtn = form.querySelector('button[type="submit"]');
const originalText = submitBtn.textContent;
// Show loading state
submitBtn.disabled = true;
submitBtn.textContent = '{{ __('addresses.saving') }}...';
// Determine if this is a new address or edit form
const isNewAddress = form.classList.contains('new-address-form');
const addressId = isNewAddress ? null : form.querySelector('[data-address-id]').dataset
.addressId;
// Prepare form data for AJAX submission
const submitData = {
_token: formData.get('_token'),
province_id: isNewAddress ?
form.querySelector('.new-province-select').value : form.querySelector(
'.province-select').value,
city_id: isNewAddress ?
form.querySelector('.new-city-select').value : form.querySelector(
'.city-select').value,
district_id: isNewAddress ?
form.querySelector('.new-district-select').value : form.querySelector(
'.district-select').value,
subdistrict_id: isNewAddress ?
form.querySelector('.new-village-select').value : form.querySelector(
'.village-select').value,
postal_code: isNewAddress ?
form.querySelector('#new-zip').value : form.querySelector(
'input[id^="psa-zip-"]').value,
address: isNewAddress ?
form.querySelector('#new-address').value : form.querySelector(
'input[id^="psa-address-"]').value,
is_primary: (isNewAddress ?
form.querySelector('#set-primary-new') :
form.querySelector('input[id^="set-primary-"]')).checked ? '1' : '0'
};
if (!isNewAddress) {
submitData._method = 'PUT';
}
fetch(form.action, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Requested-With': 'XMLHttpRequest'
},
body: new URLSearchParams(submitData).toString()
})
.then(response => response.json())
.then(result => {
if (result.success) {
// Show success message in form
const successElement = form.querySelector(isNewAddress ?
'.new-address-form-success' : '.address-form-success');
const errorElement = form.querySelector(isNewAddress ?
'.new-address-form-message' : '.address-form-message');
if (successElement) {
successElement.textContent = result.message;
successElement.classList.remove('d-none');
}
if (errorElement) {
errorElement.classList.add('d-none');
}
// Handle success based on form type
if (isNewAddress) {
// Close modal and reload page
setTimeout(() => {
const modal = bootstrap.Modal.getInstance(newAddressModal);
if (modal) modal.hide();
window.location.reload();
}, 1500);
} else {
// Close the edit form and show preview after delay
setTimeout(() => {
const previewElement = document.getElementById(
`primaryAddressPreview${addressId}`);
const editElement = document.getElementById(
`primaryAddressEdit${addressId}`);
if (previewElement && editElement) {
previewElement.classList.add('show');
editElement.classList.remove('show');
}
// Reload page to show updated data
window.location.reload();
}, 2000);
}
} else {
// Show error message in form
const errorElement = form.querySelector(isNewAddress ?
'.new-address-form-message' : '.address-form-message');
const successElement = form.querySelector(isNewAddress ?
'.new-address-form-success' : '.address-form-success');
if (errorElement) {
errorElement.textContent = result.message ||
'{{ __('addresses.error_saving_address') }}';
errorElement.classList.remove('d-none');
}
if (successElement) {
successElement.classList.add('d-none');
}
}
})
.catch(error => {
console.error('Error:', error);
// Show error message in form
const errorElement = form.querySelector(isNewAddress ?
'.new-address-form-message' : '.address-form-message');
const successElement = form.querySelector(isNewAddress ?
'.new-address-form-success' : '.address-form-success');
if (errorElement) {
errorElement.textContent = '{{ __('addresses.error_saving_address') }}';
errorElement.classList.remove('d-none');
}
if (successElement) {
successElement.classList.add('d-none');
}
})
.finally(() => {
// Restore button state
submitBtn.disabled = false;
submitBtn.textContent = originalText;
});
});
// Toast notification helper
function showToast(message, type = 'info') {
// Create toast element if it doesn't exist
let toastContainer = document.getElementById('toast-container');
if (!toastContainer) {
toastContainer = document.createElement('div');
toastContainer.id = 'toast-container';
toastContainer.className = 'position-fixed top-0 end-0 p-3';
toastContainer.style.zIndex = '1050';
document.body.appendChild(toastContainer);
}
const toastId = 'toast-' + Date.now();
const toastHtml = `
<div id="${toastId}" class="toast align-items-center text-white bg-${type === 'success' ? 'success' : type === 'error' ? 'danger' : 'primary'} border-0" role="alert">
<div class="d-flex">
<div class="toast-body">
${message}
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
</div>
</div>
`;
toastContainer.insertAdjacentHTML('beforeend', toastHtml);
const toastElement = document.getElementById(toastId);
const toast = new bootstrap.Toast(toastElement, {
autohide: true,
delay: 3000
});
toast.show();
// Remove toast element after hidden
toastElement.addEventListener('hidden.bs.toast', () => {
toastElement.remove();
});
}
/* ===============================
* HELPERS
* =============================== */
function destroyChoices(el) {
if (el._choices) {
el._choices.destroy();
el._choices = null;
}
}
function initChoices(el) {
if (!window.Choices) return;
el._choices = new Choices(el, {
searchEnabled: true,
shouldSort: false,
itemSelectText: '',
noResultsText: 'No results found'
});
}
});
</script>
@endsection