app.js

13.61 KB
18/08/2025 13:30
JS
app.js
// App JS moved from index.html

// Global variables
let cart = [];
let products = [];
const CART_KEY = 'blue_mobile_cart_v1';

// Load products from products.json
async function loadProducts() {
  const loading = document.getElementById('loading');
  const container = document.getElementById('productsContainer');

  loading.style.display = 'block';

  try {
    // Fetch local JSON file (simulate API response)
    const res = await fetch('./products.json', {cache: 'no-store'});
    const json = await res.json();

    // Support both plain array and API-style response { status, data: { products: [...] } }
    if (Array.isArray(json)) {
      products = json;
    } else if (json && json.data && Array.isArray(json.data.products)) {
      products = json.data.products;
      // optional: use metadata for logging or pagination UI later
      console.info('Products loaded from API-style response', {total: json.data.total || products.length, meta: json.meta || null});
    } else if (json && Array.isArray(json.products)) {
      products = json.products; // another possible shape
    } else {
      throw new Error('Unexpected products.json format');
    }

    // Optional small delay for UX
    await new Promise(resolve => setTimeout(resolve, 500));

    loading.style.display = 'none';
    displayProducts();

    // Load cart from localStorage if present
    const saved = localStorage.getItem(CART_KEY);
    if (saved) {
      try {
        cart = JSON.parse(saved) || [];
      } catch (e) {
        cart = [];
      }
      updateCartDisplay();
    }
  } catch (err) {
    loading.style.display = 'none';
    container.innerHTML = '<p class="text-danger">เกิดข้อผิดพลาดในการโหลดสินค้า</p>';
    console.error(err);
  }
}

function displayProducts() {
  const container = document.getElementById('productsContainer');
  container.innerHTML = '';

  products.forEach(product => {
    const discountPercent = Math.round(((product.originalPrice - product.price) / product.originalPrice) * 100);
    // Prefer local preprocessed WebP images when available; otherwise build WebP URLs for remote images
    const isLocal = !!product.image;
    let webpSmall, webpLarge;
    if (isLocal) {
      const imgBase = product.image; // e.g. images/product-1
      webpSmall = `${imgBase}-400.webp`;
      webpLarge = `${imgBase}-800.webp`;
    } else {
      // Build responsive WebP URLs (Unsplash supports fm=webp)
      const imgBase = product.image;
      webpSmall = imgBase.replace(/(\?)/, '&') + '&w=400&fm=webp';
      webpLarge = imgBase.replace(/(\?)/, '&') + '&w=800&fm=webp';
    }

    const productCard = `
      <div class="col-lg-4 col-md-6 mb-4">
        <div class="card h-100">
          <picture>
            <source type="image/webp" srcset="${webpSmall} 400w, ${webpLarge} 800w" />
            <img class="card-img-top" src="${webpSmall}" srcset="${webpSmall} 400w, ${webpLarge} 800w" sizes="(max-width: 768px) 400px, 800px" alt="${product.name}" loading="lazy">
          </picture>
          <div class="card-body">
            <h5 class="card-title">${product.name}</h5>
            <p class="card-text">${product.description}</p>
            <div class="d-flex justify-content-between align-items-center">
              <div>
                <span class="price">${product.price.toLocaleString()}</span> บาท
                <div class="original-price">${product.originalPrice.toLocaleString()} บาท</div>
                <span class="badge bg-success">ลด ${discountPercent}%</span>
              </div>
            </div>
          </div>
          <div class="card-footer bg-transparent">
            <button class="btn btn-gold w-100" onclick="addToCart(${product.id})">
              <i class="bi bi-cart-plus"></i> เพิ่มในตะกร้า
            </button>
          </div>
        </div>
      </div>
    `;
    container.innerHTML += productCard;
  });
}

function addToCart(productId) {
  const product = products.find(p => p.id === productId);
  const existingItem = cart.find(item => item.id === productId);

  if (existingItem) {
    existingItem.quantity += 1;
  } else {
    cart.push({...product, quantity: 1});
  }

  updateCartDisplay();
  saveCart();

  // Success animation
  const buttons = document.querySelectorAll(`button[onclick="addToCart(${productId})"]`);
  buttons.forEach(button => {
    button.classList.add('success-animation');
    button.innerHTML = '<i class="bi bi-check"></i> เพิ่มแล้ว';
    setTimeout(() => {
      button.classList.remove('success-animation');
      button.innerHTML = '<i class="bi bi-cart-plus"></i> เพิ่มในตะกร้า';
    }, 1000);
  });
}

