app.js

18.05 KB
14/11/2025 13:00
JS
app.js
/* =============================================================================
   AFFILIATE PRODUCT AGGREGATOR - MAIN JAVASCRIPT
   Author: MiniMax Agent
   Description: Vanilla JavaScript for affiliate product aggregator website
   ============================================================================= */

// DOM Content Loaded Event
document.addEventListener('DOMContentLoaded', function() {
    initializeApp();
});

// Main Application Initialization
function initializeApp() {
    setupMobileMenu();
    setupSearchFunctionality();
    setupProductFilters();
    setupImageLazyLoading();
    setupSmoothScrolling();
    setupFormValidation();
    setupProductInteractions();
}

// =============================================================================
// MOBILE MENU FUNCTIONALITY
// =============================================================================
function setupMobileMenu() {
    const mobileMenuToggle = document.querySelector('.mobile-menu-toggle');
    const nav = document.querySelector('.nav');
    
    if (mobileMenuToggle && nav) {
        mobileMenuToggle.addEventListener('click', function() {
            nav.classList.toggle('active');
            mobileMenuToggle.classList.toggle('active');
            
            // Animate hamburger menu
            const spans = mobileMenuToggle.querySelectorAll('span');
            spans.forEach((span, index) => {
                if (mobileMenuToggle.classList.contains('active')) {
                    if (index === 0) span.style.transform = 'rotate(45deg) translate(5px, 5px)';
                    if (index === 1) span.style.opacity = '0';
                    if (index === 2) span.style.transform = 'rotate(-45deg) translate(7px, -6px)';
                } else {
                    span.style.transform = 'none';
                    span.style.opacity = '1';
                }
            });
        });
        
        // Close menu when clicking on nav links
        const navLinks = nav.querySelectorAll('a');
        navLinks.forEach(link => {
            link.addEventListener('click', () => {
                nav.classList.remove('active');
                mobileMenuToggle.classList.remove('active');
                
                // Reset hamburger menu
                const spans = mobileMenuToggle.querySelectorAll('span');
                spans.forEach(span => {
                    span.style.transform = 'none';
                    span.style.opacity = '1';
                });
            });
        });
    }
}

// =============================================================================
// SEARCH FUNCTIONALITY
// =============================================================================
function setupSearchFunctionality() {
    const searchForm = document.querySelector('.search-form');
    const searchInput = document.querySelector('.search-input');
    
    if (searchForm && searchInput) {
        searchForm.addEventListener('submit', function(e) {
            e.preventDefault();
            const query = searchInput.value.trim();
            
            if (query) {
                performSearch(query);
            }
        });
        
        // Live search suggestions (basic implementation)
        let searchTimeout;
        searchInput.addEventListener('input', function() {
            clearTimeout(searchTimeout);
            searchTimeout = setTimeout(() => {
                const query = this.value.trim();
                if (query.length > 2) {
                    // In a real implementation, this would fetch suggestions from an API
                    showSearchSuggestions(query);
                }
            }, 300);
        });
    }
}

function performSearch(query) {
    // In a real implementation, this would redirect to search results page
    console.log('Searching for:', query);
    
    // Show loading state
    const searchBtn = document.querySelector('.search-btn');
    if (searchBtn) {
        const originalText = searchBtn.textContent;
        searchBtn.innerHTML = '<span class="loading"></span> Searching...';
        searchBtn.disabled = true;
        
        // Simulate search delay
        setTimeout(() => {
            searchBtn.textContent = originalText;
            searchBtn.disabled = false;
            // Redirect to search results (in real implementation)
            // window.location.href = `/search?q=${encodeURIComponent(query)}`;
        }, 1500);
    }
}

function showSearchSuggestions(query) {
    // Basic implementation - in real app this would show actual suggestions
    console.log('Showing suggestions for:', query);
}

// =============================================================================
// PRODUCT FILTERS
// =============================================================================
function setupProductFilters() {
    const filterButtons = document.querySelectorAll('.filter-btn');
    const productGrid = document.querySelector('.product-grid');
    
    if (filterButtons.length && productGrid) {
        filterButtons.forEach(button => {
            button.addEventListener('click', function() {
                const filter = this.dataset.filter;
                
                // Update active button
                filterButtons.forEach(btn => btn.classList.remove('active'));
                this.classList.add('active');
                
                // Filter products
                filterProducts(filter);
            });
        });
    }
}

