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