function updateCartDisplay() {
  const cartCount = document.getElementById('cartCount');
  const cartItems = document.getElementById('cartItems');
  const cartTotal = document.getElementById('cartTotal');
  const checkoutBtn = document.getElementById('checkoutBtn');

  const totalItems = cart.reduce((sum, item) => sum + item.quantity, 0);
  const totalPrice = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);

  cartCount.textContent = totalItems;
  cartTotal.textContent = totalPrice.toLocaleString();

  if (cart.length === 0) {
    cartItems.innerHTML = '<p class="text-center text-muted">ตะกร้าสินค้าว่างเปล่า</p>';
    checkoutBtn.disabled = true;
  } else {
    cartItems.innerHTML = cart.map(item => `
                    <div class="d-flex justify-content-between align-items-center mb-3 p-3 border rounded">
                        <div class="d-flex align-items-center">
                            <img src="${item.image}-400.webp" width="60" height="60" class="rounded me-3" alt="${item.name}">
                            <div>
                                <h6 class="mb-1">${item.name}</h6>
                                <small class="text-muted">${item.price.toLocaleString()} บาท</small>
                            </div>
                        </div>
                        <div class="d-flex align-items-center">
                            <button class="btn btn-sm btn-outline-warning me-2" onclick="changeQuantity(${item.id}, -1)">-</button>
                            <span class="mx-2">${item.quantity}</span>
                            <button class="btn btn-sm btn-outline-warning me-3" onclick="changeQuantity(${item.id}, 1)">+</button>
                            <button class="btn btn-sm btn-outline-danger" onclick="removeFromCart(${item.id})">
                                <i class="bi bi-trash"></i>
                            </button>
                        </div>
                    </div>
                `).join('');
    checkoutBtn.disabled = false;
  }
}

function changeQuantity(productId, change) {
  const item = cart.find(item => item.id === productId);
  if (item) {
    item.quantity += change;
    if (item.quantity <= 0) {
      removeFromCart(productId);
    } else {
      updateCartDisplay();
      saveCart();
    }
  }
}

function removeFromCart(productId) {
  cart = cart.filter(item => item.id !== productId);
  updateCartDisplay();
  saveCart();
}

function proceedToCheckout() {
  if (cart.length === 0) return;

  const cartModal = bootstrap.Modal.getInstance(document.getElementById('cartModal'));
  if (cartModal) cartModal.hide();

  setTimeout(() => {
    updateOrderSummary();
    const checkoutModal = new bootstrap.Modal(document.getElementById('checkoutModal'));
    checkoutModal.show();
  }, 500);
}

function updateOrderSummary() {
  const orderSummary = document.getElementById('orderSummary');
  const orderTotal = document.getElementById('orderTotal');

  const totalPrice = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);
  const shipping = totalPrice >= 1000 ? 0 : 100; // ส่งฟรีเมื่อซื้อมากกว่า 1000 บาท
  const finalTotal = totalPrice + shipping;

  orderSummary.innerHTML = cart.map(item => `
                <div class="d-flex justify-content-between mb-2">
                    <span>${item.name} x${item.quantity}</span>
                    <span>${(item.price * item.quantity).toLocaleString()} บาท</span>
                </div>
            `).join('') + `
                <div class="d-flex justify-content-between mb-2">
                    <span>ค่าจัดส่ง</span>
                    <span>${shipping === 0 ? 'ฟรี' : shipping + ' บาท'}</span>
                </div>
            `;

  orderTotal.textContent = finalTotal.toLocaleString() + ' บาท';
}

async function processPayment() {
  const form = document.getElementById('checkoutForm');
  const formData = new FormData(form);

  // Validate required fields
  if (!form.checkValidity()) {
    form.reportValidity();
    return;
  }

  // Show loading state
  const paymentBtn = document.getElementById('processPaymentBtn');
  const originalText = paymentBtn.innerHTML;
  paymentBtn.disabled = true;
  paymentBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>กำลังดำเนินการ...';

  // Simulate payment processing
  await new Promise(resolve => setTimeout(resolve, 3000));

  // Simulate successful payment
  paymentBtn.innerHTML = '<i class="bi bi-check-circle"></i> ชำระเงินสำเร็จ';

  setTimeout(() => {
    const checkoutModal = bootstrap.Modal.getInstance(document.getElementById('checkoutModal'));
    if (checkoutModal) checkoutModal.hide();

    // Show success message
    alert('ชำระเงินสำเร็จ! ขอบคุณสำหรับการสั่งซื้อ เราจะจัดส่งสินค้าให้คุณในเร็วๆ นี้');

    // Clear cart
    cart = [];
    updateCartDisplay();
    saveCart();

    // Reset button
    paymentBtn.disabled = false;
    paymentBtn.innerHTML = originalText;
  }, 1500);
}

