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-%

968 lines (843 loc) 29.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.renderMagazineTemplate = renderMagazineTemplate; function renderMagazineTemplate(campaignData, styleConfig = {}, templateConfig = {}) { if (!campaignData || !campaignData.products || campaignData.products.length === 0) { return renderNoCampaignContent(); } // Default styles const defaultStyle = { primary: "#ff3e6c", secondary: "#3e4152", accent: "#14cda8", textPrimary: "#282c3f", textSecondary: "#94969f", background: "#f5f5f6", cardBackground: "#ffffff", borderRadius: "8px", }; const mergedStyle = { ...defaultStyle, ...styleConfig }; // Default template config const defaultTemplateConfig = { showCategoryTabs: true, productsPerRow: 4, enableAnimations: true, carouselAutoplay: true, carouselInterval: 5000 }; const mergedTemplateConfig = { ...defaultTemplateConfig, ...templateConfig }; // Extract 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 categoryTabsHtml = mergedTemplateConfig.showCategoryTabs ? generateCategoryTabsHtml(categories) : ''; const featuredProductsHtml = generateFeaturedProductsHtml(campaignData.products, mergedTemplateConfig); const categorySectionsHtml = generateCategorySectionsHtml(campaignData.products, categories, mergedTemplateConfig); // Generate the complete template return ` ${generateCss(mergedStyle, mergedTemplateConfig)} <div class="ondc-magazine-template" id="ondc-campaign-container"> <header class="magazine-header"> <div class="campaign-title-container"> <h1 class="campaign-title">${campaignData.campaignName || 'Featured Collection'}</h1> <p class="campaign-description">${campaignData.description || 'Discover our curated selection of products'}</p> </div> </header> ${carouselHtml} ${categoryTabsHtml} <main class="magazine-content"> ${featuredProductsHtml} ${categorySectionsHtml} </main> <footer class="magazine-footer"> <div class="footer-content"> <p>Powered by ONDC Campaign SDK</p> </div> </footer> </div> ${generateJavaScript(mergedTemplateConfig)} `; } function renderNoCampaignContent() { return ` <div class="no-campaign-content"> <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="#d1d1d1" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"> <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path> <line x1="12" y1="9" x2="12" y2="13"></line> <line x1="12" y1="17" x2="12.01" y2="17"></line> </svg> <h2>No Active Campaign</h2> <p>There is no active campaign available at the moment. Please check back later.</p> </div> <style> .no-campaign-content { text-align: center; padding: 80px 20px; max-width: 500px; margin: 40px auto; background: #ffffff; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.05); } .no-campaign-content svg { margin-bottom: 20px; } .no-campaign-content h2 { font-size: 24px; margin-bottom: 12px; color: #333; } .no-campaign-content p { color: #777; font-size: 16px; line-height: 1.5; } </style> `; } function extractCategories(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) { const banners = []; if (campaignData.banner) { banners.push(campaignData.banner); } // Add some placeholder banners for demo banners.push("https://via.placeholder.com/1600x500?text=Summer+Sale"); banners.push("https://via.placeholder.com/1600x500?text=New+Collection"); 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'}" loading="lazy" onerror="this.onerror=null;this.src='https://via.placeholder.com/1600x500?text=Campaign';"> </div> `; }).join(''); const indicators = bannerImages.map((_, index) => { return `<button class="carousel-dot ${index === 0 ? 'active' : ''}" data-index="${index}"></button>`; }).join(''); return ` <div class="banner-carousel"> <div class="carousel-wrapper"> ${slides} <button class="carousel-control 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"> <polyline points="15 18 9 12 15 6"></polyline> </svg> </button> <button class="carousel-control 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"> <polyline points="9 18 15 12 9 6"></polyline> </svg> </button> </div> <div class="carousel-dots"> ${indicators} </div> </div> `; } function generateCategoryTabsHtml(categories) { if (!categories || categories.length === 0) { return ''; } const categoryTabs = categories.map((category, index) => { return `<button class="category-tab ${index === 0 ? 'active' : ''}" data-category="${category}">${category}</button>`; }).join(''); return ` <div class="category-tabs-container"> <div class="category-tabs"> <button class="category-tab active" data-category="all">All Products</button> ${categoryTabs} </div> </div> `; } function generateFeaturedProductsHtml(products, templateConfig) { // Select top products (those with highest discount or rating) const featuredProducts = [...products] .sort((a, b) => (b.discountPercentage || 0) - (a.discountPercentage || 0)) .slice(0, 8); if (featuredProducts.length === 0) { return ''; } const productCards = featuredProducts.map((product, index) => { return generateProductCard(product, index, templateConfig); }).join(''); return ` <section class="featured-products-section" id="featured"> <div class="section-header"> <h2 class="section-title">Featured Products</h2> <p class="section-subtitle">Handpicked selection of top products</p> </div> <div class="products-grid" style="--products-per-row: ${templateConfig.productsPerRow};"> ${productCards} </div> </section> `; } function generateCategorySectionsHtml(products, categories, templateConfig) { if (!categories || categories.length === 0) { return ''; } return categories.map((category, sectionIndex) => { // Filter products by category const categoryProducts = products.filter(product => product.categoryName && Array.isArray(product.categoryName) && product.categoryName.includes(category)).slice(0, templateConfig.productsPerRow * 2); // Show 2 rows maximum if (categoryProducts.length === 0) { return ''; } const productCards = categoryProducts.map((product, index) => { return generateProductCard(product, index, templateConfig); }).join(''); return ` <section class="category-section" id="category-${encodeURIComponent(category)}" data-category="${category}"> <div class="section-header"> <h2 class="section-title">${category}</h2> <a href="#view-all-${encodeURIComponent(category)}" class="view-all-link">View All</a> </div> <div class="products-grid" style="--products-per-row: ${templateConfig.productsPerRow};"> ${productCards} </div> </section> `; }).join(''); } function generateProductCard(product, index, templateConfig) { // 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; // Animation delay for staggered entrance const animationDelay = templateConfig.enableAnimations ? `style="animation-delay: ${index * 0.1}s;"` : ''; // Format prices const formattedRegularPrice = `₹${regularPrice}`; const formattedDiscountedPrice = `₹${discountedPrice}`; // Generate rating stars let ratingStars = ''; if (rating > 0) { const fullStars = Math.floor(rating); const halfStar = rating % 1 >= 0.5; const emptyStars = 5 - fullStars - (halfStar ? 1 : 0); ratingStars = ` <div class="product-rating"> <div class="rating-stars"> ${Array(fullStars).fill('<span class="star full"></span>').join('')} ${halfStar ? '<span class="star half"></span>' : ''} ${Array(emptyStars).fill('<span class="star empty"></span>').join('')} </div> <span class="rating-value">${rating.toFixed(1)}</span> </div> `; } // 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" ${animationDelay} data-product-id="${productId}"> <div class="product-image-container"> <a href="${productUrl}" class="product-image-link" target="_blank"> <img src="${imageUrl}" alt="${productName}" loading="lazy" onerror="this.onerror=null;this.src='https://via.placeholder.com/300x400?text=Product';"> ${discountPercent > 0 ? `<span class="discount-badge">${discountPercent}% OFF</span>` : ''} </a> <button class="wishlist-button" aria-label="Add to wishlist"> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78L12 21.23l8.84-8.84a5.5 5.5 0 0 0 0-7.78z"></path> </svg> </button> </div> <div class="product-details"> ${brandName ? `<div class="product-brand">${brandName}</div>` : ''} <h3 class="product-name"> <a href="${productUrl}" target="_blank">${productName}</a> </h3> <div class="product-price"> ${discountedPrice < regularPrice ? `<span class="discounted-price">${formattedDiscountedPrice}</span> <span class="regular-price">${formattedRegularPrice}</span>` : `<span class="discounted-price">${formattedRegularPrice}</span>`} </div> ${ratingStars} <button class="add-to-cart-button">View Details</button> </div> </div> `; } function generateCss(style, templateConfig) { return ` <style> :root { --primary: ${style.primary}; --secondary: ${style.secondary}; --accent: ${style.accent}; --text-primary: ${style.textPrimary}; --text-secondary: ${style.textSecondary}; --background: ${style.background}; --card-bg: ${style.cardBackground}; --border-radius: ${style.borderRadius}; --products-per-row: ${templateConfig.productsPerRow}; --animation-enabled: ${templateConfig.enableAnimations ? '1' : '0'}; } /* Base styles */ * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; background-color: var(--background); color: var(--text-primary); line-height: 1.5; } .ondc-magazine-template { max-width: 1600px; margin: 0 auto; background-color: var(--background); } /* Header */ .magazine-header { padding: 2rem 1rem; text-align: center; } .campaign-title { font-size: 2.5rem; font-weight: 700; margin-bottom: 0.5rem; color: var(--primary); } .campaign-description { font-size: 1.1rem; color: var(--text-secondary); max-width: 800px; margin: 0 auto; } /* Banner Carousel */ .banner-carousel { position: relative; margin-bottom: 2rem; overflow: hidden; } .carousel-wrapper { position: relative; height: 500px; overflow: hidden; } .carousel-slide { position: absolute; top: 0; left: 0; width: 100%; height: 100%; opacity: 0; transition: opacity 0.7s ease; } .carousel-slide.active { opacity: 1; z-index: 1; } .carousel-slide img { width: 100%; height: 100%; object-fit: cover; } .carousel-control { position: absolute; top: 50%; transform: translateY(-50%); z-index: 2; background: rgba(255, 255, 255, 0.8); border: none; width: 48px; height: 48px; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; opacity: 0.8; transition: all 0.3s ease; } .carousel-control:hover { opacity: 1; background: white; } .carousel-control.prev { left: 20px; } .carousel-control.next { right: 20px; } .carousel-dots { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); display: flex; gap: 10px; z-index: 2; } .carousel-dot { width: 12px; height: 12px; border-radius: 50%; background-color: rgba(255, 255, 255, 0.5); border: none; cursor: pointer; transition: all 0.3s ease; } .carousel-dot.active { background-color: white; transform: scale(1.2); } /* Category Tabs */ .category-tabs-container { background-color: white; padding: 1rem 0; margin-bottom: 2rem; position: sticky; top: 0; z-index: 10; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); } .category-tabs { display: flex; justify-content: center; gap: 1rem; overflow-x: auto; padding: 0 1rem; max-width: 1200px; margin: 0 auto; } .category-tab { background: none; border: none; padding: 0.75rem 1.5rem; font-size: 1rem; font-weight: 500; color: var(--text-secondary); cursor: pointer; border-bottom: 2px solid transparent; transition: all 0.3s ease; white-space: nowrap; } .category-tab:hover { color: var(--primary); } .category-tab.active { color: var(--primary); border-bottom-color: var(--primary); } /* Main Content */ .magazine-content { padding: 0 1rem 3rem; max-width: 1400px; margin: 0 auto; } /* Section Header */ .section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; padding-bottom: 0.5rem; border-bottom: 1px solid #eee; } .section-title { font-size: 1.8rem; font-weight: 600; color: var(--secondary); } .section-subtitle { color: var(--text-secondary); font-size: 1rem; margin-top: 0.5rem; } .view-all-link { color: var(--primary); text-decoration: none; font-weight: 500; transition: all 0.3s ease; } .view-all-link:hover { text-decoration: underline; } /* Products Grid */ .products-grid { display: grid; grid-template-columns: repeat(var(--products-per-row), 1fr); gap: 1.5rem; margin-bottom: 3rem; } /* Product Card */ .product-card { background-color: var(--card-bg); border-radius: var(--border-radius); overflow: hidden; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05); transition: transform 0.3s ease, box-shadow 0.3s ease; animation: var(--animation-enabled) * fadeIn 0.5s both; } @keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .product-card:hover { transform: translateY(-5px); box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); } .product-image-container { position: relative; height: 280px; overflow: hidden; } .product-image-link { display: block; height: 100%; width: 100%; } .product-image-container img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.5s ease; } .product-card:hover .product-image-container img { transform: scale(1.05); } .discount-badge { position: absolute; top: 10px; right: 10px; background-color: var(--primary); color: white; padding: 0.25rem 0.5rem; font-weight: 700; font-size: 0.85rem; border-radius: 4px; } .wishlist-button { position: absolute; top: 10px; left: 10px; background-color: white; border: none; width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; opacity: 0.9; transition: all 0.3s ease; color: #777; } .wishlist-button:hover { color: var(--primary); transform: scale(1.1); } .product-details { padding: 1.25rem; } .product-brand { font-size: 0.9rem; font-weight: 500; color: var(--text-secondary); margin-bottom: 0.25rem; } .product-name { font-size: 1.1rem; font-weight: 600; margin-bottom: 0.75rem; line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .product-name a { color: var(--text-primary); text-decoration: none; transition: color 0.3s ease; } .product-name a:hover { color: var(--primary); } .product-price { display: flex; align-items: center; gap: 8px; margin-bottom: 0.75rem; } .discounted-price { font-size: 1.2rem; font-weight: 700; color: var(--primary); } .regular-price { font-size: 0.95rem; color: var(--text-secondary); text-decoration: line-through; } .product-rating { display: flex; align-items: center; gap: 8px; margin-bottom: 1rem; } .rating-stars { display: flex; gap: 2px; color: #ffc107; } .rating-value { font-size: 0.9rem; color: var(--text-secondary); } .add-to-cart-button { width: 100%; background-color: var(--primary); color: white; border: none; padding: 0.75rem 0; border-radius: 4px; font-weight: 600; cursor: pointer; transition: background-color 0.3s ease; } .add-to-cart-button:hover { background-color: #e62e5c; } /* Footer */ .magazine-footer { background-color: var(--secondary); color: white; padding: 2rem 0; text-align: center; } .footer-content { max-width: 1200px; margin: 0 auto; } /* Responsive Styles */ @media (max-width: 1200px) { .carousel-wrapper { height: 400px; } .products-grid { --products-per-row: 3; } .product-image-container { height: 260px; } } @media (max-width: 992px) { .campaign-title { font-size: 2rem; } .carousel-wrapper { height: 320px; } .products-grid { --products-per-row: 2; } .carousel-control { width: 40px; height: 40px; } .section-title { font-size: 1.5rem; } } @media (max-width: 768px) { .magazine-header { padding: 1.5rem 1rem; } .campaign-title { font-size: 1.8rem; } .campaign-description { font-size: 1rem; } .carousel-wrapper { height: 250px; } .category-tab { padding: 0.5rem 1rem; font-size: 0.9rem; } .products-grid { gap: 1rem; } .product-image-container { height: 220px; } .product-name { font-size: 1rem; } .discounted-price { font-size: 1.1rem; } } @media (max-width: 576px) { .carousel-wrapper { height: 180px; } .products-grid { --products-per-row: 1; } .carousel-control { width: 36px; height: 36px; } .product-image-container { height: 280px; } } </style> `; } function generateJavaScript(templateConfig) { return ` <script> (function() { // Carousel functionality const carousel = document.querySelector('.banner-carousel'); if (!carousel) return; const slides = carousel.querySelectorAll('.carousel-slide'); const dots = carousel.querySelectorAll('.carousel-dot'); const prevBtn = carousel.querySelector('.carousel-control.prev'); const nextBtn = carousel.querySelector('.carousel-control.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')); dots.forEach(dot => dot.classList.remove('active')); slides[index].classList.add('active'); dots[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();` : ''} }); dots.forEach((dot, index) => { dot.addEventListener('click', () => { goToSlide(index); ${templateConfig.carouselAutoplay ? `stopAutoplay(); startAutoplay();` : ''} }); }); // Category tabs functionality const categoryTabs = document.querySelectorAll('.category-tab'); const categorySections = document.querySelectorAll('.category-section'); categoryTabs.forEach(tab => { tab.addEventListener('click', () => { const category = tab.getAttribute('data-category'); // Update active tab categoryTabs.forEach(t => t.classList.remove('active')); tab.classList.add('active'); // Show/hide sections based on category if (category === 'all') { // Show all sections categorySections.forEach(section => { section.style.display = 'block'; }); } else { // Show only matching category categorySections.forEach(section => { if (section.getAttribute('data-category') === category) { section.style.display = 'block'; } else { section.style.display = 'none'; } }); } }); }); // Wishlist button functionality const wishlistButtons = document.querySelectorAll('.wishlist-button'); wishlistButtons.forEach(button => { button.addEventListener('click', function(e) { e.preventDefault(); this.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="red" stroke="red" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78L12 21.23l8.84-8.84a5.5 5.5 0 0 0 0-7.78z"></path></svg>'; }); }); // Product click handling const productCards = document.querySelectorAll('.product-card'); productCards.forEach(card => { const viewButton = card.querySelector('.add-to-cart-button'); const productLink = card.querySelector('.product-name a'); const imageLink = card.querySelector('.product-image-link'); const handleProductClick = function(e) { e.preventDefault(); const productId = card.getAttribute('data-product-id'); const targetUrl = productLink.getAttribute('href'); // Here you would track the click (transaction) // For demonstration, just navigate to product window.open(targetUrl, '_blank'); }; viewButton.addEventListener('click', handleProductClick); imageLink.addEventListener('click', handleProductClick); productLink.addEventListener('click', handleProductClick); }); })(); </script> `; }