UNPKG

@mayurgodhani/ecomtools-cli

Version:

E-commerce tools MCP server for Shopify development

694 lines (596 loc) 20.3 kB
Title: Shopify Product Quick View Description: A lightweight product quick view modal that displays product details without leaving the current page. Code: <!-- Product Quick View Modal HTML --> <div id="quick-view-modal" class="quick-view-modal"> <div class="quick-view-container"> <div class="quick-view-close">&times;</div> <div class="quick-view-content"> <div class="quick-view-loader"> <div class="spinner"></div> </div> <div class="quick-view-product-content" id="quick-view-product-content"></div> </div> </div> </div> <!-- Product Quick View Modal Content Template --> <template id="quick-view-template"> <div class="quick-view-product"> <div class="quick-view-product-gallery"> <div class="quick-view-product-image"> <img src="" alt="" class="quick-view-featured-image"> </div> <div class="quick-view-thumbnails"></div> </div> <div class="quick-view-product-details"> <h2 class="quick-view-product-title"></h2> <div class="quick-view-product-price"></div> <div class="quick-view-product-description"></div> <form action="/cart/add" method="post" class="quick-view-product-form"> <input type="hidden" name="id" value="" class="quick-view-variant-id"> <div class="quick-view-product-options"></div> <div class="quick-view-quantity-wrapper"> <label for="quick-view-quantity">Quantity</label> <div class="quick-view-quantity-input"> <button type="button" class="quick-view-quantity-decrease">-</button> <input type="number" id="quick-view-quantity" name="quantity" value="1" min="1"> <button type="button" class="quick-view-quantity-increase">+</button> </div> </div> <div class="quick-view-submit"> <button type="submit" class="quick-view-add-to-cart">Add to Cart</button> </div> <div class="quick-view-view-details"> <a href="" class="quick-view-product-link">View Full Details</a> </div> </form> </div> </div> </template> <!-- Product Quick View CSS --> <style> .quick-view-modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 1000; overflow-y: auto; } .quick-view-container { position: relative; width: 90%; max-width: 1000px; margin: 50px auto; background-color: #fff; border-radius: 4px; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); } .quick-view-close { position: absolute; top: 10px; right: 15px; font-size: 28px; font-weight: 700; color: #333; cursor: pointer; z-index: 10; } .quick-view-content { padding: 20px; position: relative; min-height: 200px; } .quick-view-loader { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; background-color: rgba(255, 255, 255, 0.9); z-index: 5; } .spinner { width: 40px; height: 40px; border: 4px solid #f3f3f3; border-top: 4px solid #333; border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .quick-view-product { display: flex; flex-wrap: wrap; } .quick-view-product-gallery { width: 50%; padding-right: 20px; } .quick-view-product-details { width: 50%; } .quick-view-product-image { margin-bottom: 15px; } .quick-view-featured-image { width: 100%; height: auto; } .quick-view-thumbnails { display: flex; flex-wrap: wrap; gap: 10px; } .quick-view-thumbnail { width: 60px; height: 60px; border: 1px solid #ddd; cursor: pointer; overflow: hidden; } .quick-view-thumbnail.active { border-color: #000; } .quick-view-thumbnail img { width: 100%; height: 100%; object-fit: cover; } .quick-view-product-title { margin-top: 0; margin-bottom: 10px; font-size: 24px; } .quick-view-product-price { font-size: 18px; font-weight: 600; margin-bottom: 15px; } .quick-view-product-description { margin-bottom: 20px; font-size: 14px; line-height: 1.6; } .quick-view-product-options { margin-bottom: 20px; } .quick-view-option-selector { margin-bottom: 15px; } .quick-view-option-selector label { display: block; margin-bottom: 5px; font-weight: 500; } .quick-view-option-selector select { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; } .quick-view-quantity-wrapper { margin-bottom: 20px; } .quick-view-quantity-wrapper label { display: block; margin-bottom: 5px; font-weight: 500; } .quick-view-quantity-input { display: flex; align-items: center; width: fit-content; border: 1px solid #ddd; border-radius: 4px; overflow: hidden; } .quick-view-quantity-input button { width: 30px; height: 38px; background: none; border: none; cursor: pointer; font-size: 16px; } .quick-view-quantity-input input { width: 50px; height: 38px; border: none; border-left: 1px solid #ddd; border-right: 1px solid #ddd; text-align: center; -moz-appearance: textfield; } .quick-view-quantity-input input::-webkit-outer-spin-button, .quick-view-quantity-input input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } .quick-view-add-to-cart { width: 100%; padding: 12px; background-color: #000; color: #fff; border: none; border-radius: 4px; font-size: 16px; font-weight: 500; cursor: pointer; transition: background-color 0.3s; } .quick-view-add-to-cart:hover { background-color: #333; } .quick-view-add-to-cart.disabled { background-color: #ccc; cursor: not-allowed; } .quick-view-view-details { margin-top: 15px; text-align: center; } .quick-view-product-link { color: #333; text-decoration: underline; } @media (max-width: 768px) { .quick-view-product-gallery, .quick-view-product-details { width: 100%; padding-right: 0; } .quick-view-product-gallery { margin-bottom: 20px; } } </style> <!-- Quick View JS --> <script> document.addEventListener('DOMContentLoaded', function() { const quickViewButtons = document.querySelectorAll('[data-quick-view]'); const quickViewModal = document.getElementById('quick-view-modal'); const quickViewClose = document.querySelector('.quick-view-close'); const quickViewContent = document.getElementById('quick-view-product-content'); const quickViewLoader = document.querySelector('.quick-view-loader'); const quickViewTemplate = document.getElementById('quick-view-template'); // Add quick view buttons to product cards quickViewButtons.forEach(button => { button.addEventListener('click', function(e) { e.preventDefault(); const productHandle = this.getAttribute('data-product-handle'); openQuickView(productHandle); }); }); // Close modal when clicking the X if (quickViewClose) { quickViewClose.addEventListener('click', closeQuickView); } // Close modal when clicking outside of content quickViewModal.addEventListener('click', function(e) { if (e.target === quickViewModal) { closeQuickView(); } }); // Close modal with ESC key document.addEventListener('keydown', function(e) { if (e.key === 'Escape' && quickViewModal.style.display === 'block') { closeQuickView(); } }); // Open quick view modal and load product function openQuickView(productHandle) { // Show modal quickViewModal.style.display = 'block'; document.body.style.overflow = 'hidden'; // Prevent scrolling // Show loader quickViewLoader.style.display = 'flex'; quickViewContent.innerHTML = ''; // Fetch product data fetch(`/products/${productHandle}?view=quick-view`) .then(response => response.text()) .then(html => { // If the response is HTML (using a custom template) loadProductHTML(html); }) .catch(error => { // If the custom template doesn't exist, fetch JSON data fetch(`/products/${productHandle}.js`) .then(response => response.json()) .then(product => { loadProductJSON(product); }) .catch(error => { console.error('Error loading product:', error); quickViewContent.innerHTML = '<p>Error loading product. Please try again.</p>'; quickViewLoader.style.display = 'none'; }); }); } // Load product from HTML template function loadProductHTML(html) { quickViewContent.innerHTML = html; initializeQuickView(); quickViewLoader.style.display = 'none'; } // Load product from JSON data function loadProductJSON(product) { // Clone the template const content = quickViewTemplate.content.cloneNode(true); // Set product data content.querySelector('.quick-view-product-title').textContent = product.title; content.querySelector('.quick-view-product-price').innerHTML = formatMoney(product.price); content.querySelector('.quick-view-product-description').innerHTML = product.description; content.querySelector('.quick-view-product-link').href = `/products/${product.handle}`; // Set featured image if (product.featured_image) { const featuredImage = content.querySelector('.quick-view-featured-image'); featuredImage.src = product.featured_image; featuredImage.alt = product.title; } // Add thumbnail images const thumbnailsContainer = content.querySelector('.quick-view-thumbnails'); product.images.forEach((image, index) => { const thumbnail = document.createElement('div'); thumbnail.className = 'quick-view-thumbnail'; if (index === 0) thumbnail.classList.add('active'); const img = document.createElement('img'); img.src = image; img.alt = `${product.title} - Image ${index + 1}`; thumbnail.appendChild(img); thumbnail.addEventListener('click', function() { // Set new featured image content.querySelector('.quick-view-featured-image').src = image; // Update active thumbnail content.querySelectorAll('.quick-view-thumbnail').forEach(thumb => { thumb.classList.remove('active'); }); this.classList.add('active'); }); thumbnailsContainer.appendChild(thumbnail); }); // Add variant options if (product.options_with_values && product.options_with_values.length > 0) { const optionsContainer = content.querySelector('.quick-view-product-options'); product.options_with_values.forEach(option => { const optionSelector = document.createElement('div'); optionSelector.className = 'quick-view-option-selector'; const label = document.createElement('label'); label.textContent = option.name; label.htmlFor = `quick-view-option-${option.name.toLowerCase().replace(/\s+/g, '-')}`; const select = document.createElement('select'); select.id = `quick-view-option-${option.name.toLowerCase().replace(/\s+/g, '-')}`; select.className = 'quick-view-option-select'; select.dataset.option = option.position; option.values.forEach(value => { const optionEl = document.createElement('option'); optionEl.value = value; optionEl.textContent = value; select.appendChild(optionEl); }); optionSelector.appendChild(label); optionSelector.appendChild(select); optionsContainer.appendChild(optionSelector); }); // Add event listeners to update variant ID content.querySelectorAll('.quick-view-option-select').forEach(select => { select.addEventListener('change', function() { updateVariantSelection(content, product); }); }); // Initialize variant selection updateVariantSelection(content, product); } else { // Single variant - set the variant ID directly content.querySelector('.quick-view-variant-id').value = product.variants[0].id; } // Add quantity buttons functionality const quantityInput = content.querySelector('#quick-view-quantity'); content.querySelector('.quick-view-quantity-decrease').addEventListener('click', function() { if (quantityInput.value > 1) { quantityInput.value = parseInt(quantityInput.value) - 1; } }); content.querySelector('.quick-view-quantity-increase').addEventListener('click', function() { quantityInput.value = parseInt(quantityInput.value) + 1; }); // Add to cart functionality const addToCartForm = content.querySelector('.quick-view-product-form'); addToCartForm.addEventListener('submit', function(e) { e.preventDefault(); const addToCartButton = this.querySelector('.quick-view-add-to-cart'); const originalText = addToCartButton.textContent; // Disable button during submission addToCartButton.disabled = true; addToCartButton.textContent = 'Adding...'; // Prepare form data const formData = new FormData(this); // Add to cart via AJAX fetch('/cart/add.js', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { addToCartButton.textContent = 'Added!'; // Update cart count or show mini cart updateCartCount(); // Close quick view after delay setTimeout(() => { closeQuickView(); addToCartButton.disabled = false; addToCartButton.textContent = originalText; }, 1500); }) .catch(error => { console.error('Error adding to cart:', error); addToCartButton.textContent = 'Error - Try Again'; setTimeout(() => { addToCartButton.disabled = false; addToCartButton.textContent = originalText; }, 1500); }); }); // Add content to modal quickViewContent.appendChild(content); quickViewLoader.style.display = 'none'; } // Update variant selection based on option selects function updateVariantSelection(content, product) { // Get selected options const selectedOptions = []; content.querySelectorAll('.quick-view-option-select').forEach(select => { selectedOptions[parseInt(select.dataset.option) - 1] = select.value; }); // Find matching variant let matchingVariant = null; for (const variant of product.variants) { if (variant.options.every((option, index) => option === selectedOptions[index])) { matchingVariant = variant; break; } } if (matchingVariant) { // Update variant ID and price content.querySelector('.quick-view-variant-id').value = matchingVariant.id; if (matchingVariant.available) { content.querySelector('.quick-view-add-to-cart').disabled = false; content.querySelector('.quick-view-add-to-cart').textContent = 'Add to Cart'; content.querySelector('.quick-view-add-to-cart').classList.remove('disabled'); } else { content.querySelector('.quick-view-add-to-cart').disabled = true; content.querySelector('.quick-view-add-to-cart').textContent = 'Sold Out'; content.querySelector('.quick-view-add-to-cart').classList.add('disabled'); } // Update price if different if (matchingVariant.price !== product.price) { content.querySelector('.quick-view-product-price').innerHTML = formatMoney(matchingVariant.price); } } } // Initialize quick view functionality after HTML is loaded function initializeQuickView() { // Add functionality to quantity buttons const quantityInput = quickViewContent.querySelector('#quick-view-quantity'); if (quantityInput) { quickViewContent.querySelector('.quick-view-quantity-decrease')?.addEventListener('click', function() { if (quantityInput.value > 1) { quantityInput.value = parseInt(quantityInput.value) - 1; } }); quickViewContent.querySelector('.quick-view-quantity-increase')?.addEventListener('click', function() { quantityInput.value = parseInt(quantityInput.value) + 1; }); } // Add functionality to thumbnails const thumbnails = quickViewContent.querySelectorAll('.quick-view-thumbnail'); thumbnails.forEach(thumbnail => { thumbnail.addEventListener('click', function() { const newSrc = this.querySelector('img').src; quickViewContent.querySelector('.quick-view-featured-image').src = newSrc; thumbnails.forEach(t => t.classList.remove('active')); this.classList.add('active'); }); }); // Add to cart functionality const addToCartForm = quickViewContent.querySelector('.quick-view-product-form'); if (addToCartForm) { addToCartForm.addEventListener('submit', function(e) { e.preventDefault(); const addToCartButton = this.querySelector('.quick-view-add-to-cart'); const originalText = addToCartButton.textContent; // Disable button during submission addToCartButton.disabled = true; addToCartButton.textContent = 'Adding...'; // Add to cart via AJAX fetch('/cart/add.js', { method: 'POST', body: new FormData(this), headers: { 'X-Requested-With': 'XMLHttpRequest' } }) .then(response => response.json()) .then(data => { addToCartButton.textContent = 'Added!'; // Update cart count or show mini cart updateCartCount(); setTimeout(() => { closeQuickView(); addToCartButton.disabled = false; addToCartButton.textContent = originalText; }, 1500); }) .catch(error => { console.error('Error adding to cart:', error); addToCartButton.textContent = 'Error - Try Again'; setTimeout(() => { addToCartButton.disabled = false; addToCartButton.textContent = originalText; }, 1500); }); }); } // Add variant changing functionality const variantSelectors = quickViewContent.querySelectorAll('.quick-view-option-select'); if (variantSelectors.length) { variantSelectors.forEach(selector => { selector.addEventListener('change', function() { // Update product variant // This functionality would depend on your specific Shopify theme }); }); } } // Close quick view modal function closeQuickView() { quickViewModal.style.display = 'none'; document.body.style.overflow = ''; quickViewContent.innerHTML = ''; } // Format money value function formatMoney(cents) { const currency = window.Shopify?.currency?.active || 'USD'; const formatter = new Intl.NumberFormat('en-US', { style: 'currency', currency, minimumFractionDigits: 2 }); return formatter.format(cents / 100); } // Update cart count (depends on theme structure) function updateCartCount() { fetch('/cart.js') .then(response => response.json()) .then(cart => { const cartCountElements = document.querySelectorAll('.cart-count'); cartCountElements.forEach(element => { element.textContent = cart.item_count; }); }) .catch(error => { console.error('Error updating cart count:', error); }); } }); </script> <!-- Add these quick view buttons to your collection template or product card snippet --> <!-- <button class="quick-view-button" data-quick-view data-product-handle="{{ product.handle }}"> Quick View </button> --> <!-- Add a special quick-view template to your theme --> <!-- Create a template at templates/product.quick-view.liquid with the product content you want to display in the quick view modal -->