Compare commits

..

No commits in common. "9a23e75f5b338d62bfadcb9f81461fc6b30e673c" and "8c21d4dc7c9ee2cfd5526dc1c1b18b2da0bf63c6" have entirely different histories.

6 changed files with 353 additions and 823 deletions

View File

@ -16,7 +16,7 @@ class CartController extends Controller
$count = $repository->getCount($request->input('location_id')); $count = $repository->getCount($request->input('location_id'));
return response()->json([ return response()->json([
'count' => $count, 'count' => $count
]); ]);
} }
@ -79,18 +79,4 @@ class CartController extends Controller
return redirect()->route('cart.index'); return redirect()->route('cart.index');
} }
public function clear(MemberCartRepository $repository)
{
$repository->clearAll();
if (request()->expectsJson()) {
return response()->json([
'success' => true,
'message' => 'Cart cleared successfully'
]);
}
return redirect()->route('cart.index');
}
} }

View File

@ -13,19 +13,9 @@ class MemberCartRepository
$location = $locationId ?? session('location_id', 22); $location = $locationId ?? session('location_id', 22);
return Cart::where('user_id', auth()->user()->id) return Cart::where('user_id', auth()->user()->id)
->where('location_id', $location) ->where('location_id', $location)
->sum('qty'); ->count();
} }
static public function clearAll($locationId = null)
{
$location = $locationId ?? session('location_id', 22);
return Cart::where('user_id', auth()->user()->id)
->where('location_id', $location)
->delete();
}
public function getList($request) public function getList($request)
{ {
$location = (int) $request->input("location_id"); $location = (int) $request->input("location_id");

View File

@ -56,44 +56,29 @@
<th scope="col" class="py-0 px-0"> <th scope="col" class="py-0 px-0">
<div class="nav justify-content-end"> <div class="nav justify-content-end">
<button type="button" <button type="button"
class="nav-link d-inline-block text-decoration-underline text-nowrap py-3 px-0" class="nav-link d-inline-block text-decoration-underline text-nowrap py-3 px-0">Clear
onclick="clearCart()">Clear
cart</button> cart</button>
</div> </div>
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody class="align-middle"> <tbody class="align-middle">
@if ($carts->count() == 0)
@foreach ($carts as $key => $cart)
<tr> <tr>
<td colspan="5" class="text-center py-5"> <td class="py-3 ps-0">
<div class="py-4"> <div class="d-flex align-items-center">
<i class="ci-shopping-bag text-muted" style="font-size: 3rem;"></i> <a class="flex-shrink-0"
<h5 class="mt-3 mb-2">Your cart is empty</h5> href="{{ route('second', ['shop', 'product-general-electronics']) }}">
<p class="text-muted mb-4">Looks like you haven't added any items to your <img src="{{ $cart->itemReference->item->image_url ?? '' }}" width="110"
cart yet.</p> alt="{{ $cart->name ?? 'Product' }}">
<a href="{{ route('home') }}" class="btn btn-primary">
<i class="ci-shopping-bag me-2"></i>Start Shopping
</a> </a>
</div> <div class="w-100 min-w-0 ps-2 ps-xl-3">
</td> <h5 class="d-flex animate-underline mb-2">
</tr> <a class="d-block fs-sm fw-medium text-truncate animate-target"
@else href="{{ $cart->itemVariant->item->slug != null ? route('product.detail', [$cart->item->slug]) : '#' }}">{{ $cart->itemVariant->display_name ?? $cart->itemVariant->description ?? 'Product Name' }}</a>
@foreach ($carts as $key => $cart) </h5>
<tr data-cart-id="{{ $cart->id }}"> {{-- <ul class="list-unstyled gap-1 fs-xs mb-0">
<td class="py-3 ps-0">
<div class="d-flex align-items-center">
<a class="flex-shrink-0"
href="{{ route('product.detail', $cart->itemVariant->item->slug) }}">
<img src="{{ $cart->itemReference->item->image_url ?? '' }}"
width="110" alt="{{ $cart->name ?? 'Product' }}">
</a>
<div class="w-100 min-w-0 ps-2 ps-xl-3">
<h5 class="d-flex animate-underline mb-2">
<a class="d-block fs-sm fw-medium text-truncate animate-target"
href="{{ $cart->itemVariant->item->slug != null ? route('product.detail', [$cart->item->slug]) : '#' }}">{{ $cart->itemVariant->display_name ?? ($cart->itemVariant->description ?? 'Product Name') }}</a>
</h5>
{{-- <ul class="list-unstyled gap-1 fs-xs mb-0">
<li><span class="text-body-secondary">Color:</span> <span <li><span class="text-body-secondary">Color:</span> <span
class="text-dark-emphasis fw-medium">{{ $cart->color ?? 'N/A' }}</span></li> class="text-dark-emphasis fw-medium">{{ $cart->color ?? 'N/A' }}</span></li>
<li><span class="text-body-secondary">Model:</span> <span <li><span class="text-body-secondary">Model:</span> <span
@ -103,64 +88,56 @@
<span class="text-dark-emphasis fw-medium">${{ number_format($cart->itemReference->display_price ?? 0, 2) }}</span> <span class="text-dark-emphasis fw-medium">${{ number_format($cart->itemReference->display_price ?? 0, 2) }}</span>
</li> </li>
</ul> --}} </ul> --}}
<div class="count-input rounded-2 d-md-none mt-3"> <div class="count-input rounded-2 d-md-none mt-3">
<button type="button" class="btn btn-sm btn-icon" <button type="button" class="btn btn-sm btn-icon" data-decrement
data-decrement aria-label="Decrement quantity"> aria-label="Decrement quantity">
<i class="ci-minus"></i> <i class="ci-minus"></i>
</button> </button>
<input type="number" class="form-control form-control-sm" <input type="number" class="form-control form-control-sm"
value="{{ $cart->qty ?? 1 }}" readonly> value="{{ $cart->qty ?? 1 }}" readonly>
<button type="button" class="btn btn-sm btn-icon" <button type="button" class="btn btn-sm btn-icon" data-increment
data-increment aria-label="Increment quantity"> aria-label="Increment quantity">
<i class="ci-plus"></i> <i class="ci-plus"></i>
</button> </button>
</div>
</div> </div>
</div> </div>
</td> </div>
<td class="h6 py-3 d-none d-xl-table-cell"> </td>
<input type="hidden" id="price_{{ $cart->id }}" <td class="h6 py-3 d-none d-xl-table-cell" >
value="{{ $cart->itemVariant->display_price ?? 0 }}"> <input type="hidden" id="price_{{ $cart->id }}" value="{{ $cart->itemVariant->display_price ?? 0 }}">
Rp {{ number_format($cart->itemVariant->display_price ?? 0, 0, ',', '.') }} Rp {{ number_format($cart->itemVariant->display_price ?? 0, 0,",",".") }}</td>
</td> <td class="py-3 d-none d-md-table-cell">
<td class="py-3 d-none d-md-table-cell"> <div class="count-input">
<div class="count-input"> <button type="button" class="btn btn-icon" data-decrement
<button type="button" class="btn btn-icon" data-decrement aria-label="Decrement quantity" onclick="updateCartItem({{ $cart->id }}, 'decrement', this)">
aria-label="Decrement quantity" <i class="ci-minus"></i>
onclick="updateCartItem({{ $cart->id }}, 'decrement', this)"> </button>
<i class="ci-minus"></i> <input type="number" class="form-control" value="{{ $cart->qty ?? 1 }}" readonly>
</button> <button type="button" class="btn btn-icon" data-increment
<input type="number" class="form-control" aria-label="Increment quantity" onclick="updateCartItem({{ $cart->id }}, 'increment', this)">
value="{{ $cart->qty ?? 1 }}" readonly> <i class="ci-plus"></i>
<button type="button" class="btn btn-icon" data-increment </button>
aria-label="Increment quantity" </div>
onclick="updateCartItem({{ $cart->id }}, 'increment', this)"> </td>
<i class="ci-plus"></i> <td class="h6 py-3 d-none d-md-table-cell total-row">Rp {{ number_format(($cart->display_price ?? 0) * ($cart->qty ?? 1), 0,",",".") }}</td>
</button> <td class="text-end py-3 px-0">
</div> <form action="{{ route('cart.delete', $cart->id) }}" method="POST" onsubmit="deleteCartItem(event, this, {{ $cart->id }})">
</td> @csrf
<td class="h6 py-3 d-none d-md-table-cell total-row">Rp @method('DELETE')
{{ number_format(($cart->display_price ?? 0) * ($cart->qty ?? 1), 0, ',', '.') }} <button type="submit" class="btn-close fs-sm" data-bs-toggle="tooltip"
</td> data-bs-custom-class="tooltip-sm" data-bs-title="Remove"
<td class="text-end py-3 px-0"> aria-label="Remove from cart"></button>
<form action="{{ route('cart.delete', $cart->id) }}" method="POST" </form>
onsubmit="deleteCartItem(event, this, {{ $cart->id }})"> </td>
@csrf </tr>
@method('DELETE') @endforeach
<button type="submit" class="btn-close fs-sm" data-bs-toggle="tooltip"
data-bs-custom-class="tooltip-sm" data-bs-title="Remove"
aria-label="Remove from cart"></button>
</form>
</td>
</tr>
@endforeach
@endif
</tbody> </tbody>
</table> </table>
<div class="nav position-relative z-2 mb-4 mb-lg-0"> <div class="nav position-relative z-2 mb-4 mb-lg-0">
<a class="nav-link animate-underline px-0" href="{{ route('home') }}"> <a class="nav-link animate-underline px-0"
href="{{ route('second', ['shop', 'catalog-electronics']) }}'">
<i class="ci-chevron-left fs-lg me-1"></i> <i class="ci-chevron-left fs-lg me-1"></i>
<span class="animate-target">Continue shopping</span> <span class="animate-target">Continue shopping</span>
</a> </a>
@ -170,82 +147,77 @@
<!-- Order summary (sticky sidebar) --> <!-- Order summary (sticky sidebar) -->
@if ($carts->count() > 0) <aside class="col-lg-4" style="margin-top: -100px">
<aside class="col-lg-4" style="margin-top: -100px"> <div class="position-sticky top-0" style="padding-top: 100px">
<div class="position-sticky top-0" style="padding-top: 100px"> <div class="bg-body-tertiary rounded-5 p-4 mb-3">
<div class="bg-body-tertiary rounded-5 p-4 mb-3"> <div class="p-sm-2 p-lg-0 p-xl-2">
<div class="p-sm-2 p-lg-0 p-xl-2"> <h5 class="border-bottom pb-4 mb-4">Order summary</h5>
<h5 class="border-bottom pb-4 mb-4">Order summary</h5> <ul class="list-unstyled fs-sm gap-3 mb-0">
<ul class="list-unstyled fs-sm gap-3 mb-0"> <li class="d-flex justify-content-between">
<li class="d-flex justify-content-between"> Subtotal (3 items):
<div>Subtotal (<span <span class="text-dark-emphasis fw-medium">$2,427.00</span>
class="cart-count">{{ auth()->check() ? \App\Repositories\Member\Cart\MemberCartRepository::getCount() : 0 }}</span> </li>
items):</div> <li class="d-flex justify-content-between">
<span class="text-dark-emphasis fw-medium" id="cart-subtotal">Rp 0</span> Saving:
</li> <span class="text-danger fw-medium">-$110.00</span>
<li class="d-flex justify-content-between"> </li>
Saving: <li class="d-flex justify-content-between">
<span class="text-danger fw-medium">Rp 0</span> Tax collected:
</li> <span class="text-dark-emphasis fw-medium">$73.40</span>
<li class="d-flex justify-content-between" id="tax-row" style="display: none;"> </li>
Tax collected: <li class="d-flex justify-content-between">
<span class="text-dark-emphasis fw-medium">Rp 0</span> Shipping:
</li> <span class="text-dark-emphasis fw-medium">Calculated at checkout</span>
<li class="d-flex justify-content-between"> </li>
Shipping: </ul>
<span class="text-dark-emphasis fw-medium">Calculated at checkout</span> <div class="border-top pt-4 mt-4">
</li> <div class="d-flex justify-content-between mb-3">
</ul> <span class="fs-sm">Estimated total:</span>
<div class="border-top pt-4 mt-4"> <span class="h5 mb-0">$2,390.40</span>
<div class="d-flex justify-content-between mb-3"> </div>
<span class="fs-sm">Estimated total:</span> <a class="btn btn-lg btn-primary w-100"
<span class="h5 mb-0" id="cart-estimated-total">$0.00</span> href="{{ route('second', ['checkout', 'v1-delivery-1']) }}">
</div> Proceed to checkout
<a class="btn btn-lg btn-primary w-100" <i class="ci-chevron-right fs-lg ms-1 me-n1"></i>
href="{{ route('second', ['checkout', 'v1-delivery-1']) }}"> </a>
Proceed to checkout <div class="nav justify-content-center fs-sm mt-3">
<i class="ci-chevron-right fs-lg ms-1 me-n1"></i>
</a>
{{-- <div class="nav justify-content-center fs-sm mt-3">
<a class="nav-link text-decoration-underline p-0 me-1" href="#authForm" <a class="nav-link text-decoration-underline p-0 me-1" href="#authForm"
data-bs-toggle="offcanvas" role="button">Create an account</a> data-bs-toggle="offcanvas" role="button">Create an account</a>
and get and get
<span class="text-dark-emphasis fw-medium ms-1">239 bonuses</span> <span class="text-dark-emphasis fw-medium ms-1">239 bonuses</span>
</div> --}}
</div>
</div>
</div>
<div class="accordion bg-body-tertiary rounded-5 p-4">
<div class="accordion-item border-0">
<h3 class="accordion-header" id="promoCodeHeading">
<button type="button"
class="accordion-button animate-underline collapsed py-0 ps-sm-2 ps-lg-0 ps-xl-2"
data-bs-toggle="collapse" data-bs-target="#promoCode" aria-expanded="false"
aria-controls="promoCode">
<i class="ci-percent fs-xl me-2"></i>
<span class="animate-target me-2">Apply promo code</span>
</button>
</h3>
<div class="accordion-collapse collapse" id="promoCode"
aria-labelledby="promoCodeHeading">
<div class="accordion-body pt-3 pb-2 ps-sm-2 px-lg-0 px-xl-2">
<form class="needs-validation d-flex gap-2" novalidate>
<div class="position-relative w-100">
<input type="text" class="form-control"
placeholder="Enter promo code" required>
<div class="invalid-tooltip bg-transparent py-0">Enter a valid promo
code!
</div>
</div>
<button type="submit" class="btn btn-dark">Apply</button>
</form>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</aside> <div class="accordion bg-body-tertiary rounded-5 p-4">
@endif <div class="accordion-item border-0">
<h3 class="accordion-header" id="promoCodeHeading">
<button type="button"
class="accordion-button animate-underline collapsed py-0 ps-sm-2 ps-lg-0 ps-xl-2"
data-bs-toggle="collapse" data-bs-target="#promoCode" aria-expanded="false"
aria-controls="promoCode">
<i class="ci-percent fs-xl me-2"></i>
<span class="animate-target me-2">Apply promo code</span>
</button>
</h3>
<div class="accordion-collapse collapse" id="promoCode"
aria-labelledby="promoCodeHeading">
<div class="accordion-body pt-3 pb-2 ps-sm-2 px-lg-0 px-xl-2">
<form class="needs-validation d-flex gap-2" novalidate>
<div class="position-relative w-100">
<input type="text" class="form-control" placeholder="Enter promo code"
required>
<div class="invalid-tooltip bg-transparent py-0">Enter a valid promo code!
</div>
</div>
<button type="submit" class="btn btn-dark">Apply</button>
</form>
</div>
</div>
</div>
</div>
</div>
</aside>
</div> </div>
</section> </section>
@ -756,477 +728,153 @@
</section> </section>
</main> </main>
<!-- Clear Cart Confirmation Modal -->
<div class="modal fade" id="clearCartModal" tabindex="-1" aria-labelledby="clearCartModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header border-0">
<h5 class="modal-title" id="clearCartModalLabel">Clear Cart</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body text-center py-4">
<div class="mb-4">
<i class="ci-trash text-danger" style="font-size: 3rem;"></i>
</div>
<h6 class="mb-3">Are you sure you want to clear your entire cart?</h6>
<p class="text-muted mb-4">This action cannot be undone. All items will be removed from your cart.</p>
</div>
<div class="modal-footer border-0 justify-content-center">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" id="confirmClearCart">Clear Cart</button>
</div>
</div>
</div>
</div>
<!-- Delete Item Confirmation Modal -->
<div class="modal fade" id="deleteItemModal" tabindex="-1" aria-labelledby="deleteItemModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header border-0">
<h5 class="modal-title" id="deleteItemModalLabel">Remove Item</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body text-center py-4">
<div class="mb-4">
<i class="ci-trash text-warning" style="font-size: 3rem;"></i>
</div>
<h6 class="mb-3">Are you sure you want to remove this item from cart?</h6>
<p class="text-muted mb-4">This action cannot be undone. The item will be removed from your cart.</p>
</div>
<div class="modal-footer border-0 justify-content-center">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-warning" id="confirmDeleteItem">Remove Item</button>
</div>
</div>
</div>
</div>
@include('layouts.partials/footer') @include('layouts.partials/footer')
@endsection @endsection
@section('scripts') @section('scripts')
<script> <script>
// JavaScript number formatting function // JavaScript number formatting function
function number_format(number, decimals, dec_point, thousands_sep) { function number_format(number, decimals, dec_point, thousands_sep) {
number = (number + '').replace(/[^0-9+\-Ee.]/g, ''); number = (number + '').replace(/[^0-9+\-Ee.]/g, '');
var n = !isFinite(+number) ? 0 : +number; var n = !isFinite(+number) ? 0 : +number;
var prec = !isFinite(+decimals) ? 0 : Math.abs(decimals); var prec = !isFinite(+decimals) ? 0 : Math.abs(decimals);
var sep = (typeof thousands_sep === 'undefined') ? ',' : thousands_sep; var sep = (typeof thousands_sep === 'undefined') ? ',' : thousands_sep;
var dec = (typeof dec_point === 'undefined') ? '.' : dec_point; var dec = (typeof dec_point === 'undefined') ? '.' : dec_point;
var s = ''; var s = '';
var toFixedFix = function(n, prec) { var toFixedFix = function (n, prec) {
var k = Math.pow(10, prec); var k = Math.pow(10, prec);
return '' + Math.round(n * k) / k; return '' + Math.round(n * k) / k;
}; };
s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.'); s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
if (thousands_sep) { if (thousands_sep) {
var re = /(-?\d+)(\d{3})/; var re = /(-?\d+)(\d{3})/;
while (re.test(s[0])) { while (re.test(s[0])) {
s[0] = s[0].replace(re, '$1' + sep + '$2'); s[0] = s[0].replace(re, '$1' + sep + '$2');
}
}
if ((s[1] || '').length < prec) {
s[1] = s[1] || '';
s[1] += new Array(prec - s[1].length + 1).join('0');
}
return s.join(dec);
} }
}
async function updateCartItem(cartId, action, button) { if ((s[1] || '').length < prec) {
const container = button.closest('.count-input'); s[1] = s[1] || '';
const input = container.querySelector('input[type="number"]'); s[1] += new Array(prec - s[1].length + 1).join('0');
let currentQty = parseInt(input.value); }
if (action === 'increment') { return s.join(dec);
currentQty++; }
} else if (action === 'decrement' && currentQty > 1) {
currentQty--;
}
// input.value = currentQty; async function updateCartItem(cartId, action, button) {
const container = button.closest('.count-input');
const input = container.querySelector('input[type="number"]');
let currentQty = parseInt(input.value);
try { if (action === 'increment') {
const response = await fetch(`cart/${cartId}`, { currentQty++;
method: 'PUT', } else if (action === 'decrement' && currentQty > 1) {
headers: { currentQty--;
'Content-Type': 'application/json', }
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute(
'content'),
'Accept': 'application/json'
},
body: JSON.stringify({
qty: currentQty
})
});
if (response.ok) { // input.value = currentQty;
const result = await response.json();
if (result.success) {
// Update total price display
const row = button.closest('tr');
const totalCell = row.querySelector('.total-row');
const priceInput = document.getElementById(`price_${cartId}`);
const price = parseFloat(priceInput.value);
totalCell.innerHTML = `Rp ${number_format(price * currentQty, 0, ',', '.')}`;
// Update mobile quantity input try {
const mobileInput = row.querySelector('td:first-child .count-input input'); const response = await fetch(`cart/${cartId}`, {
if (mobileInput) { method: 'PUT',
mobileInput.value = currentQty; headers: {
} 'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
'Accept': 'application/json'
},
body: JSON.stringify({
qty: currentQty
})
});
// Update cart count in header if (response.ok) {
updateCartCount(); const result = await response.json();
} if (result.success) {
} else { // Update total price display
console.error('Error updating cart:', await response.json()); const row = button.closest('tr');
} const totalCell = row.querySelector('.total-row');
} catch (error) {
console.error('Network error:', error);
}
}
async function deleteCartItem(event, form, cartId) {
event.preventDefault();
// Store the cart ID and form for later use
window.pendingDelete = {
cartId,
form
};
// Show Bootstrap modal instead of native confirm
showDeleteItemModal();
}
// Function to show the delete item modal
function showDeleteItemModal() {
// Try to use Bootstrap Modal if available
if (typeof bootstrap !== 'undefined' && bootstrap.Modal) {
const modal = new bootstrap.Modal(document.getElementById('deleteItemModal'));
modal.show();
} else {
// Fallback: manually show the modal
const modalElement = document.getElementById('deleteItemModal');
if (modalElement) {
modalElement.style.display = 'block';
modalElement.classList.add('show');
modalElement.setAttribute('aria-modal', 'true');
modalElement.setAttribute('role', 'dialog');
// Add backdrop
const backdrop = document.createElement('div');
backdrop.className = 'modal-backdrop fade show';
backdrop.id = 'deleteItemBackdrop';
document.body.appendChild(backdrop);
// Handle close buttons
const closeButtons = modalElement.querySelectorAll('[data-bs-dismiss="modal"]');
closeButtons.forEach(button => {
button.onclick = hideDeleteItemModal;
});
// Handle backdrop click
backdrop.onclick = hideDeleteItemModal;
}
}
}
// Function to hide the delete item modal
function hideDeleteItemModal() {
const modalElement = document.getElementById('deleteItemModal');
const backdrop = document.getElementById('deleteItemBackdrop');
if (modalElement) {
modalElement.style.display = 'none';
modalElement.classList.remove('show');
modalElement.removeAttribute('aria-modal');
modalElement.removeAttribute('role');
}
if (backdrop) {
backdrop.remove();
}
}
// Function to actually delete the item (called when confirm button is clicked)
async function executeDeleteItem() {
if (!window.pendingDelete) return;
const {
cartId,
form
} = window.pendingDelete;
try {
const response = await fetch(`cart/${cartId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute(
'content'),
'Accept': 'application/json'
}
});
if (response.ok) {
const result = await response.json();
if (result.success) {
// Hide modal first
hideDeleteItemModal();
// Remove the row from the table
const row = form.closest('tr');
row.remove();
// Update cart count in header
updateCartCount();
// Recalculate totals
calculateCartTotals();
// Clear pending delete
window.pendingDelete = null;
}
} else {
console.error('Error deleting cart item:', await response.json());
}
} catch (error) {
console.error('Network error:', error);
}
}
async function updateCartCount() {
try {
const response = await fetch('cart/count', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
if (response.ok) {
const result = await response.json();
const cartCountElements = document.querySelectorAll('.cart-count');
cartCountElements.forEach(element => {
element.textContent = result.count;
});
}
} catch (error) {
console.error('Error updating cart count:', error);
}
}
// Function to calculate and update cart totals
function calculateCartTotals() {
let subtotal = 0;
let itemCount = 0;
// Get all cart item rows
const cartRows = document.querySelectorAll('tbody tr[data-cart-id]');
console.log('Found cart rows:', cartRows.length);
cartRows.forEach(row => {
const cartId = row.dataset.cartId;
const priceInput = document.getElementById(`price_${cartId}`); const priceInput = document.getElementById(`price_${cartId}`);
const quantityInput = row.querySelector('input[type="number"]'); const price = parseFloat(priceInput.value);
totalCell.innerHTML = `Rp ${number_format(price * currentQty, 0, ',', '.')}`;
console.log('Processing cart ID:', cartId, 'Price input:', priceInput, 'Quantity input:', // Update mobile quantity input
quantityInput); const mobileInput = row.querySelector('td:first-child .count-input input');
if (mobileInput) {
if (priceInput && quantityInput) { mobileInput.value = currentQty;
const price = parseFloat(priceInput.value);
const quantity = parseInt(quantityInput.value);
subtotal += price * quantity;
itemCount += quantity;
console.log('Price:', price, 'Quantity:', quantity, 'Running subtotal:', subtotal);
} }
});
console.log('Final subtotal:', subtotal, 'Item count:', itemCount); // Update cart count in header
updateCartCount();
// Update subtotal display
const subtotalElement = document.getElementById('cart-subtotal');
if (subtotalElement) {
subtotalElement.textContent = `Rp ${number_format(subtotal, 0, ',', '.')}`;
console.log('Updated subtotal element:', subtotalElement.textContent);
}
// Calculate estimated total (subtotal - savings + tax)
const savings = 0; // Fixed savings amount
const tax = 0; // Fixed tax amount
const estimatedTotal = subtotal - savings + tax;
// Show/hide tax row based on tax amount
const taxRow = document.getElementById('tax-row');
if (taxRow) {
if (tax > 0) {
taxRow.style.display = 'flex';
taxRow.querySelector('span').textContent = `Rp ${number_format(tax, 0, ',', '.')}`;
} else {
taxRow.style.display = 'none';
}
}
// Update estimated total display
const estimatedTotalElement = document.getElementById('cart-estimated-total');
if (estimatedTotalElement) {
estimatedTotalElement.textContent = `Rp ${number_format(estimatedTotal, 0, ',', '.')}`;
console.log('Updated estimated total element:', estimatedTotalElement.textContent);
}
// Update items count
const cartCalcElement = document.getElementById('cart-calc');
if (cartCalcElement) {
cartCalcElement.textContent = itemCount;
console.log('Updated item count:', cartCalcElement.textContent);
} }
} else {
console.error('Error updating cart:', await response.json());
} }
} catch (error) {
console.error('Network error:', error);
}
}
// Initialize cart totals on page load async function deleteCartItem(event, form, cartId) {
document.addEventListener('DOMContentLoaded', function() { event.preventDefault();
calculateCartTotals();
// Add event listener for clear cart confirm button if (!confirm('Are you sure you want to remove this item from cart?')) {
const confirmClearButton = document.getElementById('confirmClearCart'); return;
if (confirmClearButton) { }
confirmClearButton.addEventListener('click', executeClearCart);
}
// Add event listener for delete item confirm button try {
const confirmDeleteButton = document.getElementById('confirmDeleteItem'); const response = await fetch(`cart/${cartId}`, {
if (confirmDeleteButton) { method: 'DELETE',
confirmDeleteButton.addEventListener('click', executeDeleteItem); headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
'Accept': 'application/json'
} }
}); });
// Update cart totals when cart items are updated if (response.ok) {
const originalUpdateCartItem = updateCartItem; const result = await response.json();
window.updateCartItem = async function(cartId, action, button) { if (result.success) {
await originalUpdateCartItem(cartId, action, button); // Remove the row from the table
const row = form.closest('tr');
row.remove();
// Recalculate totals after successful update // Update cart count in header
setTimeout(() => { updateCartCount();
calculateCartTotals();
}, 500);
}
// Update cart totals when cart items are deleted // Optionally reload the page to update cart summary
const originalDeleteCartItem = deleteCartItem; // window.location.reload();
window.deleteCartItem = async function(event, form, cartId) {
await originalDeleteCartItem(event, form, cartId);
// Recalculate totals after successful deletion
setTimeout(() => {
calculateCartTotals();
}, 500);
}
// Function to clear entire cart
function clearCart() {
// Show Bootstrap modal instead of native confirm
showModal();
}
// Function to show the clear cart modal
function showModal() {
// Try to use Bootstrap Modal if available
if (typeof bootstrap !== 'undefined' && bootstrap.Modal) {
const modal = new bootstrap.Modal(document.getElementById('clearCartModal'));
modal.show();
} else {
// Fallback: manually show the modal
const modalElement = document.getElementById('clearCartModal');
if (modalElement) {
modalElement.style.display = 'block';
modalElement.classList.add('show');
modalElement.setAttribute('aria-modal', 'true');
modalElement.setAttribute('role', 'dialog');
// Add backdrop
const backdrop = document.createElement('div');
backdrop.className = 'modal-backdrop fade show';
backdrop.id = 'clearCartBackdrop';
document.body.appendChild(backdrop);
// Handle close buttons
const closeButtons = modalElement.querySelectorAll('[data-bs-dismiss="modal"]');
closeButtons.forEach(button => {
button.onclick = hideModal;
});
// Handle backdrop click
backdrop.onclick = hideModal;
}
} }
} else {
console.error('Error deleting cart item:', await response.json());
} }
} catch (error) {
console.error('Network error:', error);
}
}
// Function to hide the clear cart modal // Function to update cart count in header
function hideModal() { async function updateCartCount() {
const modalElement = document.getElementById('clearCartModal'); try {
const backdrop = document.getElementById('clearCartBackdrop'); const response = await fetch('cart/count', {
method: 'GET',
if (modalElement) { headers: {
modalElement.style.display = 'none'; 'Content-Type': 'application/json',
modalElement.classList.remove('show'); 'Accept': 'application/json'
modalElement.removeAttribute('aria-modal');
modalElement.removeAttribute('role');
} }
});
if (backdrop) { if (response.ok) {
backdrop.remove(); const result = await response.json();
} const cartCountElements = document.querySelectorAll('.cart-count');
cartCountElements.forEach(element => {
element.textContent = result.count;
});
} }
} catch (error) {
// Function to actually clear the cart (called when confirm button is clicked) console.error('Error updating cart count:', error);
async function executeClearCart() { }
try { }
const response = await fetch('cart/clear', { </script>
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute(
'content'),
'Accept': 'application/json'
}
});
if (response.ok) {
const result = await response.json();
if (result.success) {
// Hide modal first
hideModal();
// Remove all cart rows from the table
const cartRows = document.querySelectorAll('tbody tr[data-cart-id]');
cartRows.forEach(row => row.remove());
// Update cart count in header
updateCartCount();
// Recalculate totals (should be zero now)
calculateCartTotals();
// Optionally show a success message
console.log('Cart cleared successfully');
}
} else {
console.error('Error clearing cart:', await response.json());
}
} catch (error) {
console.error('Network error:', error);
}
}
</script>
@endsection @endsection

View File

@ -134,8 +134,7 @@
<div class="offcanvas offcanvas-top" id="searchBox" data-bs-backdrop="static" tabindex="-1"> <div class="offcanvas offcanvas-top" id="searchBox" data-bs-backdrop="static" tabindex="-1">
<div class="offcanvas-header border-bottom p-0 py-lg-1"> <div class="offcanvas-header border-bottom p-0 py-lg-1">
<form class="container d-flex align-items-center"> <form class="container d-flex align-items-center">
<input type="search" id="searchInput" <input type="search" id="searchInput" class="form-control form-control-lg fs-lg border-0 rounded-0 py-3 ps-0"
class="form-control form-control-lg fs-lg border-0 rounded-0 py-3 ps-0"
placeholder="Search the products" data-autofocus="offcanvas"> placeholder="Search the products" data-autofocus="offcanvas">
<button type="reset" class="btn-close fs-lg" data-bs-dismiss="offcanvas" <button type="reset" class="btn-close fs-lg" data-bs-dismiss="offcanvas"
aria-label="Close"></button> aria-label="Close"></button>
@ -249,7 +248,7 @@
</a> </a>
<!-- Cart button --> <!-- Cart button -->
{{-- <button type="button" <button type="button"
class="btn btn-icon btn-lg fs-xl btn-outline-secondary position-relative border-0 rounded-circle animate-scale" class="btn btn-icon btn-lg fs-xl btn-outline-secondary position-relative border-0 rounded-circle animate-scale"
data-bs-toggle="offcanvas" data-bs-target="#shoppingCart" aria-controls="shoppingCart" data-bs-toggle="offcanvas" data-bs-target="#shoppingCart" aria-controls="shoppingCart"
aria-label="Shopping cart"> aria-label="Shopping cart">
@ -257,17 +256,7 @@
class="position-absolute top-0 start-100 badge fs-xs text-bg-primary rounded-pill mt-1 ms-n4 z-2 cart-count" class="position-absolute top-0 start-100 badge fs-xs text-bg-primary rounded-pill mt-1 ms-n4 z-2 cart-count"
style="--cz-badge-padding-y: .25em; --cz-badge-padding-x: .42em">{{ auth()->check() ? \App\Repositories\Member\Cart\MemberCartRepository::getCount() : 0 }}</span> style="--cz-badge-padding-y: .25em; --cz-badge-padding-x: .42em">{{ auth()->check() ? \App\Repositories\Member\Cart\MemberCartRepository::getCount() : 0 }}</span>
<i class="ci-shopping-bag animate-target me-1"></i> <i class="ci-shopping-bag animate-target me-1"></i>
</button> --}} </button>
<a type="button" href="{{ route('cart.index') }}"
class="btn btn-icon btn-lg fs-xl btn-outline-secondary position-relative border-0 rounded-circle animate-scale"
aria-label="Shopping cart">
@if (auth()->check() && \App\Repositories\Member\Cart\MemberCartRepository::getCount() > 0)
<span
class="position-absolute top-0 start-100 badge fs-xs text-bg-primary rounded-pill mt-1 ms-n4 z-2 cart-count"
style="--cz-badge-padding-y: .25em; --cz-badge-padding-x: .42em">{{ auth()->check() ? \App\Repositories\Member\Cart\MemberCartRepository::getCount() : 0 }}</span>
@endif
<i class="ci-shopping-bag animate-target me-1"></i>
</a>
</div> </div>
</div> </div>
@ -328,39 +317,39 @@
</div> </div>
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
const searchInput = document.getElementById('searchInput'); const searchInput = document.getElementById('searchInput');
const searchResults = document.getElementById('searchResults'); const searchResults = document.getElementById('searchResults');
let searchTimeout; let searchTimeout;
if (searchInput && searchResults) { if (searchInput && searchResults) {
// Show default state initially // Show default state initially
showDefaultSearch(); showDefaultSearch();
// Handle search input with debouncing // Handle search input with debouncing
searchInput.addEventListener('input', function() { searchInput.addEventListener('input', function() {
clearTimeout(searchTimeout); clearTimeout(searchTimeout);
const query = this.value.trim(); const query = this.value.trim();
if (query.length < 2) { if (query.length < 2) {
showDefaultSearch();
return;
}
// Debounce search
searchTimeout = setTimeout(() => {
performSearch(query);
}, 300);
});
// Clear search when input is cleared
searchInput.addEventListener('reset', function() {
showDefaultSearch(); showDefaultSearch();
}); return;
} }
function showDefaultSearch() { // Debounce search
searchResults.innerHTML = ` searchTimeout = setTimeout(() => {
performSearch(query);
}, 300);
});
// Clear search when input is cleared
searchInput.addEventListener('reset', function() {
showDefaultSearch();
});
}
function showDefaultSearch() {
searchResults.innerHTML = `
<div class="container text-center py-5"> <div class="container text-center py-5">
<svg class="text-body-tertiary opacity-60 mb-4" xmlns="http://www.w3.org/2000/svg" width="60" <svg class="text-body-tertiary opacity-60 mb-4" xmlns="http://www.w3.org/2000/svg" width="60"
viewBox="0 0 512 512" fill="currentColor"> viewBox="0 0 512 512" fill="currentColor">
@ -370,11 +359,11 @@
<p class="fs-sm mb-0">Start typing in the search field above to see instant search results.</p> <p class="fs-sm mb-0">Start typing in the search field above to see instant search results.</p>
</div> </div>
`; `;
} }
function performSearch(query) { function performSearch(query) {
// Show loading state // Show loading state
searchResults.innerHTML = ` searchResults.innerHTML = `
<div class="container text-center py-5"> <div class="container text-center py-5">
<div class="spinner-border text-primary" role="status"> <div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span> <span class="visually-hidden">Loading...</span>
@ -383,39 +372,39 @@
</div> </div>
`; `;
fetch(`{{ route('search.ajax') }}?q=${encodeURIComponent(query)}`, { fetch(`{{ route('search.ajax') }}?q=${encodeURIComponent(query)}`, {
headers: { headers: {
'X-Requested-With': 'XMLHttpRequest', 'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json' 'Accept': 'application/json'
} }
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
displaySearchResults(data.results, data.query); displaySearchResults(data.results, data.query);
} else { } else {
showError('Search failed. Please try again.'); showError('Search failed. Please try again.');
} }
}) })
.catch(error => { .catch(error => {
console.error('Search error:', error); console.error('Search error:', error);
showError('Search failed. Please try again.'); showError('Search failed. Please try again.');
}); });
} }
function displaySearchResults(results, query) { function displaySearchResults(results, query) {
let html = '<div class="container py-4">'; let html = '<div class="container py-4">';
// Products section // Products section
if (results.products && results.products.length > 0) { if (results.products && results.products.length > 0) {
html += ` html += `
<div class="mb-4"> <div class="mb-4">
<h6 class="text-uppercase fs-xs fw-bold mb-3">Products</h6> <h6 class="text-uppercase fs-xs fw-bold mb-3">Products</h6>
<div class="row g-3"> <div class="row g-3">
`; `;
results.products.forEach(product => { results.products.forEach(product => {
html += ` html += `
<div class="col-6 col-md-3"> <div class="col-6 col-md-3">
<a href="${product.route}" class="text-decoration-none"> <a href="${product.route}" class="text-decoration-none">
<div class="card border-0 shadow-sm"> <div class="card border-0 shadow-sm">
@ -427,66 +416,66 @@
</a> </a>
</div> </div>
`; `;
}); });
html += '</div></div>'; html += '</div></div>';
} }
// Categories section // Categories section
if (results.categories && results.categories.length > 0) { if (results.categories && results.categories.length > 0) {
html += ` html += `
<div class="mb-4"> <div class="mb-4">
<h6 class="text-uppercase fs-xs fw-bold mb-3">Categories</h6> <h6 class="text-uppercase fs-xs fw-bold mb-3">Categories</h6>
<div class="d-flex flex-wrap gap-2"> <div class="d-flex flex-wrap gap-2">
`; `;
results.categories.forEach(category => { results.categories.forEach(category => {
html += ` html += `
<a href="${category.route}" <a href="${category.route}"
class="btn btn-outline-secondary btn-sm"> class="btn btn-outline-secondary btn-sm">
${category.name} ${category.name}
</a> </a>
`; `;
}); });
html += '</div></div>'; html += '</div></div>';
} }
// Genders section // Genders section
if (results.genders && results.genders.length > 0) { if (results.genders && results.genders.length > 0) {
html += ` html += `
<div class="mb-4"> <div class="mb-4">
<h6 class="text-uppercase fs-xs fw-bold mb-3">Genders</h6> <h6 class="text-uppercase fs-xs fw-bold mb-3">Genders</h6>
<div class="d-flex flex-wrap gap-2"> <div class="d-flex flex-wrap gap-2">
`; `;
results.genders.forEach(gender => { results.genders.forEach(gender => {
html += ` html += `
<a href="${gender.route}" <a href="${gender.route}"
class="btn btn-outline-secondary btn-sm"> class="btn btn-outline-secondary btn-sm">
${gender.name} ${gender.name}
</a> </a>
`; `;
}); });
html += '</div></div>'; html += '</div></div>';
} }
// No results // No results
if (Object.keys(results).length === 0) { if (Object.keys(results).length === 0) {
html += ` html += `
<div class="text-center py-5"> <div class="text-center py-5">
<p class="text-muted mb-0">No results found for "${query}"</p> <p class="text-muted mb-0">No results found for "${query}"</p>
</div> </div>
`; `;
}
html += '</div>';
searchResults.innerHTML = html;
} }
function showError(message) { html += '</div>';
searchResults.innerHTML = ` searchResults.innerHTML = html;
}
function showError(message) {
searchResults.innerHTML = `
<div class="container text-center py-5"> <div class="container text-center py-5">
<div class="text-danger mb-3"> <div class="text-danger mb-3">
<i class="ci-alert-circle fs-1"></i> <i class="ci-alert-circle fs-1"></i>
@ -494,6 +483,6 @@
<p class="text-muted mb-0">${message}</p> <p class="text-muted mb-0">${message}</p>
</div> </div>
`; `;
} }
}); });
</script> </script>

View File

@ -28,33 +28,6 @@
</button> </button>
</div> </div>
<!-- Login Dialog Modal -->
<div class="modal fade" id="loginDialogModal" tabindex="-1" aria-labelledby="loginDialogLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header border-0">
<h5 class="modal-title" id="loginDialogLabel">Login Required</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body text-center py-4">
<div class="mb-4">
<i class="ci-user-circle text-primary" style="font-size: 3rem;"></i>
</div>
<h6 class="mb-3">Please login to add items to cart</h6>
<p class="text-muted mb-4">You need to be logged in to add items to your shopping cart and proceed with checkout.</p>
<div class="d-grid gap-2">
<a href="{{ route('login') }}" class="btn btn-primary">
<i class="ci-login me-2"></i>Login to Continue
</a>
<a href="{{ route('register') }}" class="btn btn-outline-primary">
<i class="ci-user-plus me-2"></i>Create Account
</a>
</div>
</div>
</div>
</div>
</div>
<script> <script>
function incrementQuantity(button) { function incrementQuantity(button) {
const input = button.parentElement.querySelector('input[type="number"]'); const input = button.parentElement.querySelector('input[type="number"]');
@ -128,12 +101,7 @@
} else { } else {
const error = await response.json(); const error = await response.json();
console.error('Error adding to cart:', error); console.error('Error adding to cart:', error);
// Check if error is due to unauthenticated user // You could show an error message here
if (error.message && error.message.toLowerCase().includes('unauthenticated') || response.status === 401) {
showLoginDialog();
} else {
// You could show an error message here
}
} }
} catch (error) { } catch (error) {
console.error('Network error:', error); console.error('Network error:', error);
@ -168,54 +136,4 @@
console.error('Error updating cart count:', error); console.error('Error updating cart count:', error);
} }
} }
// Function to show login dialog
function showLoginDialog() {
// Try to use Bootstrap Modal if available
if (typeof bootstrap !== 'undefined' && bootstrap.Modal) {
const modal = new bootstrap.Modal(document.getElementById('loginDialogModal'));
modal.show();
} else {
// Fallback: manually show the modal
const modalElement = document.getElementById('loginDialogModal');
if (modalElement) {
modalElement.style.display = 'block';
modalElement.classList.add('show');
modalElement.setAttribute('aria-modal', 'true');
modalElement.setAttribute('role', 'dialog');
// Add backdrop
const backdrop = document.createElement('div');
backdrop.className = 'modal-backdrop fade show';
backdrop.id = 'loginDialogBackdrop';
document.body.appendChild(backdrop);
// Handle close buttons
const closeButtons = modalElement.querySelectorAll('[data-bs-dismiss="modal"]');
closeButtons.forEach(button => {
button.onclick = hideLoginDialog;
});
// Handle backdrop click
backdrop.onclick = hideLoginDialog;
}
}
}
// Function to hide login dialog
function hideLoginDialog() {
const modalElement = document.getElementById('loginDialogModal');
const backdrop = document.getElementById('loginDialogBackdrop');
if (modalElement) {
modalElement.style.display = 'none';
modalElement.classList.remove('show');
modalElement.removeAttribute('aria-modal');
modalElement.removeAttribute('role');
}
if (backdrop) {
backdrop.remove();
}
}
</script> </script>

View File

@ -91,7 +91,6 @@ Route::middleware(['auth'])->prefix('/cart')->group(function () {
Route::get('/', [CartController::class, 'index'])->name('cart.index'); Route::get('/', [CartController::class, 'index'])->name('cart.index');
Route::post('/', [CartController::class, 'add'])->name('cart.add'); Route::post('/', [CartController::class, 'add'])->name('cart.add');
Route::put('/{id}', [CartController::class, 'update'])->name('cart.update'); Route::put('/{id}', [CartController::class, 'update'])->name('cart.update');
Route::delete('/clear', [CartController::class, 'clear'])->name('cart.clear');
Route::delete('/{id}', [CartController::class, 'delete'])->name('cart.delete'); Route::delete('/{id}', [CartController::class, 'delete'])->name('cart.delete');
Route::get('/count', [CartController::class, 'count'])->name('cart.count'); Route::get('/count', [CartController::class, 'count'])->name('cart.count');
}); });