ondc-campaign-sdk
Version:
[](https://www.npmjs.com/package/ondc-campaign-sdk) [](LICENSE) [ โข 76.2 kB
text/typescript
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};