function filterProducts(filter) {
    const productCards = document.querySelectorAll('.product-card');
    
    productCards.forEach(card => {
        const category = card.dataset.category || '';
        const price = parseFloat(card.dataset.price) || 0;
        
        let shouldShow = true;
        
        // Apply filter logic
        switch(filter) {
            case 'price-low':
                shouldShow = price < 50;
                break;
            case 'price-high':
                shouldShow = price >= 50;
                break;
            case 'electronics':
                shouldShow = category === 'electronics';
                break;
            case 'fashion':
                shouldShow = category === 'fashion';
                break;
            case 'home':
                shouldShow = category === 'home';
                break;
            default:
                shouldShow = true; // Show all
        }
        
        // Animate card visibility
        if (shouldShow) {
            card.style.display = 'block';
            card.classList.add('fade-in');
        } else {
            card.style.display = 'none';
            card.classList.remove('fade-in');
        }
    });
}

// =============================================================================
// LAZY LOADING IMAGES
// =============================================================================
function setupImageLazyLoading() {
    const images = document.querySelectorAll('img[data-src]');
    
    if ('IntersectionObserver' in window) {
        const imageObserver = new IntersectionObserver((entries, observer) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    const img = entry.target;
                    img.src = img.dataset.src;
                    img.classList.remove('lazy');
                    imageObserver.unobserve(img);
                }
            });
        });
        
        images.forEach(img => imageObserver.observe(img));
    } else {
        // Fallback for older browsers
        images.forEach(img => {
            img.src = img.dataset.src;
        });
    }
}

// =============================================================================
// SMOOTH SCROLLING
// =============================================================================
function setupSmoothScrolling() {
    const links = document.querySelectorAll('a[href^="#"]');
    
    links.forEach(link => {
        link.addEventListener('click', function(e) {
            const href = this.getAttribute('href');
            
            if (href === '#') return;
            
            e.preventDefault();
            
            const target = document.querySelector(href);
            if (target) {
                const headerHeight = document.querySelector('.header').offsetHeight;
                const targetPosition = target.offsetTop - headerHeight - 20;
                
                window.scrollTo({
                    top: targetPosition,
                    behavior: 'smooth'
                });
            }
        });
    });
}

// =============================================================================
// FORM VALIDATION
// =============================================================================
function setupFormValidation() {
    const forms = document.querySelectorAll('form');
    
    forms.forEach(form => {
        form.addEventListener('submit', function(e) {
            if (!validateForm(this)) {
                e.preventDefault();
            }
        });
        
        // Real-time validation
        const inputs = form.querySelectorAll('input, textarea, select');
        inputs.forEach(input => {
            input.addEventListener('blur', () => validateField(input));
            input.addEventListener('input', () => clearFieldError(input));
        });
    });
}

function validateForm(form) {
    let isValid = true;
    const inputs = form.querySelectorAll('input, textarea, select');
    
    inputs.forEach(input => {
        if (!validateField(input)) {
            isValid = false;
        }
    });
    
    return isValid;
}

function validateField(field) {
    const value = field.value.trim();
    const type = field.type;
    const required = field.hasAttribute('required');
    
    // Clear previous errors
    clearFieldError(field);
    
    // Required field validation
    if (required && !value) {
        showFieldError(field, 'This field is required');
        return false;
    }
    
    // Email validation
    if (type === 'email' && value) {
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        if (!emailRegex.test(value)) {
            showFieldError(field, 'Please enter a valid email address');
            return false;
        }
    }
    
    // Phone validation
    if (type === 'tel' && value) {
        const phoneRegex = /^[\+]?[1-9][\d]{0,15}$/;
        if (!phoneRegex.test(value.replace(/[\s\-\(\)]/g, ''))) {
            showFieldError(field, 'Please enter a valid phone number');
            return false;
        }
    }
    
    return true;
}

function showFieldError(field, message) {
    field.classList.add('error');
    
    // Remove existing error message
    const existingError = field.parentNode.querySelector('.error-message');
    if (existingError) {
        existingError.remove();
    }
    
    // Add new error message
    const errorElement = document.createElement('span');
    errorElement.className = 'error-message';
    errorElement.textContent = message;
    errorElement.style.color = '#dc2626';
    errorElement.style.fontSize = '0.875rem';
    errorElement.style.marginTop = '0.25rem';
    errorElement.style.display = 'block';
    
    field.parentNode.appendChild(errorElement);
}

function clearFieldError(field) {
    field.classList.remove('error');
    const errorMessage = field.parentNode.querySelector('.error-message');
    if (errorMessage) {
        errorMessage.remove();
    }
}

