UNPKG

ondc-campaign-sdk

Version:

[![npm version](https://img.shields.io/npm/v/ondc-campaign-sdk.svg)](https://www.npmjs.com/package/ondc-campaign-sdk) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![Made with ❤️](https://img.shields.io/badge/Made%20with-%

917 lines (810 loc) 27.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.renderModernGalleryTemplate = renderModernGalleryTemplate; function renderModernGalleryTemplate(campaignData, styleConfig = {}, templateConfig = {}) { if (!campaignData || !campaignData.products || campaignData.products.length === 0) { return renderNoCampaignContent(); } // Merge default styles with provided styles const defaultStyle = { primary: "#3d5af1", primaryDark: "#2a3eb1", accent: "#ff6b6b", text: "#333333", textLight: "#777777", bgLight: "#f8f9fa", white: "#ffffff", shadow: "0 10px 30px rgba(0,0,0,0.08)", borderRadius: "12px", }; const mergedStyle = { ...defaultStyle, ...styleConfig }; // Merge default template config with provided config const defaultTemplateConfig = { showCategories: true, maxProductsPerCategory: 4, carouselAutoplay: true, carouselInterval: 5000 }; const mergedTemplateConfig = { ...defaultTemplateConfig, ...templateConfig }; // Process categories from products const categories = extractCategories(campaignData.products); // Create banner images array (for demo using the campaign banner, but in real case this would be an array) const bannerImages = createBannerImages(campaignData); // Generate HTML for each section const carouselHtml = generateCarouselHtml(bannerImages, campaignData.campaignName, mergedTemplateConfig); const categoriesHtml = mergedTemplateConfig.showCategories ? generateCategoriesHtml(categories) : ''; const featuredProductsHtml = generateFeaturedProductsHtml(campaignData.products.slice(0, 4), campaignData, mergedTemplateConfig); const productsByCategoryHtml = generateProductsByCategoryHtml(campaignData.products, categories, campaignData, mergedTemplateConfig); // Combine the CSS and HTML return ` ${generateCss(mergedStyle)} <div class="ondc-campaign modern-gallery" id="ondc-campaign-container"> ${carouselHtml} <div class="campaign-content"> <div class="campaign-header"> <h1 class="campaign-title">${campaignData.campaignName || 'Featured Products'}</h1> <p class="campaign-description">${campaignData.description || 'Browse our collection of curated products'}</p> </div> ${categoriesHtml} ${featuredProductsHtml} ${productsByCategoryHtml} </div> </div> ${generateJavaScript(mergedTemplateConfig)} `; } function renderNoCampaignContent() { return ` <div class="no-campaign-content"> <div class="icon-container"> <svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="10"></circle> <line x1="12" y1="8" x2="12" y2="12"></line> <line x1="12" y1="16" x2="12.01" y2="16"></line> </svg> </div> <h2>No Active Campaign Found</h2> <p>Please check back later or contact your administrator.</p> </div> <style> .no-campaign-content { padding: 60px 20px; text-align: center; color: #555; background: #f8f9fa; border-radius: 12px; max-width: 500px; margin: 40px auto; box-shadow: 0 10px 30px rgba(0,0,0,0.05); } .no-campaign-content .icon-container { margin-bottom: 20px; color: #bbb; } .no-campaign-content h2 { font-size: 24px; margin-bottom: 10px; } .no-campaign-content p { font-size: 16px; color: #888; } </style> `; } function extractCategories(products) { // Get unique categories from products const categoriesSet = new Set(); products.forEach(product => { if (product.categoryName && Array.isArray(product.categoryName)) { product.categoryName.forEach((category) => { categoriesSet.add(category); }); } }); return Array.from(categoriesSet); } function createBannerImages(campaignData) { // In a real scenario, this would come from the API // For now, we'll use the campaign banner and some placeholder images const banners = []; if (campaignData.banner) { banners.push(campaignData.banner); } // Add some placeholder banners for demo banners.push("https://via.placeholder.com/1200x400?text=Special+Offers"); banners.push("https://via.placeholder.com/1200x400?text=New+Arrivals"); return banners; } function generateCarouselHtml(bannerImages, campaignName, templateConfig) { const slides = bannerImages.map((banner, index) => { return ` <div class="carousel-slide ${index === 0 ? 'active' : ''}" data-index="${index}"> <img src="${banner}" alt="${campaignName || 'Campaign Banner'}" onerror="this.onerror=null;this.src='https://via.placeholder.com/1200x400?text=Campaign';"> </div> `; }).join(''); const indicators = bannerImages.map((_, index) => { return `<span class="carousel-indicator ${index === 0 ? 'active' : ''}" data-index="${index}"></span>`; }).join(''); return ` <div class="campaign-carousel"> <div class="carousel-container"> ${slides} </div> <div class="carousel-controls"> <button class="carousel-arrow prev"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M15 18l-6-6 6-6"></path> </svg> </button> <div class="carousel-indicators"> ${indicators} </div> <button class="carousel-arrow next"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M9 18l6-6-6-6"></path> </svg> </button> </div> </div> `; } function generateCategoriesHtml(categories) { if (!categories || categories.length === 0) { return ''; } const categoryItems = categories.map((category, index) => { // Generate a color based on index for category background const hue = (index * 50) % 360; const bgColor = `hsla(${hue}, 70%, 45%, 0.9)`; return ` <a href="#category-${encodeURIComponent(category)}" class="category-item" data-category="${category}"> <div class="category-icon" style="background-color: ${bgColor}"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect> <circle cx="8.5" cy="8.5" r="1.5"></circle> <polyline points="21 15 16 10 5 21"></polyline> </svg> </div> <span class="category-name">${category}</span> </a> `; }).join(''); return ` <div class="categories-section"> <h2 class="section-title">Shop By Categories</h2> <div class="categories-grid"> ${categoryItems} </div> </div> `; } function generateFeaturedProductsHtml(products, campaignData, templateConfig) { if (!products || products.length === 0) { return ''; } const productCards = products.map((product, index) => { return generateProductCard(product, campaignData, index); }).join(''); return ` <div class="featured-products-section"> <h2 class="section-title">Featured Products</h2> <div class="products-grid"> ${productCards} </div> ${products.length > templateConfig.maxProductsPerCategory ? '<div class="view-more-container"><a href="#all-products" class="view-more-btn">View All Products</a></div>' : ''} </div> `; } function generateProductsByCategoryHtml(allProducts, categories, campaignData, templateConfig) { if (!categories || categories.length === 0) { return ''; } return categories.map(category => { // Filter products by category const categoryProducts = allProducts.filter(product => product.categoryName && Array.isArray(product.categoryName) && product.categoryName.includes(category)).slice(0, templateConfig.maxProductsPerCategory); if (categoryProducts.length === 0) { return ''; } const productCards = categoryProducts.map((product, index) => { return generateProductCard(product, campaignData, index); }).join(''); return ` <div class="category-products-section" id="category-${encodeURIComponent(category)}"> <h2 class="section-title">${category}</h2> <div class="products-grid"> ${productCards} </div> ${categoryProducts.length > templateConfig.maxProductsPerCategory ? `<div class="view-more-container"><a href="#category-${encodeURIComponent(category)}-all" class="view-more-btn">View All ${category}</a></div>` : ''} </div> `; }).join(''); } function generateProductCard(product, campaignData, index) { // Normalize product data const productName = product.productName || ''; const productId = product.productId || ''; const imageUrl = product.imgUrl || (product.galleryImages && product.galleryImages.length > 0 ? product.galleryImages[0].url : ''); const discountPercent = product.discountPercentage || 0; const regularPrice = product.regularPrice || 0; const discountedPrice = product.discountedPrice || regularPrice; const brandName = product.brandName || ''; const rating = product.productRatings || 0; // Format prices const formattedRegularPrice = `₹${regularPrice}`; const formattedDiscountedPrice = `₹${discountedPrice}`; // Animation delay for staggered entrance const animationDelay = (index * 0.1).toFixed(1); // Build product URL const productNameSlug = productName .toLowerCase() .replace(/[^a-z0-9\s-]/g, '') .replace(/\s+/g, '-') .replace(/-+/g, '-'); const productUrl = `https://shop.samhita.org/product/${productNameSlug}/${productId}`; return ` <div class="product-card" style="animation-delay: ${animationDelay}s;"> ${discountPercent > 0 ? `<div class="discount-tag"> <span class="discount-value">-${discountPercent}%</span> </div>` : ''} <a href="${productUrl}" class="product-link" target="_blank" data-product-id="${productId}"> <div class="product-image"> <img src="${imageUrl}" alt="${productName}" loading="lazy" onerror="this.onerror=null;this.src='https://via.placeholder.com/300x400?text=Product';"> ${brandName ? `<div class="brand-badge">${brandName}</div>` : ''} </div> <div class="product-info"> <h3 class="product-title">${productName}</h3> ${rating > 0 ? `<div class="product-rating"> <div class="stars" style="--rating: ${rating};"></div> <span class="rating-value">${rating.toFixed(1)}</span> </div>` : ''} <div class="product-price"> ${discountedPrice < regularPrice ? `<span class="current-price">${formattedDiscountedPrice}</span> <span class="original-price">${formattedRegularPrice}</span>` : `<span class="current-price">${formattedRegularPrice}</span>`} </div> <button class="view-product-btn"> <span>View Details</span> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M5 12h14"></path> <path d="M12 5l7 7-7 7"></path> </svg> </button> </div> </a> </div> `; } function generateCss(style) { return ` <style> :root { --primary: ${style.primary}; --primary-dark: ${style.primaryDark}; --accent: ${style.accent}; --text: ${style.text}; --text-light: ${style.textLight}; --bg-light: ${style.bgLight}; --white: ${style.white}; --shadow: ${style.shadow}; --border-radius: ${style.borderRadius}; --transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); } /* Base styles */ .ondc-campaign { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', sans-serif; color: var(--text); max-width: 1400px; margin: 0 auto; background-color: #f9f9f9; } .ondc-campaign * { box-sizing: border-box; margin: 0; padding: 0; } /* Campaign Header */ .campaign-header { text-align: center; padding: 3rem 1rem; max-width: 800px; margin: 0 auto; } .campaign-title { font-size: 2.8rem; font-weight: 800; background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; text-fill-color: transparent; margin-bottom: 1rem; line-height: 1.2; } .campaign-description { font-size: 1.2rem; color: var(--text-light); line-height: 1.6; } /* Carousel */ .campaign-carousel { position: relative; overflow: hidden; width: 100%; height: 400px; border-radius: 0 0 var(--border-radius) var(--border-radius); } .carousel-container { width: 100%; height: 100%; position: relative; } .carousel-slide { position: absolute; top: 0; left: 0; width: 100%; height: 100%; opacity: 0; transition: opacity 0.6s ease; } .carousel-slide.active { opacity: 1; } .carousel-slide img { width: 100%; height: 100%; object-fit: cover; } .carousel-controls { position: absolute; bottom: 20px; left: 0; right: 0; display: flex; justify-content: space-between; align-items: center; padding: 0 20px; } .carousel-arrow { background: rgba(255, 255, 255, 0.8); border: none; border-radius: 50%; width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; cursor: pointer; color: var(--text); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); transition: var(--transition); } .carousel-arrow:hover { background: white; transform: scale(1.05); } .carousel-indicators { display: flex; gap: 8px; } .carousel-indicator { width: 10px; height: 10px; border-radius: 50%; background: rgba(255, 255, 255, 0.5); cursor: pointer; transition: var(--transition); } .carousel-indicator.active { background: white; transform: scale(1.2); } /* Categories Section */ .categories-section { padding: 3rem 1rem; background-color: var(--white); margin-bottom: 2rem; } .section-title { font-size: 2rem; font-weight: 700; margin-bottom: 2rem; text-align: center; color: var(--text); position: relative; } .section-title::after { content: ''; display: block; width: 60px; height: 3px; background: var(--primary); margin: 0.8rem auto 0; border-radius: 3px; } .categories-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 1.5rem; max-width: 1200px; margin: 0 auto; } .category-item { display: flex; flex-direction: column; align-items: center; text-decoration: none; color: var(--text); transition: var(--transition); } .category-item:hover { transform: translateY(-5px); } .category-icon { width: 80px; height: 80px; border-radius: 50%; background: var(--primary); color: white; display: flex; align-items: center; justify-content: center; margin-bottom: 1rem; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); } .category-name { font-weight: 600; text-align: center; font-size: 0.95rem; } /* Products Section */ .featured-products-section, .category-products-section { padding: 3rem 1rem; margin-bottom: 2rem; background-color: var(--white); border-radius: var(--border-radius); box-shadow: var(--shadow); } .products-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 2rem; max-width: 1200px; margin: 0 auto; } /* Product Card */ .product-card { background: var(--white); border-radius: 16px; overflow: hidden; box-shadow: 0 5px 20px rgba(0, 0, 0, 0.05); transition: var(--transition); transform: translateY(20px); opacity: 0; animation: fadeInUp 0.6s forwards; height: 100%; } @keyframes fadeInUp { to { opacity: 1; transform: translateY(0); } } .product-card:hover { transform: translateY(-10px); box-shadow: 0 15px 30px rgba(0, 0, 0, 0.1); } .product-link { text-decoration: none; color: inherit; display: flex; flex-direction: column; height: 100%; } .product-image { height: 280px; position: relative; overflow: hidden; } .product-image img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.5s ease; } .product-card:hover .product-image img { transform: scale(1.05); } .discount-tag { position: absolute; top: 15px; right: 15px; background: var(--accent); color: white; font-weight: 600; padding: 6px 12px; border-radius: 100px; font-size: 0.85rem; z-index: 1; box-shadow: 0 4px 10px rgba(255, 107, 107, 0.3); } .brand-badge { position: absolute; bottom: 0; left: 0; background: rgba(0, 0, 0, 0.7); color: white; font-size: 0.8rem; padding: 6px 12px; font-weight: 500; } .product-info { padding: 1.5rem; display: flex; flex-direction: column; flex-grow: 1; } .product-title { font-size: 1.1rem; font-weight: 600; margin-bottom: 0.8rem; color: var(--text); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .product-rating { display: flex; align-items: center; margin-bottom: 1rem; } .stars { --percent: calc(var(--rating) / 5 * 100%); display: inline-block; font-size: 0.9rem; font-family: Times; line-height: 1; position: relative; width: max-content; } .stars::before { content: '★★★★★'; letter-spacing: 3px; background: linear-gradient(90deg, gold var(--percent), #ccc var(--percent)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } .rating-value { margin-left: 8px; font-size: 0.9rem; color: var(--text-light); } .product-price { margin-bottom: 1.2rem; display: flex; align-items: center; gap: 10px; } .current-price { font-size: 1.4rem; font-weight: 700; color: var(--primary); } .original-price { font-size: 1rem; color: var(--text-light); text-decoration: line-through; } .view-product-btn { background: var(--primary); color: white; border: none; padding: 12px 20px; border-radius: 8px; font-weight: 600; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 8px; transition: var(--transition); width: 100%; margin-top: auto; } .view-product-btn:hover { background: var(--primary-dark); } .view-product-btn svg { transition: transform 0.2s ease; } .view-product-btn:hover svg { transform: translateX(4px); } .view-more-container { text-align: center; margin-top: 2rem; } .view-more-btn { display: inline-block; padding: 12px 30px; background: transparent; border: 2px solid var(--primary); color: var(--primary); font-weight: 600; border-radius: 8px; text-decoration: none; transition: var(--transition); } .view-more-btn:hover { background: var(--primary); color: white; } /* Responsive styles */ @media (max-width: 1200px) { .campaign-title { font-size: 2.4rem; } .campaign-carousel { height: 350px; } } @media (max-width: 768px) { .campaign-title { font-size: 2rem; } .campaign-description { font-size: 1rem; } .campaign-carousel { height: 280px; } .categories-grid { grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); } .products-grid { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 1.5rem; } .product-image { height: 220px; } } @media (max-width: 576px) { .campaign-title { font-size: 1.8rem; } .campaign-carousel { height: 200px; } .carousel-arrow { width: 36px; height: 36px; } .categories-grid { grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); gap: 1rem; } .category-icon { width: 60px; height: 60px; } .section-title { font-size: 1.5rem; } .products-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 1rem; } .product-image { height: 180px; } .product-info { padding: 1rem; } .product-title { font-size: 1rem; } .current-price { font-size: 1.2rem; } } </style> `; } function generateJavaScript(templateConfig) { return ` <script> (function() { // Carousel functionality const carousel = document.querySelector('.campaign-carousel'); if (!carousel) return; const slides = carousel.querySelectorAll('.carousel-slide'); const indicators = carousel.querySelectorAll('.carousel-indicator'); const prevBtn = carousel.querySelector('.carousel-arrow.prev'); const nextBtn = carousel.querySelector('.carousel-arrow.next'); let currentIndex = 0; const slideCount = slides.length; // Auto-play functionality ${templateConfig.carouselAutoplay ? `let autoplayInterval; function startAutoplay() { autoplayInterval = setInterval(() => { goToSlide((currentIndex + 1) % slideCount); }, ${templateConfig.carouselInterval}); } function stopAutoplay() { clearInterval(autoplayInterval); } // Start autoplay initially startAutoplay(); // Pause autoplay on hover carousel.addEventListener('mouseenter', stopAutoplay); carousel.addEventListener('mouseleave', startAutoplay);` : ''} // Navigation functions function goToSlide(index) { slides.forEach(slide => slide.classList.remove('active')); indicators.forEach(indicator => indicator.classList.remove('active')); slides[index].classList.add('active'); indicators[index].classList.add('active'); currentIndex = index; } // Event listeners prevBtn.addEventListener('click', () => { goToSlide((currentIndex - 1 + slideCount) % slideCount); ${templateConfig.carouselAutoplay ? `stopAutoplay(); startAutoplay();` : ''} }); nextBtn.addEventListener('click', () => { goToSlide((currentIndex + 1) % slideCount); ${templateConfig.carouselAutoplay ? `stopAutoplay(); startAutoplay();` : ''} }); indicators.forEach((indicator, index) => { indicator.addEventListener('click', () => { goToSlide(index); ${templateConfig.carouselAutoplay ? `stopAutoplay(); startAutoplay();` : ''} }); }); // Product click tracking const productLinks = document.querySelectorAll('.product-link'); productLinks.forEach(link => { link.addEventListener('click', function(e) { e.preventDefault(); const productId = this.getAttribute('data-product-id'); const targetUrl = this.getAttribute('href'); // Here you would send a request to track the click // For example: /* fetch('https://ondc-sdk.samhita.org/api/transactions/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ campaign_id: 'CAMPAIGN_ID', product_id: productId, status: 'initiated' }) }) .then(() => { window.open(targetUrl, '_blank'); }); */ // For now, just navigate to the product page window.open(targetUrl, '_blank'); }); }); })(); </script> `; }