<!DOCTYPE html>
<html lang="{{ app.session.get('_locale') ?? 'en' }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Cart โ Julico</title>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/julico-home.css">
<style>
body{background:var(--gray-50);min-height:100vh}
.cart-nav{background:var(--white);border-bottom:1px solid var(--gray-200);padding:0 24px;height:68px;display:flex;align-items:center;justify-content:space-between;box-shadow:var(--shadow-sm);position:sticky;top:0;z-index:100}
.cart-body{max-width:1280px;margin:0 auto;padding:32px 24px}
.cart-title{font-size:28px;font-weight:800;color:var(--gray-900);letter-spacing:-0.02em;margin-bottom:6px}
.cart-subtitle{font-size:14px;color:var(--gray-400);font-weight:500;margin-bottom:28px}
.cart-layout{display:grid;grid-template-columns:1fr 360px;gap:24px;align-items:start}
.cart-items{display:flex;flex-direction:column;gap:14px}
.cart-empty{background:var(--white);border:1.5px solid var(--gray-100);border-radius:var(--rlg);padding:60px 24px;text-align:center;box-shadow:var(--shadow-sm)}
.cart-empty-icon{font-size:64px;margin-bottom:16px}
.cart-empty-title{font-size:20px;font-weight:800;color:var(--gray-900);margin-bottom:8px}
.cart-empty-sub{font-size:14px;color:var(--gray-400);margin-bottom:24px}
.cart-item{background:var(--white);border:1.5px solid var(--gray-100);border-radius:var(--rlg);padding:20px;display:flex;align-items:center;gap:16px;box-shadow:var(--shadow-sm);transition:box-shadow .2s}
.cart-item:hover{box-shadow:var(--shadow-md)}
.cart-item-img{width:80px;height:80px;background:var(--gray-50);border-radius:var(--rsm);overflow:hidden;flex-shrink:0;display:flex;align-items:center;justify-content:center}
.cart-item-img img{width:100%;height:100%;object-fit:contain}
.cart-item-info{flex:1}
.cart-item-cat{font-size:10px;font-weight:700;color:var(--brand);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:3px}
.cart-item-name{font-size:15px;font-weight:700;color:var(--gray-900);margin-bottom:4px}
.cart-item-variant{font-size:12px;color:var(--gray-400);font-weight:500}
.cart-item-right{display:flex;flex-direction:column;align-items:flex-end;gap:10px;flex-shrink:0}
.cart-item-price{font-size:20px;font-weight:800;color:var(--gray-900);letter-spacing:-0.02em}
.cart-item-price sup{font-size:12px;font-weight:700;vertical-align:super}
.cart-item-subtotal{font-size:11px;color:var(--gray-400);font-weight:500}
.cart-qty{display:flex;align-items:center;border:1.5px solid var(--gray-200);border-radius:var(--rsm);overflow:hidden}
.qty-btn{width:32px;height:32px;background:var(--white);border:none;color:var(--gray-700);font-size:16px;font-weight:600;cursor:pointer;transition:background .2s;font-family:var(--font);display:flex;align-items:center;justify-content:center;touch-action:manipulation;}
.qty-btn:hover{background:var(--brand-light);color:var(--brand)}
.qty-btn:disabled{opacity:0.4;cursor:not-allowed}
.qty-num{width:36px;height:32px;border:none;border-left:1px solid var(--gray-200);border-right:1px solid var(--gray-200);text-align:center;font-family:var(--font);font-size:14px;font-weight:700;color:var(--gray-900);background:var(--white);outline:none}
.remove-btn{color:var(--gray-400);cursor:pointer;font-size:13px;font-weight:600;text-decoration:none;display:inline-flex;align-items:center;gap:4px;transition:color .2s;background:none;border:none;font-family:var(--font);padding:0}
.remove-btn:hover{color:#ef4444}
.cart-summary{background:var(--white);border:1.5px solid var(--gray-100);border-radius:var(--rlg);padding:24px;box-shadow:var(--shadow-sm);position:sticky;top:88px}
.summary-title{font-size:18px;font-weight:800;color:var(--gray-900);margin-bottom:20px;padding-bottom:16px;border-bottom:1px solid var(--gray-100)}
.summary-row{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;font-size:14px}
.summary-row .label{color:var(--gray-500);font-weight:500}
.summary-row .value{font-weight:700;color:var(--gray-800)}
.summary-divider{border:none;border-top:1px solid var(--gray-100);margin:16px 0}
.summary-total{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}
.summary-total .label{font-size:16px;font-weight:700;color:var(--gray-900)}
.summary-total .value{font-size:22px;font-weight:800;color:var(--brand)}
.btn-checkout{width:100%;height:50px;background:var(--brand);color:white;border:none;border-radius:var(--rsm);font-family:var(--font);font-size:15px;font-weight:700;cursor:pointer;transition:background .2s;margin-bottom:12px;display:flex;align-items:center;justify-content:center;gap:8px;text-decoration:none}
.btn-checkout:hover{background:var(--brand-dark)}
.btn-continue{width:100%;height:44px;background:var(--white);color:var(--gray-700);border:1.5px solid var(--gray-200);border-radius:var(--rsm);font-family:var(--font);font-size:14px;font-weight:600;cursor:pointer;transition:all .2s;text-decoration:none;display:flex;align-items:center;justify-content:center}
.btn-continue:hover{border-color:var(--brand);color:var(--brand);background:var(--brand-light)}
.trust-chips{display:flex;gap:12px;flex-wrap:wrap;margin-top:16px;padding-top:16px;border-top:1px solid var(--gray-100)}
.trust-chip{display:flex;align-items:center;gap:5px;font-size:11px;font-weight:600;color:var(--gray-500)}
.updating{opacity:0.5;pointer-events:none}
@media(max-width:768px){.cart-layout{grid-template-columns:1fr}.cart-item{flex-wrap:wrap}}
</style>
</head>
<body>
<nav class="cart-nav">
<a href="{{ path('app_home') }}">
<img src="/images/julico-logo.png" alt="Julico" style="height:48px;object-fit:contain">
</a>
<div style="font-size:18px;font-weight:800;color:var(--gray-900)">๐ My Cart</div>
<a href="{{ path('app_home') }}" style="font-size:13px;font-weight:600;color:var(--gray-500);text-decoration:none">โ Continue Shopping</a>
</nav>
<div class="cart-body">
<div class="cart-title">Shopping Cart</div>
<div class="cart-subtitle" id="cart-subtitle">
{% if items|length > 0 %}
{{ items|length }} item{% if items|length > 1 %}s{% endif %} in your cart
{% else %}
Your cart is empty
{% endif %}
</div>
<div class="cart-layout">
<div class="cart-items">
{% if items|length > 0 %}
{% for item in items %}
{% set unitPrice = item.product.prix %}
{% if item.product.salePrice is defined and item.product.salePrice is not null and item.product.salePrice > 0 %}
{% set unitPrice = item.product.salePrice %}
{% endif %}
{% set variantId = 'notdefined' %}
<div class="cart-item" id="cart-item-{{ item.product.id }}"
data-id="{{ item.product.id }}"
data-variant="{{ variantId }}"
data-price="{{ unitPrice }}"
data-qty="{{ item.quantity }}"
data-csrf="{{ csrf_token('authenticate') }}"
data-add-url="{{ path('panier_add', {'id': item.product.id, 'idVariante': variantId, 'option': 'continue'}) }}"
data-minus-url="{{ path('panier_minus', {'id': item.product.id, 'idVariante': variantId, 'option': 'continue'}) }}"
data-remove-url="{{ path('panier_remove', {'id': item.product.id, 'idVariante': variantId}) }}">
{# Image โ fixed path #}
<div class="cart-item-img">
{% if item.product.images|length > 0 %}
<img src="{{ asset('assets/uploads/products/' ~ item.product.images[0].fileName) }}"
alt="{{ item.product.productName }}"
onerror="this.onerror=null;this.parentNode.innerHTML='<span style=font-size:32px;opacity:0.3>๐ฆ</span>'">
{% else %}
<span style="font-size:32px;opacity:0.3">๐ฆ</span>
{% endif %}
</div>
<div class="cart-item-info">
<div class="cart-item-cat">
{% if item.product.category is defined and item.product.category %}{{ item.product.category.nom }}{% endif %}
{% if item.product.magasin is defined and item.product.magasin %} ยท {{ item.product.magasin.nom }}{% endif %}
</div>
<a href="{{ path('show_produit', {'id': item.product.id}) }}" style="text-decoration:none">
<div class="cart-item-name">{{ item.product.productName }}</div>
</a>
{% if item.productVariant is defined and item.productVariant %}
<div class="cart-item-variant">
{% if item.productVariant.paramColor is defined and item.productVariant.paramColor %}Color: {{ item.productVariant.paramColor.colorName }}{% endif %}
{% if item.productVariant.paramSize is defined and item.productVariant.paramSize %} ยท Size: {{ item.productVariant.paramSize.sizeAbreviation }}{% endif %}
</div>
{% endif %}
</div>
<div class="cart-item-right">
<div class="cart-item-price"><sup>$</sup>{{ unitPrice|number_format(2) }}</div>
<div class="cart-item-subtotal" id="subtotal-{{ item.product.id }}">
ร {{ item.quantity }} = ${{ (unitPrice * item.quantity)|number_format(2) }}
</div>
{# Qty controls โ AJAX, no page reload #}
<div class="cart-qty">
<button type="button" class="qty-btn btn-minus" data-action="minus" title="Remove one">โ</button>
<input type="number" class="qty-num" value="{{ item.quantity }}" readonly>
<button type="button" class="qty-btn btn-plus" data-action="plus" title="Add one">+</button>
</div>
<button type="button" class="remove-btn btn-remove">๐ Remove</button>
</div>
</div>
{% endfor %}
{% else %}
<div class="cart-empty">
<div class="cart-empty-icon">๐</div>
<div class="cart-empty-title">Your cart is empty</div>
<div class="cart-empty-sub">Looks like you haven't added anything yet.</div>
<a href="{{ path('app_home') }}" class="btn-checkout" style="max-width:220px;margin:0 auto">Browse Products โ</a>
</div>
{% endif %}
</div>
{% if items|length > 0 %}
<div class="cart-summary">
<div class="summary-title">Order Summary</div>
<div class="summary-row">
<span class="label">Subtotal</span>
<span class="value" id="summary-subtotal">${{ (total - frais)|number_format(2) }}</span>
</div>
<div class="summary-row">
<span class="label">Delivery</span>
<span class="value" style="color:var(--green)">
{% if frais == 0 %}Free{% else %}${{ frais|number_format(2) }}{% endif %}
</span>
</div>
<hr class="summary-divider">
<div class="summary-total">
<span class="label">Total</span>
<span class="value" id="summary-total">${{ total|number_format(2) }}</span>
</div>
{% if app.user %}
<form action="{{ path('panier_validationcmd', {'option': 'goprofile', 'addresseid': 'notdefined', 'cmdid': 'notdefined'}) }}" method="POST">
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
<button type="submit" class="btn-checkout">Proceed to Checkout โ</button>
</form>
{% else %}
<a href="/login" class="btn-checkout">Sign In to Checkout โ</a>
{% endif %}
<a href="{{ path('app_home') }}" class="btn-continue">โ Continue Shopping</a>
<div class="trust-chips">
<div class="trust-chip">๐ Secure checkout</div>
<div class="trust-chip">๐ Fast delivery</div>
<div class="trust-chip">โฉ๏ธ Easy returns</div>
</div>
</div>
{% endif %}
</div>
</div>
<script>
// โโ Cart item AJAX handlers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
var frais = {{ frais }};
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.cart-item').forEach(function(itemEl) {
var id = itemEl.dataset.id;
var csrf = itemEl.dataset.csrf;
var addUrl = itemEl.dataset.addUrl;
var minusUrl = itemEl.dataset.minusUrl;
var removeUrl = itemEl.dataset.removeUrl;
var price = parseFloat(itemEl.dataset.price);
var qtyInput = itemEl.querySelector('.qty-num');
var subtotal = document.getElementById('subtotal-' + id);
var plusBtn = itemEl.querySelector('.btn-plus');
var minusBtn = itemEl.querySelector('.btn-minus');
var removeBtn = itemEl.querySelector('.btn-remove');
// โโ + button โโ
plusBtn.addEventListener('click', function() {
ajaxQty(addUrl, id, csrf, price, 1, itemEl, qtyInput, subtotal);
});
// โโ โ button โโ
minusBtn.addEventListener('click', function() {
var qty = parseInt(qtyInput.value);
if (qty <= 1) {
// Remove item
ajaxRemove(removeUrl, id, itemEl);
} else {
ajaxQty(minusUrl, id, csrf, price, -1, itemEl, qtyInput, subtotal);
}
});
// โโ Remove button โโ
removeBtn.addEventListener('click', function() {
ajaxRemove(removeUrl, id, itemEl);
});
});
});
function ajaxQty(url, id, csrf, price, delta, itemEl, qtyInput, subtotalEl) {
itemEl.classList.add('updating');
var formData = new FormData();
formData.append('_csrf_token', csrf);
formData.append('productid', id);
formData.append('qttprdid', '1');
var xhr = new XMLHttpRequest();
xhr.open('POST', url);
xhr.withCredentials = true;
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.setRequestHeader('Accept', 'application/json');
xhr.onload = function() {
itemEl.classList.remove('updating');
if (xhr.status === 200) {
try {
var data = JSON.parse(xhr.responseText);
if (data.success) {
var newQty = parseInt(qtyInput.value) + delta;
qtyInput.value = newQty;
if (subtotalEl) {
var sub = price * newQty;
subtotalEl.textContent = 'ร ' + newQty + ' = $' + sub.toFixed(2);
}
updateSummary(price * delta);
}
} catch(e) {
// JSON parse failed โ don't reload, just ignore
var newQty = parseInt(qtyInput.value) + delta;
qtyInput.value = Math.max(0, newQty);
if (subtotalEl && newQty > 0) {
subtotalEl.textContent = 'ร ' + newQty + ' = $' + (price * newQty).toFixed(2);
}
updateSummary(price * delta);
}
}
};
xhr.onerror = function() {
itemEl.classList.remove('updating');
};
xhr.send(formData);
}
function ajaxRemove(url, id, itemEl) {
itemEl.classList.add('updating');
// Check if this is the last item BEFORE removing
var remaining = document.querySelectorAll('.cart-item');
var isLast = remaining.length === 1;
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.withCredentials = true;
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.setRequestHeader('Accept', 'application/json');
xhr.onload = function() {
if (isLast) {
// Last item โ reload immediately to show empty cart
window.location.reload();
return;
}
// Animate removal
itemEl.style.transition = 'all 0.3s';
itemEl.style.opacity = '0';
itemEl.style.height = itemEl.offsetHeight + 'px';
setTimeout(function() {
itemEl.style.height = '0';
itemEl.style.padding = '0';
itemEl.style.margin = '0';
itemEl.style.overflow = 'hidden';
setTimeout(function() { itemEl.remove(); }, 300);
}, 300);
// Update summary
var price = parseFloat(itemEl.dataset.price);
var qty = parseInt(itemEl.querySelector('.qty-num').value);
updateSummary(-(price * qty));
};
xhr.onerror = function() { window.location.href = url; };
xhr.send();
}
function updateSummary(priceDelta) {
var subtotalEl = document.getElementById('summary-subtotal');
var totalEl = document.getElementById('summary-total');
if (subtotalEl) {
var sub = parseFloat(subtotalEl.textContent.replace('$','').replace(',','')) + priceDelta;
subtotalEl.textContent = '$' + Math.max(0, sub).toFixed(2);
}
if (totalEl) {
var tot = parseFloat(totalEl.textContent.replace('$','').replace(',','')) + priceDelta;
totalEl.textContent = '$' + Math.max(0, tot + frais).toFixed(2);
}
}
</script>
</body>
</html>