File manager - Edit - /home/webapp69.cm.in.th/u69319090007/shoping/js/app.js
Back
/** * uายD Store Application * - Multi-Level Product Options Matrix * - User System: Register / Login / Profile / Orders / Coins * - Seller Dashboard: Add / Edit / Delete Products */ /* ==================================================== Storage helpers (simulate backend with localStorage) ==================================================== */ function loadUsers() { try { return JSON.parse(localStorage.getItem('shp_users')) || []; } catch { return []; } } function saveUsers(users) { localStorage.setItem('shp_users', JSON.stringify(users)); } function loadSession() { try { return JSON.parse(localStorage.getItem('shp_session')); } catch { return null; } } function saveSession(user) { localStorage.setItem('shp_session', user ? JSON.stringify(user) : ''); } function loadProducts() { try { return JSON.parse(localStorage.getItem('shp_user_products')) || []; } catch { return []; } } function saveProducts(prods) { localStorage.setItem('shp_user_products', JSON.stringify(prods)); } /* ==================================================== Application State ==================================================== */ const appState = { activeCategory: 'all', searchQuery: '', cart: [], activeVoucher: null, selectedProduct: null, selectedOptions: {}, customTextInput: '', modalQty: 1, selectedPayment: 'promptpay', user: null, useCoinsInCheckout: false, userProducts: loadProducts(), // seller-listed products editingProductId: null, // for edit mode in seller dashboard }; /* ==================================================== DOM Element refs ==================================================== */ let productsGrid, categoryGrid, cartDrawer, cartItemsList, cartBadge, cartSubtotal, cartDiscount, cartTotal, productModal, chatWindow; /* ==================================================== DOMContentLoaded ==================================================== */ document.addEventListener('DOMContentLoaded', () => { // Restore session const session = loadSession(); if (session) { // Reload freshest user object from users store const users = loadUsers(); const found = users.find(u => u.id === session.id); appState.user = found || session; } else if (typeof defaultUser !== 'undefined') { // seed demo user on first visit const users = loadUsers(); if (!users.find(u => u.id === defaultUser.id)) { users.push(defaultUser); saveUsers(users); } appState.user = defaultUser; saveSession(defaultUser); } initDOMReferences(); renderUserHeader(); renderCategories(); renderProducts(); startFlashSaleTimer(); setupEventListeners(); }); function initDOMReferences() { productsGrid = document.getElementById('products-grid'); categoryGrid = document.getElementById('categories-grid'); cartDrawer = document.getElementById('cart-drawer'); cartItemsList = document.getElementById('cart-items-list'); cartBadge = document.getElementById('cart-badge'); cartSubtotal = document.getElementById('cart-subtotal'); cartDiscount = document.getElementById('cart-discount'); cartTotal = document.getElementById('cart-total'); productModal = document.getElementById('product-modal'); chatWindow = document.getElementById('chat-window'); } /* ==================================================== USER HEADER ==================================================== */ function renderUserHeader() { const area = document.getElementById('user-header-area'); if (!area) return; if (appState.user) { area.innerHTML = ` <div class="user-menu-btn" onclick="openUserProfileModal('orders')"> <img src="${appState.user.avatar || 'https://ui-avatars.com/api/?name=' + encodeURIComponent(appState.user.name) + '&background=FF3D00&color=fff'}" class="user-avatar-img" alt="${appState.user.name}"> <div style="display:flex;flex-direction:column;text-align:left;"> <span class="user-name-text">${appState.user.name}</span> <span class="user-coins-pill"> <i class="fa-solid fa-coins" style="color:var(--dark);"></i> ${appState.user.coins || 0} Coins </span> </div> <i class="fa-solid fa-chevron-down" style="font-size:0.75rem;margin-left:4px;"></i> </div> `; } else { area.innerHTML = ` <button class="user-menu-btn" onclick="openAuthModal('login')"> <i class="fa-regular fa-user" style="font-size:1.1rem;"></i> <span>เข้าสู่ระบบ / สมัครสมาชิก</span> </button> `; } } /* ==================================================== AUTH MODAL — Login / Register tabs ==================================================== */ function openAuthModal(tab = 'login') { const modal = document.getElementById('auth-modal'); if (modal) modal.classList.add('active'); switchAuthTab(tab); } function closeAuthModal() { const modal = document.getElementById('auth-modal'); if (modal) modal.classList.remove('active'); } function switchAuthTab(tab) { ['login','register'].forEach(t => { const btn = document.getElementById(`auth-tab-${t}`); if (btn) btn.classList.toggle('active', t === tab); }); const loginForm = document.getElementById('auth-login-form'); const regForm = document.getElementById('auth-register-form'); if (loginForm) loginForm.style.display = tab === 'login' ? 'flex' : 'none'; if (regForm) regForm.style.display = tab === 'register' ? 'flex' : 'none'; } /* --- Login --- */ function handleLoginSubmit(e) { if (e) e.preventDefault(); clearAuthError('login'); const usernameOrEmail = document.getElementById('login-user-input').value.trim(); const password = document.getElementById('login-pass-input').value; const users = loadUsers(); const found = users.find(u => (u.username === usernameOrEmail || u.email === usernameOrEmail) && u.password === password ); if (!found) { showAuthError('login', 'ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง'); return; } appState.user = found; saveSession(found); renderUserHeader(); closeAuthModal(); showToast(`ยินดีต้อนรับกลับมา คุณ ${found.name}! 🎉`); } /* --- Register --- */ function handleRegisterSubmit(e) { if (e) e.preventDefault(); clearAuthError('register'); const name = document.getElementById('reg-name-input').value.trim(); const username = document.getElementById('reg-username-input').value.trim(); const email = document.getElementById('reg-email-input').value.trim(); const phone = document.getElementById('reg-phone-input').value.trim(); const password = document.getElementById('reg-pass-input').value; const confirm = document.getElementById('reg-pass-confirm-input').value; const role = document.querySelector('input[name="reg-role"]:checked')?.value || 'buyer'; if (!name || !username || !email || !password) { showAuthError('register', 'กรุณากรอกข้อมูลให้ครบทุกช่อง'); return; } if (password !== confirm) { showAuthError('register', 'รหัสผ่านทั้งสองช่องไม่ตรงกัน'); return; } if (password.length < 6) { showAuthError('register', 'รหัสผ่านต้องมีอย่างน้อย 6 ตัวอักษร'); return; } const users = loadUsers(); if (users.find(u => u.username === username)) { showAuthError('register', `ชื่อผู้ใช้ "${username}" มีคนใช้แล้ว`); return; } if (users.find(u => u.email === email)) { showAuthError('register', 'อีเมลนี้มีบัญชีอยู่แล้ว'); return; } const newUser = { id: 'usr-' + Date.now(), name, username, email, phone, password, role, // 'buyer' | 'seller' avatar: `https://ui-avatars.com/api/?name=${encodeURIComponent(name)}&background=FF3D00&color=fff&size=200`, coins: 50, // welcome bonus memberTier: role === 'seller' ? 'Shopee Seller' : 'Shopee Member', addresses: [], orders: [] }; users.push(newUser); saveUsers(users); appState.user = newUser; saveSession(newUser); renderUserHeader(); closeAuthModal(); showToast(`🎉 สมัครสมาชิก uายD สำเร็จ! ยินดีต้อนรับ คุณ ${name} (รับโบนัส 50 Coins!)`); if (role === 'seller') { setTimeout(() => openSellerDashboard(), 400); } } function showAuthError(form, msg) { const el = document.getElementById(`${form}-error-msg`); if (el) { el.textContent = msg; el.style.display = 'block'; } } function clearAuthError(form) { const el = document.getElementById(`${form}-error-msg`); if (el) { el.textContent = ''; el.style.display = 'none'; } } /* ==================================================== USER PROFILE MODAL ==================================================== */ function openUserProfileModal(tab = 'orders') { const modal = document.getElementById('user-profile-modal'); if (!modal) return; if (!appState.user) { openAuthModal('login'); return; } modal.classList.add('active'); switchUserTab(tab); } function closeUserProfileModal() { const modal = document.getElementById('user-profile-modal'); if (modal) modal.classList.remove('active'); } function switchUserTab(tabName) { document.querySelectorAll('.user-tab-btn').forEach(btn => btn.classList.remove('active')); const activeBtn = document.getElementById(`tab-btn-${tabName}`); if (activeBtn) activeBtn.classList.add('active'); const area = document.getElementById('user-tab-content-area'); if (!area || !appState.user) return; if (tabName === 'profile') { renderProfileTab(area); } else if (tabName === 'coins') { renderCoinsTab(area); } else if (tabName === 'orders') { renderOrdersTab(area); } } function renderProfileTab(area) { const u = appState.user; area.innerHTML = ` <div style="display:flex;flex-direction:column;gap:16px;"> <div style="display:flex;align-items:center;gap:16px;background:var(--gray-100);padding:16px;border-radius:var(--radius-md);"> <img src="${u.avatar}" style="width:70px;height:70px;border-radius:50%;object-fit:cover;border:3px solid var(--primary);"> <div> <h3 style="font-size:1.2rem;">${u.name}</h3> <span style="background:var(--warning);color:var(--dark);font-size:0.75rem;font-weight:700;padding:2px 8px;border-radius:4px;"> ${u.memberTier} </span> <p style="font-size:0.85rem;color:var(--gray-500);margin-top:4px;"> @${u.username} | ${u.email || ''}${u.phone ? ' | ' + u.phone : ''} </p> </div> </div> ${u.role === 'seller' ? ` <button onclick="closeUserProfileModal(); openSellerDashboard();" style="display:flex;align-items:center;gap:10px;padding:14px 20px;background:linear-gradient(135deg,#FF3D00,#FF007A);color:#fff;border:none;border-radius:var(--radius-md);font-weight:700;cursor:pointer;font-size:1rem;box-shadow:0 4px 15px rgba(255,61,0,0.3);"> <i class="fa-solid fa-store"></i> เปิด Seller Dashboard — จัดการสินค้าของฉัน </button> ` : ` <div style="background:#FFF9E6;padding:14px;border-radius:var(--radius-md);border:1px dashed var(--warning);display:flex;align-items:center;justify-content:space-between;gap:10px;"> <div> <div style="font-weight:700;"><i class="fa-solid fa-store" style="color:var(--primary);"></i> อยากเริ่มขายสินค้าบน uายD?</div> <div style="font-size:0.85rem;color:var(--gray-500);">อัปเกรดเป็น Seller ฟรี ไม่มีค่าใช้จ่าย</div> </div> <button onclick="upgradeToSeller()" style="background:var(--primary);color:#fff;border:none;padding:10px 18px;border-radius:var(--radius-full);font-weight:700;cursor:pointer;white-space:nowrap;"> สมัครเป็น Seller <i class="fa-solid fa-arrow-right"></i> </button> </div> `} <h4 style="font-size:1rem;"><i class="fa-solid fa-location-dot" style="color:var(--primary);"></i> สมุดที่อยู่</h4> ${u.addresses && u.addresses.length > 0 ? u.addresses.map((addr, i) => ` <div style="background:var(--white);border:1.5px solid var(--gray-300);padding:12px;border-radius:var(--radius-sm);font-size:0.9rem;display:flex;justify-content:space-between;align-items:center;"> <span>${addr}</span> ${i === 0 ? '<span style="background:var(--primary-light);color:var(--primary);font-size:0.75rem;font-weight:700;padding:2px 6px;border-radius:4px;">ค่าเริ่มต้น</span>' : ''} </div> `).join('') : '<p style="color:var(--gray-500);font-size:0.9rem;">ยังไม่มีที่อยู่จัดส่ง</p>'} <button onclick="logoutUser()" style="margin-top:10px;background:#FFEBEE;color:var(--danger);border:1px solid var(--danger);padding:10px;border-radius:var(--radius-sm);font-weight:700;cursor:pointer;"> <i class="fa-solid fa-right-from-bracket"></i> ออกจากระบบ </button> </div> `; } function renderCoinsTab(area) { const u = appState.user; area.innerHTML = ` <div style="text-align:center;padding:20px;background:linear-gradient(135deg,#FFF9E6,#FFF0ED);border-radius:var(--radius-md);border:1px dashed var(--warning);"> <i class="fa-solid fa-coins" style="font-size:3rem;color:#FFC107;margin-bottom:10px;"></i> <h2 style="font-size:2rem;color:var(--dark);">${u.coins} Coins</h2> <p style="font-size:0.9rem;color:var(--gray-800);margin-bottom:16px;">1 Coin = ส่วนลด 1 บาทเมื่อเช็คเอาต์!</p> <button onclick="claimDailyCoins()" style="background:var(--warning);color:var(--dark);border:none;padding:10px 24px;border-radius:var(--radius-full);font-weight:700;cursor:pointer;box-shadow:0 4px 12px rgba(255,193,7,0.4);"> <i class="fa-solid fa-gift"></i> รับฟรี +10 Coins ประจำวัน </button> </div> `; } function renderOrdersTab(area) { const u = appState.user; if (!u.orders || u.orders.length === 0) { area.innerHTML = '<p style="text-align:center;color:var(--gray-500);padding:30px;">ยังไม่มีประวัติการสั่งซื้อ</p>'; return; } area.innerHTML = u.orders.map(order => ` <div class="order-card"> <div class="order-header-row"> <div> <strong>ออเดอร์: ${order.orderId}</strong> <span style="color:var(--gray-500);margin-left:10px;">${order.date}</span> </div> <span class="order-status-badge"><i class="fa-solid fa-circle-check"></i> ${order.status}</span> </div> ${order.items.map(item => ` <div style="display:flex;gap:12px;margin-bottom:10px;"> <img src="${item.image}" style="width:60px;height:60px;border-radius:8px;object-fit:cover;"> <div style="flex:1;"> <div style="font-weight:600;font-size:0.9rem;">${item.title}</div> <div style="font-size:0.75rem;color:var(--gray-500);">${item.optionsText}</div> <div style="font-size:0.85rem;font-weight:700;color:var(--primary);margin-top:4px;">฿${item.price.toLocaleString()} x ${item.quantity}</div> </div> </div> `).join('')} <div style="display:flex;justify-content:space-between;align-items:center;border-top:1px dashed var(--gray-300);padding-top:10px;margin-top:10px;"> <span style="font-size:0.85rem;color:var(--gray-500);">พัสดุ: <strong>${order.trackingNo}</strong></span> <span style="font-size:1rem;font-weight:800;color:var(--primary);">ยอดรวม: ฿${order.totalAmount.toLocaleString()}</span> </div> </div> `).join(''); } function claimDailyCoins() { if (!appState.user) return; appState.user.coins += 10; persistUserUpdate(); switchUserTab('coins'); showToast('🎉 ได้รับฟรี +10 Coins ประจำวัน!'); } function upgradeToSeller() { if (!appState.user) return; appState.user.role = 'seller'; appState.user.memberTier = 'uายD Seller'; persistUserUpdate(); renderUserHeader(); switchUserTab('profile'); showToast('🎉 อัปเกรดเป็น uายD Seller สำเร็จแล้ว! เปิด Dashboard ได้เลย'); } function persistUserUpdate() { const users = loadUsers(); const idx = users.findIndex(u => u.id === appState.user.id); if (idx >= 0) users[idx] = appState.user; else users.push(appState.user); saveUsers(users); saveSession(appState.user); renderUserHeader(); } function logoutUser() { appState.user = null; saveSession(null); renderUserHeader(); closeUserProfileModal(); showToast('ออกจากระบบเรียบร้อยแล้ว'); } /* ==================================================== SELLER DASHBOARD ==================================================== */ function openSellerDashboard() { if (!appState.user) { openAuthModal('login'); return; } if (appState.user.role !== 'seller') { showToast('⚠️ เฉพาะ Seller เท่านั้นที่เข้าถึง Dashboard ได้'); return; } appState.editingProductId = null; const modal = document.getElementById('seller-dashboard-modal'); if (modal) modal.classList.add('active'); switchSellerTab('my-products'); } function closeSellerDashboard() { const modal = document.getElementById('seller-dashboard-modal'); if (modal) modal.classList.remove('active'); } function switchSellerTab(tab) { ['my-products', 'add-product'].forEach(t => { const btn = document.getElementById(`seller-tab-${t}`); if (btn) btn.classList.toggle('active', t === tab); }); const area = document.getElementById('seller-tab-content'); if (!area) return; if (tab === 'my-products') renderSellerProducts(area); else if (tab === 'add-product') renderAddProductForm(area); } /* ---- My Products List ---- */ function renderSellerProducts(area) { const myProds = appState.userProducts.filter(p => p.sellerId === appState.user.id); if (myProds.length === 0) { area.innerHTML = ` <div style="text-align:center;padding:40px;color:var(--gray-500);"> <i class="fa-solid fa-box-open" style="font-size:3rem;color:var(--gray-300);margin-bottom:12px;"></i> <h3>คุณยังไม่มีสินค้า</h3> <p>กดปุ่ม "เพิ่มสินค้าใหม่" เพื่อเริ่มขายสินค้าบน uายD!</p> <button onclick="switchSellerTab('add-product')" style="margin-top:16px;background:var(--gradient-main);color:#fff;border:none;padding:12px 24px;border-radius:var(--radius-full);font-weight:700;cursor:pointer;"> <i class="fa-solid fa-plus"></i> เพิ่มสินค้าแรกของคุณ </button> </div> `; return; } area.innerHTML = ` <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:16px;"> ${myProds.map(prod => ` <div style="background:var(--white);border-radius:var(--radius-md);overflow:hidden;box-shadow:var(--shadow-sm);border:1px solid rgba(0,0,0,0.06);"> <div style="position:relative;"> <img src="${prod.images[0]}" style="width:100%;height:180px;object-fit:cover;"> <span style="position:absolute;top:8px;left:8px;background:var(--gradient-flash);color:#fff;font-size:0.7rem;font-weight:800;padding:3px 7px;border-radius:5px;"> ${prod.discountBadge || 'ใหม่'} </span> </div> <div style="padding:14px;"> <div style="font-weight:700;font-size:0.9rem;margin-bottom:6px;line-height:1.3;">${prod.title}</div> <div style="font-size:1.1rem;font-weight:800;color:var(--primary);">฿${prod.basePrice.toLocaleString()}</div> <div style="font-size:0.75rem;color:var(--gray-500);margin-top:2px;"> <i class="fa-solid fa-layer-group"></i> ${prod.options.length} ตัวเลือก | <i class="fa-solid fa-star" style="color:var(--warning);"></i> ${prod.rating} </div> <div style="display:flex;gap:8px;margin-top:12px;"> <button onclick="editSellerProduct('${prod.id}')" style="flex:1;padding:8px;background:var(--primary-light);color:var(--primary);border:1px solid var(--primary);border-radius:var(--radius-sm);font-weight:700;cursor:pointer;font-size:0.82rem;"> <i class="fa-solid fa-pen-to-square"></i> แก้ไข </button> <button onclick="deleteSellerProduct('${prod.id}')" style="padding:8px 12px;background:#FFEBEE;color:var(--danger);border:1px solid var(--danger);border-radius:var(--radius-sm);font-weight:700;cursor:pointer;font-size:0.82rem;"> <i class="fa-solid fa-trash"></i> </button> </div> </div> </div> `).join('')} </div> `; } /* ---- Add / Edit Product Form ---- */ function renderAddProductForm(area, existingProd = null) { const p = existingProd || {}; const catOptions = categoriesData.filter(c => c.id !== 'all').map(c => `<option value="${c.id}" ${p.category === c.id ? 'selected' : ''}>${c.name}</option>` ).join(''); area.innerHTML = ` <form id="seller-product-form" onsubmit="submitSellerProduct(event)" style="display:flex;flex-direction:column;gap:16px;"> <h3 style="font-weight:700;font-size:1.1rem;color:var(--primary);"> <i class="fa-solid fa-${existingProd ? 'pen-to-square' : 'plus'}"></i> ${existingProd ? 'แก้ไขข้อมูลสินค้า' : 'เพิ่มสินค้าใหม่'} </h3> <!-- Basic Info --> <div style="display:grid;grid-template-columns:1fr 1fr;gap:14px;"> <div> <label class="seller-form-label">ชื่อสินค้า *</label> <input class="seller-form-input" id="sf-title" required placeholder="เช่น เสื้อฮู้ดดี้โอเวอร์ไซส์ สตรีทเกาหลี" value="${p.title || ''}"> </div> <div> <label class="seller-form-label">หมวดหมู่ *</label> <select class="seller-form-input" id="sf-category">${catOptions}</select> </div> </div> <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:14px;"> <div> <label class="seller-form-label">ราคาปกติ (฿) *</label> <input class="seller-form-input" id="sf-original-price" type="number" min="1" required placeholder="1290" value="${p.originalPrice || ''}"> </div> <div> <label class="seller-form-label">ราคาขาย (฿) *</label> <input class="seller-form-input" id="sf-base-price" type="number" min="1" required placeholder="690" value="${p.basePrice || ''}"> </div> <div> <label class="seller-form-label">ข้อความส่วนลด</label> <input class="seller-form-input" id="sf-discount-badge" placeholder="ลด 46%" value="${p.discountBadge || ''}"> </div> </div> <!-- Images --> <div> <label class="seller-form-label">URL รูปภาพสินค้า (หนึ่ง URL ต่อบรรทัด) *</label> <textarea class="seller-form-input" id="sf-images" rows="3" required placeholder="https://images.unsplash.com/photo-xxx?w=800 https://images.unsplash.com/photo-yyy?w=800" style="resize:vertical;">${(p.images || []).join('\n')}</textarea> <div id="sf-img-previews" style="display:flex;gap:8px;flex-wrap:wrap;margin-top:8px;"></div> </div> <!-- Options Builder --> <div> <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;"> <label class="seller-form-label" style="margin:0;">ตัวเลือกสินค้า (Options)</label> <button type="button" onclick="addProductOption()" style="background:var(--primary-light);color:var(--primary);border:1px solid var(--primary);padding:6px 12px;border-radius:6px;font-weight:700;cursor:pointer;font-size:0.82rem;"> <i class="fa-solid fa-plus"></i> เพิ่มกลุ่มตัวเลือก </button> </div> <div id="options-builder" style="display:flex;flex-direction:column;gap:12px;"> ${(p.options || []).map((opt, i) => renderOptionBuilderRow(opt, i)).join('')} </div> </div> <div style="display:flex;gap:12px;margin-top:8px;"> <button type="button" onclick="switchSellerTab('my-products')" style="flex:1;padding:12px;border:1px solid var(--gray-300);background:var(--white);border-radius:var(--radius-md);font-weight:700;cursor:pointer;"> ยกเลิก </button> <button type="submit" style="flex:2;padding:12px;background:var(--gradient-main);color:#fff;border:none;border-radius:var(--radius-md);font-weight:700;font-size:1rem;cursor:pointer;box-shadow:0 4px 15px rgba(255,61,0,0.3);"> <i class="fa-solid fa-check"></i> ${existingProd ? 'บันทึกการแก้ไข' : 'เผยแพร่สินค้า'} </button> </div> </form> `; // Live image preview const imgTextarea = document.getElementById('sf-images'); if (imgTextarea) { imgTextarea.addEventListener('input', previewImages); previewImages(); } } function renderOptionBuilderRow(opt, idx) { return ` <div class="option-builder-row" id="opt-row-${idx}" style="background:var(--gray-100);padding:12px;border-radius:var(--radius-sm);border:1px solid var(--gray-300);"> <div style="display:flex;gap:10px;align-items:center;margin-bottom:8px;"> <input class="seller-form-input" style="flex:1;" placeholder="ชื่อกลุ่มตัวเลือก เช่น เลือกสี" value="${opt.name || ''}" oninput="updateOptionName(${idx}, this.value)"> <select class="seller-form-input" style="width:140px;" onchange="updateOptionType(${idx}, this.value)"> <option value="chip" ${opt.type === 'chip' ? 'selected' : ''}>Chip (ปุ่มเลือก)</option> <option value="text_input" ${opt.type === 'text_input' ? 'selected' : ''}>Text Input (กรอกข้อความ)</option> </select> <button type="button" onclick="removeOptionRow(${idx})" style="background:#FFEBEE;color:var(--danger);border:1px solid var(--danger);padding:6px 10px;border-radius:6px;cursor:pointer;"> <i class="fa-solid fa-trash"></i> </button> </div> ${opt.type !== 'text_input' ? ` <div id="opt-values-${idx}" style="display:flex;flex-direction:column;gap:6px;"> ${(opt.values || []).map((v, vi) => renderOptionValueRow(idx, vi, v)).join('')} </div> <button type="button" onclick="addOptionValue(${idx})" style="margin-top:6px;background:var(--white);color:var(--primary);border:1px dashed var(--primary);padding:5px 10px;border-radius:6px;font-size:0.8rem;cursor:pointer;"> <i class="fa-solid fa-plus"></i> เพิ่มตัวเลือกย่อย </button> ` : `<input class="seller-form-input" placeholder="Placeholder ของช่องกรอก" value="${opt.placeholder || ''}" oninput="updateOptionPlaceholder(${idx}, this.value)">`} </div> `; } function renderOptionValueRow(optIdx, valIdx, val) { return ` <div style="display:flex;gap:8px;align-items:center;" id="val-row-${optIdx}-${valIdx}"> <input class="seller-form-input" style="flex:2;" placeholder="ชื่อตัวเลือก เช่น สีแดง / ไซส์ L" value="${val.label || ''}" oninput="updateOptionValue(${optIdx}, ${valIdx}, 'label', this.value)"> <input class="seller-form-input" style="width:110px;" type="number" placeholder="ราคา +/- (฿)" value="${val.priceDelta || 0}" oninput="updateOptionValue(${optIdx}, ${valIdx}, 'priceDelta', parseFloat(this.value)||0)"> <button type="button" onclick="removeOptionValue(${optIdx}, ${valIdx})" style="background:#FFEBEE;color:var(--danger);border:none;padding:6px 10px;border-radius:6px;cursor:pointer;font-size:0.75rem;">✕</button> </div> `; } /* ---- Options Builder State ---- */ let draftOptions = []; function addProductOption() { draftOptions.push({ id: 'opt-' + Date.now(), name: '', type: 'chip', values: [{ label: '', priceDelta: 0 }] }); refreshOptionsBuilder(); } function removeOptionRow(idx) { draftOptions.splice(idx, 1); refreshOptionsBuilder(); } function updateOptionName(idx, val) { draftOptions[idx].name = val; } function updateOptionType(idx, val) { draftOptions[idx].type = val; if (val === 'text_input') delete draftOptions[idx].values; else if (!draftOptions[idx].values) draftOptions[idx].values = [{ label: '', priceDelta: 0 }]; refreshOptionsBuilder(); } function updateOptionPlaceholder(idx, val) { draftOptions[idx].placeholder = val; } function addOptionValue(idx) { draftOptions[idx].values.push({ label: '', priceDelta: 0 }); refreshOptionsBuilder(); } function removeOptionValue(optIdx, valIdx) { draftOptions[optIdx].values.splice(valIdx, 1); refreshOptionsBuilder(); } function updateOptionValue(optIdx, valIdx, key, val) { draftOptions[optIdx].values[valIdx][key] = val; } function refreshOptionsBuilder() { const container = document.getElementById('options-builder'); if (!container) return; container.innerHTML = draftOptions.map((opt, i) => renderOptionBuilderRow(opt, i)).join(''); } /* ---- Image Preview ---- */ function previewImages() { const textarea = document.getElementById('sf-images'); const previews = document.getElementById('sf-img-previews'); if (!textarea || !previews) return; const urls = textarea.value.split('\n').map(u => u.trim()).filter(Boolean); previews.innerHTML = urls.slice(0, 6).map(url => ` <img src="${url}" style="width:60px;height:60px;object-fit:cover;border-radius:6px;border:1px solid var(--gray-300);" onerror="this.style.display='none'"> `).join(''); } /* ---- Submit Seller Product ---- */ function submitSellerProduct(e) { e.preventDefault(); const title = document.getElementById('sf-title').value.trim(); const category = document.getElementById('sf-category').value; const originalPrice = parseFloat(document.getElementById('sf-original-price').value); const basePrice = parseFloat(document.getElementById('sf-base-price').value); const discountBadge = document.getElementById('sf-discount-badge').value.trim() || 'ใหม่'; const images = document.getElementById('sf-images').value.split('\n').map(u => u.trim()).filter(Boolean); if (!title || !images.length || isNaN(basePrice)) { showToast('⚠️ กรุณากรอกข้อมูลให้ครบ (ชื่อสินค้า, ราคา, รูปภาพ)'); return; } const options = draftOptions.filter(o => o.name).map(o => ({ ...o })); const prodData = { id: appState.editingProductId || 'uprod-' + Date.now(), sellerId: appState.user.id, sellerName: appState.user.name, category, title, basePrice, originalPrice: originalPrice || basePrice, discountBadge, rating: 5.0, soldCount: 0, images, options, reviews: [] }; if (appState.editingProductId) { const idx = appState.userProducts.findIndex(p => p.id === appState.editingProductId); if (idx >= 0) appState.userProducts[idx] = prodData; showToast('✅ แก้ไขสินค้าสำเร็จแล้ว!'); } else { appState.userProducts.unshift(prodData); showToast('🎉 เผยแพร่สินค้าสำเร็จ! สินค้าของคุณปรากฏบนหน้าร้านแล้ว'); } saveProducts(appState.userProducts); appState.editingProductId = null; draftOptions = []; // Merge into productsData runtime and re-render mergeUserProducts(); renderProducts(); switchSellerTab('my-products'); } function editSellerProduct(id) { const prod = appState.userProducts.find(p => p.id === id); if (!prod) return; appState.editingProductId = id; draftOptions = JSON.parse(JSON.stringify(prod.options || [])); const area = document.getElementById('seller-tab-content'); const btn = document.getElementById('seller-tab-add-product'); if (btn) btn.classList.add('active'); if (area) renderAddProductForm(area, prod); } function deleteSellerProduct(id) { if (!confirm('ลบสินค้านี้ออกจากร้านค้า?')) return; appState.userProducts = appState.userProducts.filter(p => p.id !== id); saveProducts(appState.userProducts); mergeUserProducts(); renderProducts(); switchSellerTab('my-products'); showToast('ลบสินค้าเรียบร้อยแล้ว'); } function mergeUserProducts() { // Remove old user-products from productsData then add current const baseProds = productsData.filter(p => !p.sellerId); productsData.length = 0; appState.userProducts.forEach(p => productsData.push(p)); baseProds.forEach(p => productsData.push(p)); } /* ==================================================== CATEGORY & PRODUCT RENDERING ==================================================== */ function renderCategories() { if (!categoryGrid) return; categoryGrid.innerHTML = categoriesData.map(cat => ` <div class="category-chip ${appState.activeCategory === cat.id ? 'active' : ''}" onclick="selectCategory('${cat.id}')"> <div class="category-icon"><i class="fa-solid ${cat.icon}"></i></div> <span class="category-name">${cat.name}</span> </div> `).join(''); } function selectCategory(catId) { appState.activeCategory = catId; renderCategories(); renderProducts(); } function renderProducts() { if (!productsGrid) return; mergeUserProducts(); const filtered = productsData.filter(prod => { const matchesCat = appState.activeCategory === 'all' || prod.category === appState.activeCategory; const matchesSearch = prod.title.toLowerCase().includes(appState.searchQuery.toLowerCase()); return matchesCat && matchesSearch; }); if (filtered.length === 0) { productsGrid.innerHTML = ` <div style="grid-column:1/-1;text-align:center;padding:40px;color:var(--gray-500);"> <i class="fa-solid fa-box-open" style="font-size:3rem;margin-bottom:12px;color:var(--primary);"></i> <h3>ไม่พบสินค้าที่ค้นหา</h3> </div>`; return; } productsGrid.innerHTML = filtered.map(prod => { const optionTags = prod.options.map(opt => opt.name.split(' ')[0]).join(' • '); return ` <div class="product-card" onclick="openProductModal('${prod.id}')"> <div class="product-thumb"> <img src="${prod.images[0]}" alt="${prod.title}" class="product-img" loading="lazy"> <span class="badge-discount">${prod.discountBadge}</span> <span class="badge-options"><i class="fa-solid fa-sliders"></i> ${prod.options.length} ตัวเลือก</span> ${prod.sellerId ? `<span style="position:absolute;top:10px;right:10px;background:rgba(0,200,83,0.9);color:#fff;font-size:0.65rem;font-weight:700;padding:2px 7px;border-radius:var(--radius-full);">🏪 ร้านค้าของ ${prod.sellerName}</span>` : ''} </div> <div class="product-body"> <h3 class="product-title">${prod.title}</h3> <div class="product-options-preview"> <span class="preview-tag"><i class="fa-solid fa-list-check"></i> ${optionTags || 'ไม่มีตัวเลือก'}</span> </div> <div class="product-price-row"> <span class="current-price">฿${prod.basePrice.toLocaleString()}</span> <span class="original-price">฿${prod.originalPrice.toLocaleString()}</span> </div> <div class="product-footer-meta"> <span class="rating-stars"><i class="fa-solid fa-star"></i> ${prod.rating}</span> <span>ขายแล้ว ${prod.soldCount.toLocaleString()} ชิ้น</span> </div> </div> </div>`; }).join(''); } /* ==================================================== PRODUCT MODAL (Multi-Level Options) ==================================================== */ function openProductModal(productId) { const prod = productsData.find(p => p.id === productId); if (!prod) return; appState.selectedProduct = prod; appState.modalQty = 1; appState.customTextInput = ''; appState.selectedOptions = {}; prod.options.forEach(opt => { if ((opt.type === 'chip' || opt.type === 'chip_image') && opt.values?.length) { appState.selectedOptions[opt.id] = opt.values[0]; } }); renderModalContent(); productModal.classList.add('active'); } function closeModal() { productModal.classList.remove('active'); } function renderModalContent() { const prod = appState.selectedProduct; const modalContent = document.getElementById('modal-content'); if (!modalContent || !prod) return; let calculatedPrice = prod.basePrice; Object.values(appState.selectedOptions).forEach(v => { if (v?.priceDelta) calculatedPrice += v.priceDelta; }); const selectedImage = appState.selectedOptions.color?.img || prod.images[0]; modalContent.innerHTML = ` <button class="modal-close-btn" onclick="closeModal()"><i class="fa-solid fa-xmark"></i></button> <div class="modal-grid"> <div class="gallery-container"> <img src="${selectedImage}" id="modal-main-img" class="main-preview-image" alt="${prod.title}"> <div class="gallery-thumbs"> ${prod.images.map(img => ` <img src="${img}" class="gallery-thumb-item ${img === selectedImage ? 'active' : ''}" onclick="setMainImage('${img}')"> `).join('')} </div> </div> <div class="product-details-content"> <h2 class="detail-title">${prod.title}</h2> ${prod.sellerName ? `<div style="font-size:0.8rem;color:var(--success);font-weight:600;"><i class="fa-solid fa-store"></i> ร้านค้า: ${prod.sellerName}</div>` : ''} <div class="detail-price-box"> <span class="detail-current-price" id="modal-calculated-price">฿${(calculatedPrice * appState.modalQty).toLocaleString()}</span> <span class="detail-original-price">฿${prod.originalPrice.toLocaleString()}</span> <span style="margin-left:auto;font-size:0.8rem;color:var(--primary);font-weight:700;"><i class="fa-solid fa-bolt"></i> ราคาเรียลไทม์</span> </div> <div style="display:flex;flex-direction:column;gap:16px;"> ${prod.options.map(opt => renderOptionGroup(opt)).join('')} </div> <div class="option-group" style="margin-top:8px;"> <div class="option-title">จำนวน (Quantity)</div> <div class="quantity-control"> <button class="qty-btn" onclick="changeQty(-1)"><i class="fa-solid fa-minus"></i></button> <span class="qty-val" id="modal-qty-val">${appState.modalQty}</span> <button class="qty-btn" onclick="changeQty(1)"><i class="fa-solid fa-plus"></i></button> <span style="font-size:0.8rem;color:var(--gray-500);margin-left:12px;">พร้อมส่ง 120 ชิ้น</span> </div> </div> <div class="modal-actions-row"> <button class="btn-add-cart" onclick="addToCartFromModal(false)"> <i class="fa-solid fa-cart-plus"></i> เพิ่มลงตะกร้า </button> <button class="btn-buy-now" onclick="addToCartFromModal(true)">สั่งซื้อทันที</button> </div> </div> </div>`; } function renderOptionGroup(opt) { const currentSelected = appState.selectedOptions[opt.id]; if (opt.type === 'chip_image' || opt.type === 'chip') { return ` <div class="option-group"> <div class="option-title"> <span>${opt.name}</span> <span class="selected-val-label">${currentSelected?.label || ''}</span> </div> <div class="option-chips-container"> ${(opt.values || []).map(val => { const isSel = currentSelected?.label === val.label; return ` <div class="option-chip ${isSel ? 'selected' : ''}" onclick="selectOptionValue('${opt.id}', '${encodeURIComponent(JSON.stringify(val))}')"> ${val.img ? `<img src="${val.img}" class="chip-img">` : ''} <span>${val.label}${val.priceDelta ? ` (+${val.priceDelta}฿)` : ''}</span> </div>`; }).join('')} </div> </div>`; } else if (opt.type === 'text_input') { return ` <div class="option-group"> <div class="option-title">${opt.name}</div> <input type="text" class="custom-input-box" placeholder="${opt.placeholder || ''}" value="${appState.customTextInput}" oninput="updateCustomTextInput(this.value)"> </div>`; } return ''; } function setMainImage(url) { const el = document.getElementById('modal-main-img'); if (el) el.src = url; } function selectOptionValue(optId, jsonStr) { appState.selectedOptions[optId] = JSON.parse(decodeURIComponent(jsonStr)); renderModalContent(); } function updateCustomTextInput(val) { appState.customTextInput = val; } function changeQty(delta) { appState.modalQty = Math.max(1, appState.modalQty + delta); const el = document.getElementById('modal-qty-val'); if (el) el.innerText = appState.modalQty; const prod = appState.selectedProduct; if (!prod) return; let price = prod.basePrice; Object.values(appState.selectedOptions).forEach(v => { if (v?.priceDelta) price += v.priceDelta; }); const el2 = document.getElementById('modal-calculated-price'); if (el2) el2.innerText = `฿${(price * appState.modalQty).toLocaleString()}`; } /* ==================================================== CART ==================================================== */ function addToCartFromModal(buyNow = false) { const prod = appState.selectedProduct; if (!prod) return; let unitPrice = prod.basePrice; const summary = []; Object.entries(appState.selectedOptions).forEach(([, optVal]) => { if (optVal) { if (optVal.priceDelta) unitPrice += optVal.priceDelta; summary.push(optVal.label); } }); if (appState.customTextInput.trim()) summary.push(`สลักชื่อ: "${appState.customTextInput.trim()}"`); appState.cart.push({ cartId: Date.now() + Math.random().toString(36).slice(2, 5), productId: prod.id, title: prod.title, image: appState.selectedOptions.color?.img || prod.images[0], unitPrice, quantity: appState.modalQty, optionsText: summary.join(' | ') }); updateCartUI(); closeModal(); showToast(`เพิ่ม "${prod.title.substring(0, 22)}…" ลงตะกร้าแล้ว!`); if (buyNow) toggleCartDrawer(true); } function removeCartItem(cartId) { appState.cart = appState.cart.filter(i => i.cartId !== cartId); updateCartUI(); } function toggleCartDrawer(open) { if (cartDrawer) cartDrawer.classList.toggle('active', open); } function updateCartUI() { const total = appState.cart.reduce((s, i) => s + i.quantity, 0); if (cartBadge) cartBadge.innerText = total; if (cartItemsList) { cartItemsList.innerHTML = appState.cart.length === 0 ? `<div style="text-align:center;padding:40px;color:var(--gray-500);"><i class="fa-solid fa-cart-arrow-down" style="font-size:2.5rem;margin-bottom:10px;color:var(--gray-300);"></i><p>ไม่มีสินค้าในตะกร้า</p></div>` : appState.cart.map(item => ` <div class="cart-item-card"> <img src="${item.image}" class="cart-item-img"> <div class="cart-item-info"> <span class="cart-item-title">${item.title}</span> <span class="cart-item-options">${item.optionsText}</span> <span class="cart-item-price">฿${item.unitPrice.toLocaleString()} x ${item.quantity}</span> </div> <button class="remove-cart-item" onclick="removeCartItem('${item.cartId}')"><i class="fa-solid fa-trash-can"></i></button> </div>`).join(''); } const subtotal = appState.cart.reduce((s, i) => s + i.unitPrice * i.quantity, 0); let discount = 0; if (appState.activeVoucher) { const v = appState.activeVoucher; discount = v.type === 'percent' ? Math.min(500, subtotal * v.value) : v.value; } if (cartSubtotal) cartSubtotal.innerText = `฿${subtotal.toLocaleString()}`; if (cartDiscount) cartDiscount.innerText = `-฿${discount.toLocaleString()}`; if (cartTotal) cartTotal.innerText = `฿${Math.max(0, subtotal - discount).toLocaleString()}`; } function applyVoucherCode() { const input = document.getElementById('voucher-code-input'); if (!input) return; const code = input.value.trim().toUpperCase(); if (vouchersData[code]) { appState.activeVoucher = vouchersData[code]; updateCartUI(); showToast(`ใช้โค้ดส่วนลด "${code}" สำเร็จ!`); } else { showToast('โค้ดไม่ถูกต้อง ลองใส่ SHOPEE50 หรือ FREESHIP'); } } /* ==================================================== CHECKOUT ==================================================== */ function openCheckoutModal() { if (appState.cart.length === 0) { showToast('กรุณาเลือกสินค้าก่อนชำระเงิน'); return; } toggleCartDrawer(false); const m = document.getElementById('checkout-modal'); if (m) m.classList.add('active'); renderCheckoutSummary(); } function closeCheckoutModal() { const m = document.getElementById('checkout-modal'); if (m) m.classList.remove('active'); } function selectPaymentMethod(method) { appState.selectedPayment = method; document.querySelectorAll('.payment-option').forEach(el => el.classList.remove('selected')); const t = document.getElementById(`pay-${method}`); if (t) t.classList.add('selected'); const qr = document.getElementById('qr-payment-box'); if (qr) qr.style.display = method === 'promptpay' ? 'block' : 'none'; } function toggleUseCoins(checked) { appState.useCoinsInCheckout = checked; renderCheckoutSummary(); } function renderCheckoutSummary() { const subtotal = appState.cart.reduce((s, i) => s + i.unitPrice * i.quantity, 0); let discount = 0; if (appState.activeVoucher) { discount = appState.activeVoucher.type === 'percent' ? Math.min(500, subtotal * appState.activeVoucher.value) : appState.activeVoucher.value; } const coinsDiscount = (appState.useCoinsInCheckout && appState.user) ? Math.min(appState.user.coins, subtotal - discount) : 0; const grand = Math.max(0, subtotal - discount - coinsDiscount); const el = document.getElementById('checkout-total-val'); if (el) el.innerText = `฿${grand.toLocaleString()}`; const coinsLabel = document.getElementById('checkout-coins-label'); if (coinsLabel && appState.user) { coinsLabel.textContent = `ใช้ ${appState.user.coins} Coins (ลดเพิ่ม ${appState.user.coins} บาท)`; } } function confirmOrderCheckout() { const subtotal = appState.cart.reduce((s, i) => s + i.unitPrice * i.quantity, 0); let discount = 0; if (appState.activeVoucher) { discount = appState.activeVoucher.type === 'percent' ? Math.min(500, subtotal * appState.activeVoucher.value) : appState.activeVoucher.value; } let coinsDiscount = 0; if (appState.useCoinsInCheckout && appState.user) { coinsDiscount = Math.min(appState.user.coins, subtotal - discount); appState.user.coins -= coinsDiscount; } const finalAmount = Math.max(0, subtotal - discount - coinsDiscount); const orderId = 'SHP-' + Math.floor(1e5 + Math.random() * 9e5); if (appState.user) { appState.user.orders = appState.user.orders || []; appState.user.orders.unshift({ orderId, date: new Date().toISOString().split('T')[0], status: 'เตรียมจัดส่งสินค้า', trackingNo: 'TH' + Date.now().toString().slice(-8), totalAmount: finalAmount, items: appState.cart.map(i => ({ title: i.title, optionsText: i.optionsText, price: i.unitPrice, quantity: i.quantity, image: i.image })) }); persistUserUpdate(); } closeCheckoutModal(); appState.cart = []; appState.activeVoucher = null; appState.useCoinsInCheckout = false; updateCartUI(); showToast(`🎉 สั่งซื้อสำเร็จ! ดูสถานะออเดอร์ในโปรไฟล์ได้เลย`); alert(`✨ ขอบคุณสำหรับการสั่งซื้อบน uายD!\nหมายเลขออเดอร์: ${orderId}\nยอดรวม: ฿${finalAmount.toLocaleString()}`); } /* ==================================================== LIVE CHAT ==================================================== */ function toggleChatWindow() { if (chatWindow) chatWindow.classList.toggle('active'); } function sendChatMessage() { const input = document.getElementById('chat-input'); if (!input || !input.value.trim()) return; const msg = input.value.trim(); const box = document.getElementById('chat-msg-container'); if (!box) return; box.innerHTML += `<div class="chat-msg user">${msg}</div>`; input.value = ''; box.scrollTop = box.scrollHeight; setTimeout(() => { box.innerHTML += `<div class="chat-msg seller"><i class="fa-solid fa-store" style="color:var(--primary);"></i> uายD Store: ขอบคุณครับ! แอดมินจะตอบกลับภายใน 1 นาที 😊</div>`; box.scrollTop = box.scrollHeight; }, 900); } /* ==================================================== FLASH SALE TIMER & SEARCH ==================================================== */ function startFlashSaleTimer() { let sec = 3 * 3600 + 45 * 60 + 12; setInterval(() => { sec = sec > 0 ? sec - 1 : 86400; const h = Math.floor(sec / 3600), m = Math.floor((sec % 3600) / 60), s = sec % 60; const pad = n => n.toString().padStart(2, '0'); const hEl = document.getElementById('timer-hours'); const mEl = document.getElementById('timer-mins'); const sEl = document.getElementById('timer-secs'); if (hEl) hEl.innerText = pad(h); if (mEl) mEl.innerText = pad(m); if (sEl) sEl.innerText = pad(s); }, 1000); } function setupEventListeners() { const si = document.getElementById('search-input'); if (si) si.addEventListener('input', e => { appState.searchQuery = e.target.value; renderProducts(); }); } function quickSearch(tag) { const si = document.getElementById('search-input'); if (si) { si.value = tag; appState.searchQuery = tag; renderProducts(); } } /* ==================================================== TOAST ==================================================== */ function showToast(msg) { let container = document.getElementById('toast-container'); if (!container) { container = document.createElement('div'); container.id = 'toast-container'; container.className = 'toast-container'; document.body.appendChild(container); } const t = document.createElement('div'); t.className = 'toast'; t.innerHTML = `<i class="fa-solid fa-circle-check" style="color:var(--warning);"></i> ${msg}`; container.appendChild(t); setTimeout(() => t.remove(), 3500); }
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.23 |
proxy
|
phpinfo
|
Settings