function saveCart() {
  try {
    localStorage.setItem(CART_KEY, JSON.stringify(cart));
  } catch (e) {
    console.warn('Failed to save cart', e);
  }
}

function scrollToProducts() {
  document.getElementById('products').scrollIntoView({behavior: 'smooth'});
}

// Payment method toggle & helpers
function initUI() {
  const paymentRadios = document.querySelectorAll('input[name="payment"]');
  const creditCardForm = document.getElementById('creditCardForm');

  paymentRadios.forEach(radio => {
    radio.addEventListener('change', function() {
      if (this.value === 'credit') {
        creditCardForm.style.display = 'block';
      } else {
        creditCardForm.style.display = 'none';
      }
    });
  });

  // Credit card number formatting
  const cardNumberInput = document.querySelector('input[placeholder*="หมายเลขบัตร"]');
  if (cardNumberInput) {
    cardNumberInput.addEventListener('input', function() {
      let value = this.value.replace(/\s+/g, '').replace(/[^0-9]/gi, '');
      let formattedValue = value.match(/.{1,4}/g)?.join(' ') || value;
      if (formattedValue !== this.value) {
        this.value = formattedValue;
      }
    });
  }

  // Expiry date formatting
  const expiryInput = document.querySelector('input[placeholder="MM/YY"]');
  if (expiryInput) {
    expiryInput.addEventListener('input', function() {
      let value = this.value.replace(/\D/g, '');
      if (value.length >= 2) {
        value = value.substring(0, 2) + '/' + value.substring(2, 4);
      }
      this.value = value;
    });
  }

  // Smooth scrolling for navigation links
  document.querySelectorAll('a[href^="#"]').forEach(anchor => {
    anchor.addEventListener('click', function(e) {
      e.preventDefault();
      const target = document.querySelector(this.getAttribute('href'));
      if (target) {
        const offsetTop = target.offsetTop - 80; // Account for fixed navbar
        window.scrollTo({
          top: offsetTop,
          behavior: 'smooth'
        });
      }
    });
  });

  // Navbar scroll effect
  window.addEventListener('scroll', function() {
    const navbar = document.querySelector('.navbar');
    if (window.scrollY > 50) {
      navbar.style.background = 'rgba(26, 26, 26, 0.98)';
    } else {
      navbar.style.background = 'rgba(26, 26, 26, 0.95)';
    }
  });
}

// Initialize on DOM ready
document.addEventListener('DOMContentLoaded', function() {
  initUI();
  loadProducts();
  registerServiceWorker();
});

// Register service worker
function registerServiceWorker() {
  if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('./sw.js').then(reg => {
      console.info('Service worker registered', reg.scope);
    }).catch(err => {
      console.warn('Service worker registration failed', err);
    });
  }
}

// PWA install prompt handling
let deferredPrompt = null;
function setupPWAInstall() {
  const installModalEl = document.getElementById('installModal');
  const installConfirm = document.getElementById('installConfirm');
  const installCancel = document.getElementById('installCancel');

  const bsModal = installModalEl ? new bootstrap.Modal(installModalEl) : null;

  window.addEventListener('beforeinstallprompt', (e) => {
    e.preventDefault();
    deferredPrompt = e;
    // show modal
    if (bsModal) bsModal.show();
  });

  installConfirm && installConfirm.addEventListener('click', async () => {
    if (!deferredPrompt) return;
    deferredPrompt.prompt();
    const choice = await deferredPrompt.userChoice;
    deferredPrompt = null;
    if (bsModal) bsModal.hide();
    console.info('PWA install choice', choice);
  });

  installCancel && installCancel.addEventListener('click', () => {
    if (bsModal) bsModal.hide();
  });

  window.addEventListener('appinstalled', () => {
    if (bsModal) bsModal.hide();
    console.info('App installed');
  });
}