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:
@@ -59,4 +59,8 @@ return [
|
||||
'redirect' => env('GOOGLE_REDIRECT_URI', '/auth/google/callback'),
|
||||
],
|
||||
|
||||
'google_places' => [
|
||||
'api_key' => env('GOOGLE_PLACES_API_KEY'),
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -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,29 +251,36 @@
|
||||
<input type="tel" id="customer_phone" name="customer_phone" value="{{ old('customer_phone') }}" required>
|
||||
</div>
|
||||
|
||||
<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 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" placeholder="e.g., 123 Main Street" value="{{ old('shipping_street_address') }}" required>
|
||||
<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_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>
|
||||
<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_city">City</label>
|
||||
<input type="text" id="shipping_city" name="shipping_city" placeholder="e.g., Pretoria" value="{{ old('shipping_city') }}" required>
|
||||
<input type="text" id="shipping_city" name="shipping_city" value="{{ old('shipping_city') }}" 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>
|
||||
<input type="text" id="shipping_zone" name="shipping_zone" value="{{ old('shipping_zone') }}" 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>
|
||||
<input type="text" id="shipping_postcode" name="shipping_postcode" value="{{ old('shipping_postcode') }}" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
@@ -248,6 +295,7 @@
|
||||
<option value="business" {{ old('shipping_type') === 'business' ? 'selected' : '' }}>Business</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="notes">Order Notes (Optional)</label>
|
||||
@@ -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
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-gray-600">Packed At</p>
|
||||
<p class="text-lg font-semibold"><?php echo e($order->packing_completed_at->format('M d, H:i')); ?></p>
|
||||
<p class="text-lg font-semibold"><?php echo e($order->packing_completed_at ? $order->packing_completed_at->format('M d, H:i') : 'Not packed'); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<!-- Read-Only State -->
|
||||
<div class="bg-blue-50 border-2 border-blue-200 rounded-lg p-6 mb-8">
|
||||
<div class="flex items-start">
|
||||
<div class="flex-1">
|
||||
<h2 class="text-2xl font-bold text-blue-900 mb-2">📋 Order Status: Read-Only</h2>
|
||||
<p class="text-blue-800 mb-4">
|
||||
This order is in the <strong><?php echo e(str_replace('_', ' ', ucfirst($order->status))); ?></strong> phase.
|
||||
No actions are available at this stage.
|
||||
</p>
|
||||
|
||||
<?php if(\Livewire\Mechanisms\ExtendBlade\ExtendBlade::isRenderingLivewireComponent()): ?><!--[if BLOCK]><![endif]--><?php endif; ?><?php if($order->status === 'ready_to_ship'): ?>
|
||||
<div class="bg-white border-l-4 border-blue-500 p-4 rounded">
|
||||
<p class="text-gray-700">
|
||||
<strong>Next Step:</strong> Move Trello card to "Ready to Ship" to trigger automatic shipment creation.
|
||||
</p>
|
||||
</div>
|
||||
<?php elseif($order->status === 'awaiting_collection'): ?>
|
||||
<div class="bg-white border-l-4 border-blue-500 p-4 rounded">
|
||||
<p class="text-gray-700">
|
||||
<strong>Status:</strong> Parcel is awaiting collection from courier.
|
||||
</p>
|
||||
<?php if(\Livewire\Mechanisms\ExtendBlade\ExtendBlade::isRenderingLivewireComponent()): ?><!--[if BLOCK]><![endif]--><?php endif; ?><?php if($order->courier_waybill_id): ?>
|
||||
<p class="text-gray-700 mt-2">
|
||||
<strong>Waybill:</strong> <code class="bg-gray-100 px-2 py-1 rounded"><?php echo e($order->courier_waybill_id); ?></code>
|
||||
</p>
|
||||
<?php endif; ?><?php if(\Livewire\Mechanisms\ExtendBlade\ExtendBlade::isRenderingLivewireComponent()): ?><!--[if ENDBLOCK]><![endif]--><?php endif; ?>
|
||||
</div>
|
||||
<?php elseif($order->status === 'in_transit'): ?>
|
||||
<div class="bg-white border-l-4 border-green-500 p-4 rounded">
|
||||
<p class="text-gray-700">
|
||||
<strong>Status:</strong> Parcel is in transit to the customer.
|
||||
</p>
|
||||
<?php if(\Livewire\Mechanisms\ExtendBlade\ExtendBlade::isRenderingLivewireComponent()): ?><!--[if BLOCK]><![endif]--><?php endif; ?><?php if($order->courier_tracking_number): ?>
|
||||
<p class="text-gray-700 mt-2">
|
||||
<strong>Tracking:</strong> <code class="bg-gray-100 px-2 py-1 rounded"><?php echo e($order->courier_tracking_number); ?></code>
|
||||
</p>
|
||||
<?php endif; ?><?php if(\Livewire\Mechanisms\ExtendBlade\ExtendBlade::isRenderingLivewireComponent()): ?><!--[if ENDBLOCK]><![endif]--><?php endif; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="bg-white border-l-4 border-gray-500 p-4 rounded">
|
||||
<p class="text-gray-700">
|
||||
The order is progressing through the fulfillment pipeline.
|
||||
</p>
|
||||
</div>
|
||||
<?php endif; ?><?php if(\Livewire\Mechanisms\ExtendBlade\ExtendBlade::isRenderingLivewireComponent()): ?><!--[if ENDBLOCK]><![endif]--><?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php /**PATH /var/www/additional_design/resources/views/ops/actions/read-only.blade.php ENDPATH**/ ?>
|
||||
@@ -210,8 +210,41 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="shipping_address">Delivery Address</label>
|
||||
<textarea id="shipping_address" name="shipping_address" required><?php echo e(old('shipping_address')); ?></textarea>
|
||||
<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="<?php echo e(old('shipping_street_address')); ?>" 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" placeholder="e.g., Menlyn" value="<?php echo e(old('shipping_local_area')); ?>" 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="<?php echo e(old('shipping_city')); ?>" 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="<?php echo e(old('shipping_zone')); ?>" 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="<?php echo e(old('shipping_postcode')); ?>" 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_type">Address Type</label>
|
||||
<select id="shipping_type" name="shipping_type" required>
|
||||
<option value="residential" <?php echo e(old('shipping_type') === 'residential' ? 'selected' : ''); ?>>Residential</option>
|
||||
<option value="business" <?php echo e(old('shipping_type') === 'business' ? 'selected' : ''); ?>>Business</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
||||
Reference in New Issue
Block a user