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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user