/* Cart interactions (CSP compliant) - Parses hydration JSON from #cart-hydration - Binds all cart-related events (increase/decrease qty, remove, clear, currency change) - Handles guest checkout modal buttons - Shows toasts via createToast helpers here */ (function () { 'use strict'; let hydration = { item_count: 0, redirect_login_url: '/login?checkout=true', redirect_register_url: '/register?checkout=true', redirect_after_auth: '/cart' }; function readHydration() { try { const el = document.getElementById('cart-hydration'); if (!el) return; const parsed = JSON.parse(el.textContent || '{}'); hydration = Object.assign(hydration, parsed || {}); } catch (e) { console.warn('Failed to parse cart hydration JSON', e); } } function showLoading() { const overlay = document.getElementById('loadingOverlay'); if (overlay) overlay.classList.remove('d-none'); } function hideLoading() { const overlay = document.getElementById('loadingOverlay'); if (overlay) overlay.classList.add('d-none'); } function createToast(message, type, icon) { const toast = document.createElement('div'); toast.className = `toast align-items-center text-white bg-${type} border-0 position-fixed end-0 m-3`; toast.style.top = '80px'; toast.style.zIndex = '10000'; toast.innerHTML = `
${message}
`; document.body.appendChild(toast); if (window.bootstrap && window.bootstrap.Toast) { const bsToast = new window.bootstrap.Toast(toast); bsToast.show(); toast.addEventListener('hidden.bs.toast', () => toast.remove()); } } function showError(message) { createToast(message, 'danger', 'bi-exclamation-triangle'); } function showSuccess(message) { createToast(message, 'success', 'bi-check-circle'); } async function parseResponse(response) { let json = {}; try { json = await response.json(); } catch (_) {} const payload = (json && (json.data || json)) || {}; const success = (typeof json.success === 'boolean') ? json.success : (typeof payload.success === 'boolean' ? payload.success : response.ok); return { json, payload, success }; } async function updateQuantity(productId, newQuantity) { if (newQuantity < 1) { await removeFromCart(productId); return; } showLoading(); try { await window.apiJson(`/api/cart/item/${encodeURIComponent(productId)}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ quantity: newQuantity }) }); { showSuccess('Quantity updated successfully'); setTimeout(() => window.location.reload(), 500); } } catch (e) { showError(e && e.message ? e.message : 'Network error occurred'); } finally { hideLoading(); } } let productIdToRemove = null; function showClearCartModal() { const el = document.getElementById('clearCartModal'); if (!el || !window.bootstrap) return; const modal = new window.bootstrap.Modal(el); modal.show(); } function showRemoveItemModal(productId) { productIdToRemove = productId; const el = document.getElementById('removeItemModal'); if (!el || !window.bootstrap) return; const modal = new window.bootstrap.Modal(el); modal.show(); } async function confirmRemoveItem() { if (!productIdToRemove) return; const el = document.getElementById('removeItemModal'); if (el && window.bootstrap) { const modal = window.bootstrap.Modal.getInstance(el); if (modal) modal.hide(); } await removeFromCart(productIdToRemove); productIdToRemove = null; } async function removeFromCart(productId) { showLoading(); try { await window.apiJson(`/api/cart/item/${encodeURIComponent(productId)}`, { method: 'DELETE' }); { showSuccess('Item removed from cart'); setTimeout(() => window.location.reload(), 500); } } catch (e) { showError(e && e.message ? e.message : 'Network error occurred'); } finally { hideLoading(); } } async function clearCart() { const clearEl = document.getElementById('clearCartModal'); if (clearEl && window.bootstrap) { const modal = window.bootstrap.Modal.getInstance(clearEl); if (modal) modal.hide(); } showLoading(); try { await window.apiJson('/api/cart', { method: 'DELETE' }); { showSuccess('Cart cleared successfully'); setTimeout(() => window.location.reload(), 500); } } catch (e) { showError(e && e.message ? e.message : 'Network error occurred'); } finally { hideLoading(); } } async function changeCurrency() { const currencySelector = document.getElementById('currencySelector'); if (!currencySelector) return; const selectedCurrency = currencySelector.value; showLoading(); try { await window.apiJson('/api/user/currency', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ currency: selectedCurrency }) }); { showSuccess('Currency updated'); setTimeout(() => window.location.reload(), 500); } } catch (e) { showError(e && e.message ? e.message : 'Network error occurred'); } finally { hideLoading(); } } function saveForLater() { showSuccess('Cart saved for later'); } function shareCart() { const shareData = { title: 'My ThreeFold Cart', text: 'Check out my ThreeFold marketplace cart', url: window.location.href }; if (navigator.share) { navigator.share(shareData).catch(() => {}); return; } if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(window.location.href) .then(() => showSuccess('Cart link copied to clipboard')) .catch(() => {}); } } // Refresh cart contents after small delay (used by recommended add) function refreshCartContents() { setTimeout(() => { window.location.reload(); }, 500); } // Initialize recommended products add-to-cart buttons function initializeRecommendedProducts() { document.querySelectorAll('.add-recommended-btn').forEach(button => { button.addEventListener('click', function () { const productId = this.getAttribute('data-product-id'); const productName = this.getAttribute('data-product-name') || 'Product'; const productCategory = this.getAttribute('data-product-category') || ''; addRecommendedToCart(productId, productName, productCategory, this); }); }); } async function addRecommendedToCart(productId, productName, productCategory, buttonEl) { if (!productId || !buttonEl) return; const original = buttonEl.innerHTML; buttonEl.disabled = true; buttonEl.innerHTML = 'Adding...'; try { await window.apiJson('/api/cart/add', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ product_id: productId, quantity: 1, specifications: {} }) }); buttonEl.innerHTML = 'Added!'; buttonEl.classList.remove('btn-primary', 'btn-success', 'btn-info', 'btn-warning'); buttonEl.classList.add('btn-success'); createToast(`${productName} added to cart!`, 'success', 'bi-check-circle'); if (typeof window.updateCartCount === 'function') window.updateCartCount(); try { if (typeof window.emitCartUpdated === 'function') window.emitCartUpdated(undefined); } catch (_) {} updateMyOrdersVisibility(); refreshCartContents(); setTimeout(() => { buttonEl.innerHTML = original; buttonEl.disabled = false; buttonEl.classList.remove('btn-success'); if (productCategory === 'compute') buttonEl.classList.add('btn-primary'); else if (productCategory === 'storage') buttonEl.classList.add('btn-success'); else if (productCategory === 'gateways') buttonEl.classList.add('btn-info'); else if (productCategory === 'applications') buttonEl.classList.add('btn-warning'); else buttonEl.classList.add('btn-primary'); }, 2000); } catch (e) { console.error('Error adding recommended product:', e); if (e && e.status === 402) { return; } buttonEl.innerHTML = 'Error'; buttonEl.classList.add('btn-danger'); createToast(`Failed to add ${productName} to cart. Please try again.`, 'danger', 'bi-exclamation-triangle'); setTimeout(() => { buttonEl.innerHTML = original; buttonEl.disabled = false; buttonEl.classList.remove('btn-danger'); if (productCategory === 'compute') buttonEl.classList.add('btn-primary'); else if (productCategory === 'storage') buttonEl.classList.add('btn-success'); else if (productCategory === 'gateways') buttonEl.classList.add('btn-info'); else if (productCategory === 'applications') buttonEl.classList.add('btn-warning'); else buttonEl.classList.add('btn-primary'); }, 3000); } } // Toggle visibility of My Orders link based on API async function updateMyOrdersVisibility() { const myOrdersLink = document.getElementById('myOrdersLink'); if (!myOrdersLink) return; try { const data = await window.apiJson('/api/orders'); const hasOrders = !!(data && Array.isArray(data.orders) && data.orders.length > 0); myOrdersLink.style.display = hasOrders ? 'inline-block' : 'none'; } catch (e) { myOrdersLink.style.display = 'none'; } } function bindEvents() { // Quantity controls document.querySelectorAll('[data-action="increase"], [data-action="decrease"]').forEach(button => { button.addEventListener('click', function () { const productId = this.getAttribute('data-product-id'); const action = this.getAttribute('data-action'); const qtySpan = this.parentElement.querySelector('span'); const currentQuantity = parseInt(qtySpan && qtySpan.textContent, 10) || 0; if (action === 'increase') updateQuantity(productId, currentQuantity + 1); else if (action === 'decrease') updateQuantity(productId, currentQuantity - 1); }); }); // Remove buttons document.querySelectorAll('[data-action="remove"]').forEach(button => { button.addEventListener('click', function () { const productId = this.getAttribute('data-product-id'); showRemoveItemModal(productId); }); }); // Clear cart const clearCartBtn = document.getElementById('clearCartBtn'); if (clearCartBtn) clearCartBtn.addEventListener('click', showClearCartModal); const confirmClearCartBtn = document.getElementById('confirmClearCartBtn'); if (confirmClearCartBtn) confirmClearCartBtn.addEventListener('click', clearCart); const confirmRemoveItemBtn = document.getElementById('confirmRemoveItemBtn'); if (confirmRemoveItemBtn) confirmRemoveItemBtn.addEventListener('click', confirmRemoveItem); // Currency selector const currencySelector = document.getElementById('currencySelector'); if (currencySelector) currencySelector.addEventListener('change', changeCurrency); // Extra actions document.querySelectorAll('[data-action="save-for-later"]').forEach(btn => btn.addEventListener('click', saveForLater)); document.querySelectorAll('[data-action="share-cart"]').forEach(btn => btn.addEventListener('click', shareCart)); // Guest checkout modal buttons const loginBtn = document.getElementById('guestLoginBtn'); const registerBtn = document.getElementById('guestRegisterBtn'); if (loginBtn) { loginBtn.addEventListener('click', function () { try { sessionStorage.setItem('redirectAfterLogin', hydration.redirect_after_auth || '/cart'); } catch (_) {} window.location.href = hydration.redirect_login_url || '/login?checkout=true'; }); } if (registerBtn) { registerBtn.addEventListener('click', function () { try { sessionStorage.setItem('redirectAfterRegister', hydration.redirect_after_auth || '/cart'); } catch (_) {} window.location.href = hydration.redirect_register_url || '/register?checkout=true'; }); } // Checkout CTA (guest) fallback: open modal if button present const checkoutBtn = document.getElementById('checkoutBtn'); if (checkoutBtn) { checkoutBtn.addEventListener('click', function () { const el = document.getElementById('guestCheckoutModal'); if (el && window.bootstrap) { const modal = new window.bootstrap.Modal(el); modal.show(); } }); } } document.addEventListener('DOMContentLoaded', function () { readHydration(); bindEvents(); initializeRecommendedProducts(); updateMyOrdersVisibility(); }); })();