// =============================================================================
// PRODUCT INTERACTIONS
// =============================================================================
function setupProductInteractions() {
    // Product card hover effects
    const productCards = document.querySelectorAll('.product-card');
    
    productCards.forEach(card => {
        card.addEventListener('mouseenter', function() {
            this.style.transform = 'translateY(-5px)';
        });
        
        card.addEventListener('mouseleave', function() {
            this.style.transform = 'translateY(0)';
        });
    });
    
    // Affiliate button tracking
    const affiliateButtons = document.querySelectorAll('.affiliate-btn, .btn-primary');
    
    affiliateButtons.forEach(button => {
        button.addEventListener('click', function(e) {
            // Track affiliate click (in real implementation)
            const productTitle = this.closest('.product-card')?.querySelector('.product-title')?.textContent || 'Unknown Product';
            console.log('Affiliate click tracked for:', productTitle);
            
            // Add loading state
            const originalText = this.textContent;
            this.innerHTML = '<span class="loading"></span> Redirecting...';
            this.disabled = true;
            
            // In real implementation, this would redirect to affiliate link
            setTimeout(() => {
                this.textContent = originalText;
                this.disabled = false;
            }, 1000);
        });
    });
    
    // Product quantity selector
    const quantityInputs = document.querySelectorAll('.quantity-input');
    
    quantityInputs.forEach(input => {
        input.addEventListener('change', function() {
            updateProductPrice(this);
        });
    });
}

function updateProductPrice(quantityInput) {
    const quantity = parseInt(quantityInput.value) || 1;
    const productCard = quantityInput.closest('.product-card');
    const basePrice = parseFloat(productCard.dataset.basePrice) || 0;
    const newPrice = (basePrice * quantity).toFixed(2);
    
    const priceElement = productCard.querySelector('.price-current');
    if (priceElement) {
        priceElement.textContent = `$${newPrice}`;
    }
}

// =============================================================================
// UTILITY FUNCTIONS
// =============================================================================
function debounce(func, wait) {
    let timeout;
    return function executedFunction(...args) {
        const later = () => {
            clearTimeout(timeout);
            func(...args);
        };
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
    };
}

function throttle(func, limit) {
    let inThrottle;
    return function() {
        const args = arguments;
        const context = this;
        if (!inThrottle) {
            func.apply(context, args);
            inThrottle = true;
            setTimeout(() => inThrottle = false, limit);
        }
    }
}

// Format currency
function formatCurrency(amount, currency = 'USD') {
    return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: currency
    }).format(amount);
}

// Local storage helpers
function setLocalStorage(key, value) {
    try {
        localStorage.setItem(key, JSON.stringify(value));
    } catch (e) {
        console.warn('LocalStorage not available:', e);
    }
}

function getLocalStorage(key) {
    try {
        const item = localStorage.getItem(key);
        return item ? JSON.parse(item) : null;
    } catch (e) {
        console.warn('LocalStorage not available:', e);
        return null;
    }
}

// =============================================================================
// SEO & ANALYTICS HELPERS
// =============================================================================
function trackPageView(page) {
    // In real implementation, this would send data to analytics service
    console.log('Page view tracked:', page);
}

function trackEvent(eventName, eventData) {
    // In real implementation, this would send event data to analytics
    console.log('Event tracked:', eventName, eventData);
}

// Track user interactions for SEO analysis
document.addEventListener('click', function(e) {
    if (e.target.matches('.product-card, .product-card *')) {
        trackEvent('product_click', {
            element: e.target.tagName,
            text: e.target.textContent?.trim()?.substring(0, 50)
        });
    }
});

// =============================================================================
// ERROR HANDLING
// =============================================================================
window.addEventListener('error', function(e) {
    console.error('JavaScript error:', e.error);
    // In production, this could send error reports to monitoring service
});

window.addEventListener('unhandledrejection', function(e) {
    console.error('Unhandled promise rejection:', e.reason);
    // In production, this could send error reports to monitoring service
});

// =============================================================================
// PERFORMANCE MONITORING
// =============================================================================
window.addEventListener('load', function() {
    // Track page load time
    const loadTime = performance.now();
    console.log('Page loaded in:', Math.round(loadTime), 'ms');
    
    // In real implementation, this could send performance metrics
});

// =============================================================================
// ACCESSIBILITY ENHANCEMENTS
// =============================================================================
// Keyboard navigation for product grid
document.addEventListener('keydown', function(e) {
    if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') {
        const focusedElement = document.activeElement;
        if (focusedElement.closest('.product-card')) {
            e.preventDefault();
            navigateProductGrid(focusedElement, e.key);
        }
    }
});

function navigateProductGrid(currentElement, direction) {
    const productCards = Array.from(document.querySelectorAll('.product-card'));
    const currentIndex = productCards.indexOf(currentElement.closest('.product-card'));
    
    let nextIndex;
    if (direction === 'ArrowRight') {
        nextIndex = (currentIndex + 1) % productCards.length;
    } else {
        nextIndex = currentIndex === 0 ? productCards.length - 1 : currentIndex - 1;
    }
    
    const nextCard = productCards[nextIndex];
    const focusableElement = nextCard.querySelector('a, button, [tabindex="0"]');
    if (focusableElement) {
        focusableElement.focus();
    }
}

// Reduce motion for users who prefer it
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
    document.documentElement.style.setProperty('--animation-duration', '0.01ms');
}