feat: Add Google Places autocomplete for address search

- Add address search input field with autocomplete
- Integrate Google Places API to provide address suggestions
- Parse selected address components and auto-populate form fields
- Address fields initially hidden, show when address is selected
- Add clear button to reset address selection
- Support South Africa locations with country restriction
- Add Google Places API key config to services.php
- Hide/show address fields based on selection and validation errors
This commit is contained in:
twotalesanimation
2026-01-03 00:15:30 +02:00
parent dd950b5aba
commit b8cc8bd421
5 changed files with 260 additions and 34 deletions
+171 -31
View File
@@ -43,7 +43,8 @@
}
.form-group input,
.form-group textarea {
.form-group textarea,
.form-group select {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
@@ -58,12 +59,51 @@
}
.form-group input:focus,
.form-group textarea:focus {
.form-group textarea:focus,
.form-group select:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(45, 80, 71, 0.1);
}
/* Google Places Autocomplete styling */
.pac-container {
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
margin-top: 4px;
}
.pac-item {
padding: 0.75rem;
cursor: pointer;
}
.pac-item:hover {
background-color: #f0f0f0;
}
.address-search-group {
margin-bottom: 1.5rem;
position: relative;
}
.address-clear-btn {
position: absolute;
right: 12px;
top: 32px;
background: none;
border: none;
color: #999;
cursor: pointer;
font-size: 1.2rem;
padding: 0;
display: none;
}
.address-clear-btn.show {
display: block;
}
.order-summary {
height: fit-content;
}
@@ -211,42 +251,50 @@
<input type="tel" id="customer_phone" name="customer_phone" value="{{ old('customer_phone') }}" required>
</div>
<div class="form-group">
<label for="shipping_street_address">Street Address</label>
<input type="text" id="shipping_street_address" name="shipping_street_address" placeholder="e.g., 123 Main Street" value="{{ old('shipping_street_address') }}" required>
<div class="address-search-group">
<label for="address_search">Delivery Address</label>
<input type="text" id="address_search" placeholder="Start typing your address..." autocomplete="off">
<button type="button" class="address-clear-btn" id="address_clear_btn"></button>
</div>
<div class="form-group">
<label for="shipping_local_area">Suburb/Local Area</label>
<input type="text" id="shipping_local_area" name="shipping_local_area" placeholder="e.g., Menlyn" value="{{ old('shipping_local_area') }}" required>
</div>
<div id="address_fields_group" style="display: none;">
<div class="form-group">
<label for="shipping_street_address">Street Address</label>
<input type="text" id="shipping_street_address" name="shipping_street_address" value="{{ old('shipping_street_address') }}" required>
</div>
<div class="form-group">
<label for="shipping_city">City</label>
<input type="text" id="shipping_city" name="shipping_city" placeholder="e.g., Pretoria" value="{{ old('shipping_city') }}" required>
</div>
<div class="form-group">
<label for="shipping_local_area">Suburb/Local Area</label>
<input type="text" id="shipping_local_area" name="shipping_local_area" value="{{ old('shipping_local_area') }}" required>
</div>
<div class="form-group">
<label for="shipping_zone">Province/Zone</label>
<input type="text" id="shipping_zone" name="shipping_zone" placeholder="e.g., Gauteng" value="{{ old('shipping_zone') }}" required>
</div>
<div class="form-group">
<label for="shipping_city">City</label>
<input type="text" id="shipping_city" name="shipping_city" value="{{ old('shipping_city') }}" required>
</div>
<div class="form-group">
<label for="shipping_postcode">Postal Code</label>
<input type="text" id="shipping_postcode" name="shipping_postcode" placeholder="e.g., 0181" value="{{ old('shipping_postcode') }}" required>
</div>
<div class="form-group">
<label for="shipping_zone">Province/Zone</label>
<input type="text" id="shipping_zone" name="shipping_zone" value="{{ old('shipping_zone') }}" required>
</div>
<div class="form-group">
<label for="shipping_country">Country</label>
<input type="text" id="shipping_country" name="shipping_country" value="ZA" readonly>
</div>
<div class="form-group">
<label for="shipping_postcode">Postal Code</label>
<input type="text" id="shipping_postcode" name="shipping_postcode" value="{{ old('shipping_postcode') }}" required>
</div>
<div class="form-group">
<label for="shipping_type">Address Type</label>
<select id="shipping_type" name="shipping_type" required>
<option value="residential" {{ old('shipping_type') === 'residential' ? 'selected' : '' }}>Residential</option>
<option value="business" {{ old('shipping_type') === 'business' ? 'selected' : '' }}>Business</option>
</select>
<div class="form-group">
<label for="shipping_country">Country</label>
<input type="text" id="shipping_country" name="shipping_country" value="ZA" readonly>
</div>
<div class="form-group">
<label for="shipping_type">Address Type</label>
<select id="shipping_type" name="shipping_type" required>
<option value="residential" {{ old('shipping_type') === 'residential' ? 'selected' : '' }}>Residential</option>
<option value="business" {{ old('shipping_type') === 'business' ? 'selected' : '' }}>Business</option>
</select>
</div>
</div>
<div class="form-group">
@@ -320,4 +368,96 @@
</div>
</div>
</div>
<script src="https://maps.googleapis.com/maps/api/js?key={{ config('services.google_places.api_key') }}&libraries=places"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const addressSearchInput = document.getElementById('address_search');
const addressFieldsGroup = document.getElementById('address_fields_group');
const clearBtn = document.getElementById('address_clear_btn');
if (!addressSearchInput) return;
// Initialize Google Places Autocomplete
const autocomplete = new google.maps.places.Autocomplete(addressSearchInput, {
componentRestrictions: { country: 'za' },
types: ['geocode']
});
// Prevent form submission on Enter when autocomplete is open
addressSearchInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && document.querySelector('.pac-container:not([style*="display: none"])')) {
e.preventDefault();
}
});
// When place is selected
autocomplete.addListener('place_changed', function() {
const place = autocomplete.getPlace();
if (!place.geometry) {
console.error('Place has no geometry');
return;
}
// Parse address components
const addressComponents = {};
place.address_components.forEach(component => {
addressComponents[component.types[0]] = component.long_name;
});
// Populate form fields
document.getElementById('shipping_street_address').value =
(addressComponents['street_number'] ? addressComponents['street_number'] + ' ' : '') +
(addressComponents['route'] || '');
document.getElementById('shipping_local_area').value =
addressComponents['sublocality'] || addressComponents['sublocality_level_1'] || '';
document.getElementById('shipping_city').value =
addressComponents['locality'] || addressComponents['administrative_area_level_2'] || '';
document.getElementById('shipping_zone').value =
addressComponents['administrative_area_level_1'] || '';
document.getElementById('shipping_postcode').value =
addressComponents['postal_code'] || '';
document.getElementById('shipping_country').value =
addressComponents['country'] || 'ZA';
// Show address fields group
addressFieldsGroup.style.display = 'block';
// Show clear button
clearBtn.classList.add('show');
});
// Clear button functionality
clearBtn.addEventListener('click', function(e) {
e.preventDefault();
addressSearchInput.value = '';
addressSearchInput.focus();
clearBtn.classList.remove('show');
addressFieldsGroup.style.display = 'none';
// Clear form fields
document.getElementById('shipping_street_address').value = '';
document.getElementById('shipping_local_area').value = '';
document.getElementById('shipping_city').value = '';
document.getElementById('shipping_zone').value = '';
document.getElementById('shipping_postcode').value = '';
});
// Show fields if form has old values (validation error)
const hasOldValues =
document.getElementById('shipping_street_address').value ||
document.getElementById('shipping_city').value;
if (hasOldValues) {
addressFieldsGroup.style.display = 'block';
}
});
</script>
@endsection