ondc-campaign-sdk
Version:
[](https://www.npmjs.com/package/ondc-campaign-sdk) [](LICENSE) [ • 29.4 kB
JavaScript
"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>
`;
}