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

1,619 lines (1,428 loc) โ€ข 76.2 kB
import { fetchLiveCampaignProducts, fetchLiveCampaignBasedOnCount } from "./api"; export async function renderDynamicTemplate( campignOrder: number = 1, productCount: number = 12 ): Promise<string> { // Try to use transactions in browser environments only const withTransaction = typeof window !== 'undefined'; const result = await fetchLiveCampaignProducts(withTransaction , campignOrder); // Handle both response formats (with or without transaction) const campaign = result?.campaign || result; const transaction = result?.transaction; const userId = result?.user_id; const templateId = result?.template_id || result?.campaign?.template_id || "light"; const noCampaignContentFound = `<div style="text-align: center; padding: 40px;"> <img src="https://cdn-icons-png.flaticon.com/512/4076/4076549.png" alt="No Campaign" width="120" style="opacity: 0.6;" /> <h2 style="color: #555; margin-top: 20px;">No Active Campaign Found</h2> <p style="color: #888;">Please check back later or contact your administrator.</p> </div>`; if(!campaign || campaign.message) { return noCampaignContentFound; } const liveProductCount = campaign?.products?.length || 0; if (!liveProductCount || !campaign) { return noCampaignContentFound; } // Category to icon mapping const categoryIconMap: { [key: string]: string } = { "T Shirts": "๐Ÿ‘•", "Kurtis, Tunics": "๐Ÿ‘—", "Stationery": "๐Ÿ“", "Home Decor": "๐Ÿ ", "Kurtas & Kurta Sets": "๐Ÿ‘˜", "Shirts": "๐Ÿ‘”", "Dresses": "๐Ÿ’ƒ", "Garden & Outdoor": "๐ŸŒฟ", "Casual Shoes": "๐Ÿ‘Ÿ", "Flip-Flops & Flats": "๐Ÿฉด", "Sports Shoes": "๐Ÿ‘Ÿ", "Home Furnishing - Bedding and Linen": "๐Ÿ›๏ธ", "Sarees": "๐Ÿฅป", "Innerwear & Sleepwear": "๐Ÿฉฑ", "Clothing Sets": "๐Ÿ‘—", "Sandals & Floaters": "๐Ÿ‘ก", "Tops": "๐Ÿ‘š", "Bra": "๐Ÿฉฑ", "Ethnic Wear": "๐Ÿ‘˜", "Jeans": "๐Ÿ‘–", "Sweatshirts": "๐Ÿงฅ", "Nightwear & Loungewear": "๐Ÿ›Œ", "Coordinates": "๐Ÿ“", "Fragrance": "๐ŸŒธ", "Shorts": "๐Ÿฉณ", "Handbags": "๐Ÿ‘œ", "Track Pants": "๐Ÿƒ", "Kitchen Storage and Containers": "๐Ÿฅ„", "Hardware, Tools and Home Safety": "๐Ÿ”ง", "Masala & Seasoning": "๐ŸŒถ๏ธ", "Trousers": "๐Ÿ‘”", "Watches": "โŒš", "Skin Care - Face Cleansers": "๐Ÿงด", "Jackets & Coats": "๐Ÿงฅ", "Formal Shoes": "๐Ÿ‘ž", "Toys and Games": "๐ŸŽฎ", "Earrings": "๐Ÿ’Ž", "Ayurvedic": "๐ŸŒฟ", "Hair Oils, Care, and Styling": "๐Ÿ’‡", "Sports and Fitness Equipment": "๐Ÿ‹๏ธ", "Books": "๐Ÿ“š", "Jewellery Sets": "๐Ÿ’", "Mobile Cover": "๐Ÿ“ฑ", "Belts": "๐Ÿ‘ข", "Serveware": "๐Ÿฝ๏ธ", "Salwars": "๐Ÿ‘—", "Necklaces": "๐Ÿ“ฟ", "Sweaters": "๐Ÿงฅ", "Bangles & Bracelets": "โœจ", "Skirts": "๐Ÿ‘—" }; // Get categories from API or use fallback let categoriesData = []; if (campaign.categories && Array.isArray(campaign.categories)) { categoriesData = campaign.categories.map((category: any, index: number) => ({ name: category.filterItem || category.name, icon: categoryIconMap[category.filterItem || category.name] || "๐Ÿ›๏ธ", productCount: category.productCount || 0 })).slice(0, 12); // Limit to 12 categories for better layout } else { // Fallback categories if API doesn't provide them // categoriesData = [ // { id: 1, name: "T Shirts", icon: "๐Ÿ‘•", productCount: 14247 }, // { id: 2, name: "Stationery", icon: "๐Ÿ“", productCount: 26818 }, // { id: 3, name: "Home Decor", icon: "๐Ÿ ", productCount: 25655 }, // { id: 4, name: "Kurtis, Tunics", icon: "๐Ÿ‘—", productCount: 9945 }, // { id: 5, name: "Shirts", icon: "๐Ÿ‘”", productCount: 5758 }, // { id: 6, name: "Dresses", icon: "๐Ÿ’ƒ", productCount: 6270 } // ]; } // Select template based on templateId switch(templateId) { case "dark": return generateLuxuryPremiumTemplate(campaign, categoriesData, productCount, transaction, userId); case "light": return generateModernElegantTemplate(campaign, categoriesData, productCount, transaction, userId); default: return generateModernElegantTemplate(campaign, categoriesData, productCount, transaction, userId); } } function generateModernElegantTemplate( campaign: any, categoriesData: any[], productCount: number, transaction: any, userId: string ): string { const productCards = campaign.products .slice(0, productCount) .map((product: any, index: number) => generateProductCard(product, campaign, transaction, userId)) .join(""); const categoryCards = categoriesData.map(category => ` <div class="modern-category-card" onclick=" (function(event) { event.preventDefault(); var campaignId = '${campaign._id}'; var categoryName = '${category.name}'; var affiliateId = '${transaction?.data?.affiliate_id || ''}'; var userId = '${userId}'; fetch('https://ondc-sdk.samhita.org/api/transactions/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ campaign_id: campaignId, category_name: categoryName, affiliate_id: affiliateId || undefined, status: 'initiated', user_id: userId }) }) .then(function(response) { var categoryUrl = 'https://shop.samhita.org/products-near-me' + '?affiliate_id=' + affiliateId + '&user_id=' + userId + '&campaign_id=' + campaignId + '&category=' + encodeURIComponent(categoryName) + '&pageId=1'; if (response.ok) { window.open(categoryUrl, '_blank'); } else { window.open(categoryUrl, '_blank'); console.error('Failed to track category click'); } }) .catch(function(err) { var categoryUrl = 'https://shop.samhita.org/products-near-me' + '?affiliate_id=' + affiliateId + '&user_id=' + userId + '&campaign_id=' + campaignId + '&category=' + encodeURIComponent(categoryName) + '&pageId=1'; console.error('Error tracking category click:', err); window.open(categoryUrl, '_blank'); }); return false; })(event); "> <div class="category-header"> <span class="category-icon">${category.icon}</span> <div class="category-details"> <h3 class="category-name">${category.name}</h3> <span class="product-count">${category.productCount?.toLocaleString()} items</span> </div> </div> <div class="category-arrow">โ†’</div> </div> `).join(""); // Categories section - only show if categories are available const categoriesSection = categoriesData.length > 0 ? ` <!-- Categories Section --> <div class="categories-section"> <div class="section-header"> <h2 class="section-title">Shop by Category</h2> <p class="section-subtitle">Browse through our diverse collection of categories</p> </div> <div class="categories-grid"> ${categoryCards} </div> </div> ` : ''; return ` <style> @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap'); * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; line-height: 1.6; color: #1a1a1a; background: #fafafa; } .modern-template { background: #ffffff; min-height: 100vh; } /* Hero Section */ .hero-section { position: relative; height: 500px; overflow: hidden; margin-bottom: 80px; } .hero-image { width: 100%; height: 100%; object-fit: cover; object-position: center; } .hero-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient(135deg, rgba(0,0,0,0.6) 0%, rgba(0,0,0,0.3) 100%); display: flex; align-items: center; justify-content: center; text-align: center; color: white; } .hero-content h1 { font-size: 4rem; font-weight: 800; margin-bottom: 1.5rem; letter-spacing: -0.02em; text-shadow: 0 4px 20px rgba(0,0,0,0.3); } .hero-content p { font-size: 1.4rem; font-weight: 400; opacity: 0.95; max-width: 600px; line-height: 1.6; text-shadow: 0 2px 10px rgba(0,0,0,0.2); } /* Categories Section */ .categories-section { padding: 0 60px 80px; max-width: 1400px; margin: 0 auto; } .section-header { text-align: center; margin-bottom: 60px; } .section-title { font-size: 3rem; font-weight: 700; color: #1a1a1a; margin-bottom: 16px; letter-spacing: -0.025em; } .section-subtitle { font-size: 1.2rem; color: #6b7280; font-weight: 400; max-width: 500px; margin: 0 auto; } .categories-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 24px; margin-bottom: 80px; } .modern-category-card { background: #ffffff; border: 1px solid #e5e7eb; border-radius: 20px; padding: 32px; cursor: pointer; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); position: relative; overflow: hidden; display: flex; align-items: center; justify-content: space-between; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); } .modern-category-card::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 4px; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); transform: scaleX(0); transition: transform 0.3s ease; } .modern-category-card:hover::before { transform: scaleX(1); } .modern-category-card:hover { transform: translateY(-8px); box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); border-color: #d1d5db; } .category-header { display: flex; align-items: center; gap: 20px; } .category-icon { font-size: 2.5rem; display: flex; align-items: center; justify-content: center; width: 70px; height: 70px; background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%); border-radius: 16px; flex-shrink: 0; } .category-details { flex: 1; } .category-name { font-size: 1.5rem; font-weight: 600; color: #1a1a1a; margin-bottom: 4px; letter-spacing: -0.01em; } .product-count { font-size: 1rem; color: #6b7280; font-weight: 500; } .category-arrow { font-size: 1.5rem; color: #9ca3af; transition: all 0.3s ease; font-weight: 600; } .modern-category-card:hover .category-arrow { color: #374151; transform: translateX(4px); } /* Products Section */ .products-section { padding: 0 60px 80px; background: linear-gradient(180deg, #fafafa 0%, #ffffff 100%); max-width: 1400px; margin: 0 auto; } .products-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 32px; margin-top: 60px; } .modern-product-card { background: #ffffff; border-radius: 24px; overflow: hidden; transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); position: relative; } .modern-product-card:hover { transform: translateY(-12px); box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); } .product-image-container { position: relative; height: 280px; overflow: hidden; background: #f8fafc; } .modern-product-image { width: 100%; height: 100%; object-fit: contain; transition: transform 0.4s ease; } .modern-product-card:hover .modern-product-image { transform: scale(1.08); } .modern-discount-badge { position: absolute; top: 16px; right: 16px; background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); color: white; padding: 8px 16px; border-radius: 20px; font-size: 0.875rem; font-weight: 600; z-index: 2; box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4); } .modern-product-info { padding: 28px; } .modern-product-brand { font-size: 0.875rem; color: #6b7280; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 8px; font-weight: 500; } .modern-product-name { font-size: 1.25rem; font-weight: 600; margin-bottom: 12px; color: #1a1a1a; line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .modern-product-rating { display: flex; align-items: center; gap: 8px; margin-bottom: 16px; } .modern-stars { color: #fbbf24; font-size: 1rem; letter-spacing: 1px; } .modern-rating-value { font-size: 0.875rem; color: #6b7280; font-weight: 500; } .modern-product-price { display: flex; align-items: center; gap: 12px; margin-bottom: 24px; } .modern-current-price { font-size: 1.75rem; font-weight: 700; color: #1a1a1a; } .modern-original-price { font-size: 1.125rem; color: #9ca3af; text-decoration: line-through; font-weight: 400; } .modern-product-btn { width: 100%; background: linear-gradient(135deg, #1a1a1a 0%, #374151 100%); color: white; border: none; padding: 16px 24px; border-radius: 12px; font-weight: 600; font-size: 1rem; cursor: pointer; transition: all 0.3s ease; position: relative; overflow: hidden; } .modern-product-btn::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent); transition: left 0.5s ease; } .modern-product-btn:hover::before { left: 100%; } .modern-product-btn:hover { background: linear-gradient(135deg, #374151 0%, #4b5563 100%); transform: translateY(-2px); box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2); } /* Responsive Design */ @media (max-width: 1200px) { .categories-section, .products-section { padding: 0 40px 60px; } .hero-content h1 { font-size: 3.5rem; } .section-title { font-size: 2.5rem; } } @media (max-width: 768px) { .categories-section, .products-section { padding: 0 20px 40px; } .hero-section { height: 350px; margin-bottom: 60px; } .hero-content h1 { font-size: 2.5rem; } .hero-content p { font-size: 1.125rem; } .section-title { font-size: 2rem; } .categories-grid { grid-template-columns: 1fr; gap: 20px; } .modern-category-card { padding: 24px; } .category-name { font-size: 1.25rem; } .products-grid { grid-template-columns: 1fr; gap: 24px; } } </style> <div class="modern-template"> <!-- Hero Section --> <div class="hero-section"> <img src="${campaign.banner || 'https://images.unsplash.com/photo-1441986300917-64674bd600d8?w=1400&h=500&fit=crop&auto=format'}" alt="Campaign Banner" class="hero-image" /> <div class="hero-overlay"> <div class="hero-content"> <h1>${campaign.campaignName || 'Discover Amazing Products'}</h1> <p>${campaign.description || 'Explore our curated collection of premium items, handpicked just for you'}</p> </div> </div> </div> ${categoriesSection} <!-- Products Section --> <div class="products-section"> <div class="section-header"> <h2 class="section-title">Featured Products</h2> <p class="section-subtitle">Handpicked products that you'll absolutely love</p> </div> <div class="products-grid"> ${productCards} </div> </div> </div> `; } function generateLuxuryPremiumTemplate( campaign: any, categoriesData: any[], productCount: number, transaction: any, userId: string ): string { const productCards = campaign.products .slice(0, productCount) .map((product: any, index: number) => generateProductCard(product, campaign, transaction, userId, 'luxury')) .join(""); const categoryItems = categoriesData.map(category => ` <div class="luxury-category" onclick=" (function(event) { event.preventDefault(); var campaignId = '${campaign._id}'; var categoryName = '${category.name}'; var affiliateId = '${transaction?.data?.affiliate_id || ''}'; var userId = '${userId}'; fetch('https://ondc-sdk.samhita.org/api/transactions/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ campaign_id: campaignId, category_name: categoryName, affiliate_id: affiliateId || undefined, status: 'initiated', user_id: userId }) }) .then(function(response) { var categoryUrl = 'https://shop.samhita.org/' + categoryName + '?affiliate_id=' + affiliateId + '&user_id=' + userId + '&campaign_id=' + campaignId; if (response.ok) { window.open(categoryUrl, '_blank'); } else { console.error('Failed to track category click'); window.open(categoryUrl, '_blank'); } }) .catch(function(err) { var categoryUrl = 'https://shop.samhita.org/' + categoryName + '?affiliate_id=' + affiliateId + '&user_id=' + userId + '&campaign_id=' + campaignId; console.error('Error tracking category click:', err); window.open(categoryUrl, '_blank'); }); return false; })(event); "> <div class="luxury-cat-icon">${category.icon}</div> <div class="luxury-cat-info"> <h4>${category.name}</h4> <span>${category.productCount?.toLocaleString()} Products</span> </div> <div class="luxury-cat-chevron">โ€บ</div> </div> `).join(""); // Categories section - only show if categories are available const categoriesSection = categoriesData.length > 0 ? ` <!-- Categories Section --> <div class="luxury-categories-section"> <div class="luxury-section-header"> <h2 class="luxury-section-title">Explore Categories</h2> <p class="luxury-section-desc">Discover our premium collections across various categories</p> </div> <div class="luxury-categories-grid"> ${categoryItems} </div> </div> ` : ''; return ` <style> @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;600;700;800&family=Inter:wght@300;400;500;600;700&display=swap'); * { margin: 0; padding: 0; box-sizing: border-box; } .luxury-template { font-family: 'Inter', sans-serif; background: #0f0f0f; color: #ffffff; min-height: 100vh; line-height: 1.6; } /* Hero Banner */ .luxury-hero { position: relative; height: 70vh; min-height: 600px; overflow: hidden; display: flex; align-items: center; justify-content: center; } .luxury-hero-bg { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; object-position: center; } .luxury-hero-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(45deg, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0.4) 100%); } .luxury-hero-content { position: relative; z-index: 2; text-align: center; max-width: 800px; padding: 0 40px; } .luxury-hero-title { font-family: 'Playfair Display', serif; font-size: 5rem; font-weight: 700; margin-bottom: 2rem; letter-spacing: -0.02em; text-shadow: 0 4px 30px rgba(0,0,0,0.5); background: linear-gradient(135deg, #ffffff 0%, #e5e7eb 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } .luxury-hero-subtitle { font-size: 1.5rem; font-weight: 300; opacity: 0.9; line-height: 1.8; text-shadow: 0 2px 15px rgba(0,0,0,0.3); } /* Main Content */ .luxury-main { background: linear-gradient(180deg, #0f0f0f 0%, #1a1a1a 50%, #0f0f0f 100%); padding: 100px 0; } .luxury-container { padding: 0 40px; } /* Categories Section */ .luxury-categories-section { margin-bottom: 120px; max-width: 1400px; margin: 0 auto; } .luxury-section-header { text-align: center; margin-bottom: 80px; } .luxury-section-title { font-family: 'Playfair Display', serif; font-size: 3.5rem; font-weight: 600; margin-bottom: 20px; letter-spacing: -0.025em; background: linear-gradient(135deg, #ffffff 0%, #d1d5db 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } .luxury-section-desc { font-size: 1.2rem; color: #9ca3af; font-weight: 300; max-width: 600px; margin: 0 auto; } .luxury-categories-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 32px; } .luxury-category { background: linear-gradient(135deg, #1f2937 0%, #111827 100%); border: 1px solid #374151; border-radius: 24px; padding: 40px; cursor: pointer; transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); position: relative; overflow: hidden; display: flex; align-items: center; gap: 24px; } .luxury-category::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient(135deg, rgba(255,255,255,0.05) 0%, transparent 100%); opacity: 0; transition: opacity 0.4s ease; } .luxury-category:hover::before { opacity: 1; } .luxury-category:hover { transform: translateY(-12px); border-color: #6b7280; box-shadow: 0 30px 60px rgba(0, 0, 0, 0.4); } .luxury-cat-icon { width: 80px; height: 80px; background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%); border-radius: 20px; display: flex; align-items: center; justify-content: center; font-size: 2.5rem; flex-shrink: 0; box-shadow: 0 8px 32px rgba(79, 70, 229, 0.3); } .luxury-cat-info { flex: 1; } .luxury-cat-info h4 { font-family: 'Playfair Display', serif; font-size: 1.75rem; font-weight: 600; margin-bottom: 8px; color: #ffffff; } .luxury-cat-info span { color: #9ca3af; font-size: 1rem; font-weight: 400; } .luxury-cat-chevron { font-size: 2rem; color: #6b7280; transition: all 0.3s ease; font-weight: 300; } .luxury-category:hover .luxury-cat-chevron { color: #ffffff; transform: translateX(8px); } /* Products Section */ .luxury-products-section { position: relative; max-width: 1400px; margin: 0 auto; } .luxury-products-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 40px; margin-top: 80px; } .luxury-product-card { background: linear-gradient(135deg, #1f2937 0%, #111827 100%); border: 1px solid #374151; border-radius: 32px; overflow: hidden; transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); position: relative; group: luxury-card; } .luxury-product-card:hover { transform: translateY(-16px) rotateY(5deg); border-color: #6b7280; box-shadow: 0 40px 80px rgba(0, 0, 0, 0.5); } .luxury-product-image-container { position: relative; height: 320px; overflow: hidden; background: #f3f4f6; } .luxury-product-image { width: 100%; height: 100%; object-fit: contain; transition: transform 0.6s ease; } .luxury-product-card:hover .luxury-product-image { transform: scale(1.1); } .luxury-discount-badge { position: absolute; top: 20px; right: 20px; background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); color: white; padding: 12px 20px; border-radius: 30px; font-size: 0.9rem; font-weight: 600; z-index: 2; box-shadow: 0 8px 25px rgba(239, 68, 68, 0.4); backdrop-filter: blur(10px); } .luxury-product-info { padding: 36px; } .luxury-product-brand { font-size: 0.875rem; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 12px; font-weight: 500; } .luxury-product-name { font-family: 'Playfair Display', serif; font-size: 1.5rem; font-weight: 600; margin-bottom: 16px; color: #ffffff; line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .luxury-product-rating { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; } .luxury-stars { color: #fbbf24; font-size: 1.1rem; letter-spacing: 2px; } .luxury-rating-value { font-size: 0.9rem; color: #9ca3af; font-weight: 500; } .luxury-product-price { display: flex; align-items: center; gap: 16px; margin-bottom: 28px; } .luxury-current-price { font-family: 'Playfair Display', serif; font-size: 2rem; font-weight: 700; color: #ffffff; } .luxury-original-price { font-size: 1.25rem; color: #6b7280; text-decoration: line-through; font-weight: 400; } .luxury-product-btn { width: 100%; background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%); color: white; border: none; padding: 18px 28px; border-radius: 16px; font-weight: 600; font-size: 1rem; cursor: pointer; transition: all 0.4s ease; position: relative; overflow: hidden; text-transform: uppercase; letter-spacing: 0.5px; } .luxury-product-btn::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent); transition: left 0.6s ease; } .luxury-product-btn:hover::before { left: 100%; } .luxury-product-btn:hover { background: linear-gradient(135deg, #5b21b6 0%, #8b5cf6 100%); transform: translateY(-3px); box-shadow: 0 15px 35px rgba(79, 70, 229, 0.4); } /* Responsive Design */ @media (max-width: 1200px) { .luxury-container { padding: 0 30px; } .luxury-hero-title { font-size: 4rem; } .luxury-section-title { font-size: 3rem; } } @media (max-width: 768px) { .luxury-container { padding: 0 20px; } .luxury-hero { height: 60vh; min-height: 500px; } .luxury-hero-content { padding: 0 20px; } .luxury-hero-title { font-size: 2.5rem; } .luxury-hero-subtitle { font-size: 1.25rem; } .luxury-main { padding: 60px 0; } .luxury-categories-section { margin-bottom: 80px; } .luxury-section-header { margin-bottom: 60px; } .luxury-section-title { font-size: 2.5rem; } .luxury-categories-grid { grid-template-columns: 1fr; gap: 24px; } .luxury-category { padding: 28px; } .luxury-cat-icon { width: 60px; height: 60px; font-size: 2rem; } .luxury-cat-info h4 { font-size: 1.5rem; } .luxury-products-grid { grid-template-columns: 1fr; gap: 32px; } } </style> <div class="luxury-template"> <!-- Hero Section --> <div class="luxury-hero"> <img src="${campaign.banner || 'https://images.unsplash.com/photo-1441986300917-64674bd600d8?w=1400&h=800&fit=crop&auto=format'}" alt="Campaign Banner" class="luxury-hero-bg" /> <div class="luxury-hero-overlay"></div> <div class="luxury-hero-content"> <h1 class="luxury-hero-title">${campaign.campaignName || 'Exclusive Collection'}</h1> <p class="luxury-hero-subtitle">${campaign.description || 'Discover our carefully curated selection of premium products, designed for those who appreciate excellence'}</p> </div> </div> <!-- Main Content --> <div class="luxury-main"> <div class="luxury-container"> ${categoriesSection} <!-- Products Section --> <div class="luxury-products-section"> <div class="luxury-section-header"> <h2 class="luxury-section-title">Featured Products</h2> <p class="luxury-section-desc">Handpicked items that embody luxury and quality</p> </div> <div class="luxury-products-grid"> ${productCards} </div> </div> </div> </div> </div> `; } function generateProductCard(product: any, campaign: any, transaction: any, userId: string, style: string = 'modern'): string { // Data normalization (same as render.ts) if (!product.productId && product.id) { product.productId = product.id.toString(); } if (!product.productName && product.name) { product.productName = product.name; } if (product.imgUrl && product.imgUrl.startsWith('/')) { product.imgUrl = `https://cdnaz.plotch.io/image/upload/w_300,h_450${product.imgUrl}?product_id=${product.productId}&s=1&tf=vt`; } if (product.base_image && product.base_image.original_image_url && !product.imgUrl) { product.imgUrl = product.base_image.original_image_url; } if (typeof product.regularPrice === 'undefined' && product.prices?.regular?.price) { product.regularPrice = parseFloat(product.prices.regular.price); } if (typeof product.discountedPrice === 'undefined' && product.prices?.final?.price) { product.discountedPrice = parseFloat(product.prices.final.price); } if (typeof product.productRatings === 'undefined' && product.ratings?.average) { product.productRatings = parseFloat(product.ratings.average); } const productName = product.productName .toLowerCase() .replace(/[^a-z0-9\s-]/g, '') .replace(/\s+/g, '-') .replace(/-+/g, '-') .replace(/(\d+)\s*(kg|g|ml|l)\b/i, '$1$2'); const productUrl = `https://shop.samhita.org/product/${productName}/${product.productId}?affiliate_id=${transaction?.data?.affiliate_id}&user_id=${userId}&campaign_id=${campaign._id}`; const formattedRegularPrice = `โ‚น${product.regularPrice?.toLocaleString('en-IN') || ''}`; const formattedDiscountedPrice = `โ‚น${product.discountedPrice?.toLocaleString('en-IN') || ''}`; const productImage = product.imgUrl || (product.galleryImages && product.galleryImages.length > 0 ? product.galleryImages[0].url : ''); const rating = product.productRatings || 0; const ratingStars = Array(5).fill('') .map((_, i) => i < rating ? 'โ˜…' : 'โ˜†') .join(''); const brandName = product.brandName || ''; // Calculate discount percentage let discountPercent = 0; if (product.regularPrice && product.discountedPrice && product.regularPrice > product.discountedPrice) { const discount = product.regularPrice - product.discountedPrice; discountPercent = Math.round((discount / product.regularPrice) * 100); } if (style === 'luxury') { return ` <div class="luxury-product-card"> ${discountPercent > 0 ? `<div class="luxury-discount-badge">${discountPercent}% OFF</div>` : ''} <div class="luxury-product-image-container"> <img src="${productImage}" alt="${product.productName || ''}" class="luxury-product-image" loading="lazy" onerror="this.onerror=null;this.src='https://via.placeholder.com/350x320?text=Product';" /> </div> <div class="luxury-product-info"> ${brandName ? `<div class="luxury-product-brand">${brandName}</div>` : ''} <h3 class="luxury-product-name">${product.productName || ''}</h3> ${rating > 0 ? `<div class="luxury-product-rating"> <div class="luxury-stars">${ratingStars}</div> <span class="luxury-rating-value">${rating.toFixed(1)}</span> </div>` : ''} <div class="luxury-product-price"> ${product.discountedPrice && product.regularPrice ? `<div class="luxury-current-price">${formattedDiscountedPrice}</div> <div class="luxury-original-price">${formattedRegularPrice}</div>` : `<div class="luxury-current-price">${formattedRegularPrice}</div>`} </div> <button class="luxury-product-btn" onclick=" (function(event) { event.preventDefault(); var campaignId = '${campaign._id || ''}'; var productId = '${product.productId}'; var affiliateId = '${transaction?.data?.affiliate_id || ''}'; var targetUrl = '${productUrl}'; var userId = '${userId}'; fetch('https://ondc-sdk.samhita.org/api/transactions/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ campaign_id: campaignId, product_id: productId, affiliate_id: affiliateId || undefined, status: 'initiated', user_id: userId }) }) .then(function(response) { if (response.ok) { window.open(targetUrl, '_blank'); } else { console.error('Failed to update transaction'); window.open(targetUrl, '_blank'); } }) .catch(function(err) { console.error('Error updating transaction:', err); window.open(targetUrl, '_blank'); }); return false; })(event); "> Shop Now </button> </div> </div> `; } return ` <div class="modern-product-card"> ${discountPercent > 0 ? `<div class="modern-discount-badge">${discountPercent}% OFF</div>` : ''} <div class="product-image-container"> <img src="${productImage}" alt="${product.productName || ''}" class="modern-product-image" loading="lazy" onerror="this.onerror=null;this.src='https://via.placeholder.com/320x280?text=Product';" /> </div> <div class="modern-product-info"> ${brandName ? `<div class="modern-product-brand">${brandName}</div>` : ''} <h3 class="modern-product-name">${product.productName || ''}</h3> ${rating > 0 ? `<div class="modern-product-rating"> <div class="modern-stars">${ratingStars}</div> <span class="modern-rating-value">${rating.toFixed(1)}</span> </div>` : ''} <div class="modern-product-price"> ${product.discountedPrice && product.regularPrice ? `<div class="modern-current-price">${formattedDiscountedPrice}</div> <div class="modern-original-price">${formattedRegularPrice}</div>` : `<div class="modern-current-price">${formattedRegularPrice}</div>`} </div> <button class="modern-product-btn" onclick=" (function(event) { event.preventDefault(); var campaignId = '${campaign._id || ''}'; var productId = '${product.productId}'; var affiliateId = '${transaction?.data?.affiliate_id || ''}'; var targetUrl = '${productUrl}'; var userId = '${userId}'; fetch('https://ondc-sdk.samhita.org/api/transactions/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ campaign_id: campaignId, product_id: productId, affiliate_id: affiliateId || undefined, status: 'initiated', user_id: userId }) }) .then(function(response) { if (response.ok) { window.open(targetUrl, '_blank'); } else { console.error('Failed to update transaction'); window.open(targetUrl, '_blank'); } }) .catch(function(err) { console.error('Error updating transaction:', err); window.open(targetUrl, '_blank'); }); return false; })(event); "> View Product </button> </div> </div> `; } // Campaign Style Interface export interface CampaignStyle { titleFontSize?: 'small' | 'medium' | 'large' | 'xl'; titleColor?: string; titleFontWeight?: 'normal' | 'bold' | 'bolder'; descriptionFontSize?: 'small' | 'medium' | 'large'; descriptionColor?: string; bannerTemplate?: 'classic' | 'split' | 'overlay' | 'modern'; overlayOpacity?: number; showButton?: boolean; buttonText?: string; buttonBgColor?: string; buttonTextColor?: string; buttonBorderRadius?: number; buttonFontSize?: 'small' | 'medium' | 'large'; buttonFontWeight?: 'normal' | 'semibold' | 'bold'; } // Helper function to get font sizes function getFontSize(size: string, type: 'title' | 'description' | 'button'): string { const sizes = { title: { small: '1.5rem', medium: '2rem', large: '2.5rem', xl: '3rem' }, description: { small: '0.875rem', medium: '1rem', large: '1.125rem' }, button: { small: '0.875rem', medium: '1rem', large: '1.125rem' } }; return sizes[type][size as keyof typeof sizes[typeof type]] || sizes[type].medium; } // Helper function to generate carousel banner templates with dynamic styling function generateBannerTemplate(campaign: any, index: number, transaction?: any, userId?: string): string { const isActive = index === 0 ? 'active' : ''; const style: CampaignStyle = campaign.campaign_style || campaign.style || {}; const campaignName = campaign.campaignName || campaign.campaign_name || 'Featured Campaign'; const description = campaign.description || 'Discover amazing products and deals'; const banner = campaign.banner || 'https://images.unsplash.com/photo-1441986300917-64674bd600d8?w=800&h=400&fit=crop'; const campaignUrl = `https://seller.samhita.org/campaign/${campaign._id}?affiliate_id=${transaction?.data?.affiliate_id}&user_id=${userId}`; // Template selection based on bannerTemplate or fallback to template_name const templateName = style.bannerTemplate || campaign.template_name || campaign.templateName || 'classic'; console.log('selected campaign', campaign); const clickHandler = ` (function(event) { event.preventDefault(); var campaignId = '${campaign._id}'; var affiliateId = '${transaction?.data?.affiliate_id || ''}'; var userId = '${userId || ''}'; var campaignUrl = '${campaignUrl}'; ${transaction && userId ? ` fetch('https://ondc-sdk.samhita.org/api/transactions/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ campaign_id: campaignId, affiliate_id: affiliateId || undefined, status: 'campaign_initiated', user_id: userId }) }) .then(function(response) { if (response.ok) { window.open(campaignUrl, '_blank'); } else { console.error('Failed to track campaign click'); window.open(campaignUrl, '_blank'); } }) .catch(function(err) { console.error('Error tracking campaign click:', err); window.open(campaignUrl, '_blank'); }); ` : ` window.open(campaignUrl, '_blank'); `} return false; })(event); `; // Dynamic styling with proper defaults for each template type const titleFontSize = getFontSize(style.titleFontSize || 'medium', 'title'); const titleColor = style.titleColor || (templateName === 'modern' ? '#1f2937' : '#ffffff'); const titleFontWeight = style.titleFontWeight || 'bold'; const descriptionFontSize = getFontSize(style.descriptionFontSize || 'medium', 'description'); const descriptionColor = style.descriptionColor || (templateName === 'modern' ? '#4b5563' : '#e2e8f0'); const overlayOpacity = style.overlayOpacity || 0.5; const showButton = style.showButton !== false; // Default to true const buttonText = style.buttonText || 'Shop Now'; const buttonBgColor = style.buttonBgColor || (templateName === 'modern' ? '#8b5cf6' : '#3b82f6'); const buttonTextColor = style.buttonTextColor || '#ffffff'; const buttonBorderRadius = style.buttonBorderRadius || 8; const buttonFontSize = getFontSize(style.buttonFontSize || 'medium', 'button'); const buttonFontWeight = style.buttonFontWeight || 'semibold'; // Debug logging to console (can be removed in production) if (typeof console !== 'undefined') { console.log('Campaign Template Debug:', { campaignId: campaign._id, templateName, style: style, titleFontSize, titleColor, buttonText, showButton }); } switch (templateName.toLowerCase()) { case 'split': case 'left-aligned': case 'left_aligned': case 'leftaligned': return generateSplitTemplate(campaignName, description, banner, clickHandler, isActive, { titleFontSize, titleColor, titleFontWeight, descriptionFontSize, descriptionColor, overlayOpacity, showButton, buttonText, buttonBgColor, buttonTextColor, buttonBorderRadius, buttonFontSize, buttonFontWeight }); case 'overlay': return generateOverlayTemplate(campaignName, description, banner, clickHandler, isActive, { titleFontSize, titleColor, titleFontWeight, descriptionFontSize, descriptionColor, overlayOpacity, showButton, buttonText, buttonBgColor, buttonTextColor, buttonBorderRadius, buttonFontSize, buttonFontWeight }); case 'modern': case 'card-style': case 'card_style': case 'cardstyle': return generateModernTemplate(campaignName, description, banner, clickHandler, isActive, { titleFontSize, titleColor, titleFontWeight, descriptionFontSize, descriptionColor, overlayOpacity, showButton, buttonText, buttonBgColor, buttonTextColor, buttonBorderRadius, buttonFontSize, buttonFontWeight }); case 'classic': case 'centered': default: return generateClassicTemplate(campaignName, description, banner, clickHandler, isActive, { titleFontSize, titleColor, titleFontWeight, descriptionFontSize, descriptionColor, overlayOpacity, showButton, buttonText, buttonBgColor, buttonTextColor, buttonBorderRadius, buttonFontSize, buttonFontWeight }); } } function generateClassicTemplate(campaignName: string, description: string, banner: string, clickHandler: string, isActive: string, styles: any): string { return ` <div class="carousel-item ${isActive}" onclick="${clickHandler}" style="cursor: pointer;"> <div class="banner-showcase"> <div class="banner-container" style=" background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); position: relative; height: 480px; overflow: hidden; border-radius: 16px; "> ${!banner.includes('placeholder') ? ` <img src="${banner}" alt="Campaign banner" style=" width: 100%; height: 100%; object-fit: cover; position: absolute; top: 0; left: 0; z-index: 0; " onerror="this.style.display='none';" /> ` : ''} <div style=" position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,${styles.overlayOpacity}); z-index: 1; "></div> <div class="banner-content-wrapper template-centered" style=" position: absolute; top: 0; left: 0; right: 0; bottom: 0; display: flex; width: 100%; height: 100%; padding: 2.5rem; pointer-events: none; z-index: 2; align-items: center; justify-content: center; text-align: center; "> <div class="banner-text-content" style=" pointer-events: auto; z-index: 10; max-width: 600px; width: 100%; text-align: center; "> <h2 class="banner-headline" style=" font-size: ${styles.titleFontSize}; color: ${styles.titleColor}; font-weight: ${styles.titleFontWeight}; margin: 0 0 1rem 0; line-height: 1.2; word-wrap: break-word; white-space: normal; text-align: center; ">${campaignName}</h2> <p class="banner-description-text" style=" font-size: ${styles.descriptionFontSize}; color: ${styles.descriptionColor}; margin: 0 0 1.5rem 0; line-height: 1.5; word-wrap: break-word; white-space: normal; text-align: center; ">${description}</p> ${styles.showButton ? ` <button type="button" class="banner-cta-button" style=" background-color: ${styles.buttonBgColor};